feat: add TypeScript type checking task to VSCode configuration

feat: implement area metadata blocks in game commands for enhanced area details

fix: refactor game commands to utilize new area metadata blocks and improve code consistency

feat: enhance Appwrite API integration with additional collections and storage support

refactor: update componentsV2 to support image blocks in display rendering
This commit is contained in:
2025-10-07 22:17:03 -05:00
parent 5ea9cfd67c
commit 67643595f3
9 changed files with 572 additions and 215 deletions

View File

@@ -1,4 +1,5 @@
import { prisma } from "../../../core/database/prisma";
import { textBlock, dividerBlock } from "../../../core/lib/componentsV2";
import type { GameArea } from "@prisma/client";
import type { ItemProps } from "../../../game/economy/types";
import type {
@@ -209,6 +210,44 @@ export async function fetchItemBasics(
return result;
}
export type AreaMetadata =
| {
previewImage?: string;
image?: string;
referenceImage?: string;
description?: string;
[k: string]: any;
}
| null
| undefined;
export function buildAreaMetadataBlocks(
area: Pick<GameArea, "metadata" | "key" | "name">
) {
const blocks: any[] = [];
const meta = (area.metadata as AreaMetadata) || undefined;
if (!meta) return blocks;
const img = meta.previewImage || meta.image || meta.referenceImage;
const desc =
typeof meta.description === "string" && meta.description.trim().length > 0
? meta.description.trim()
: null;
if (desc) {
blocks.push(textBlock(`**🗺️ Detalles del área**\n${desc}`));
}
if (img && typeof img === "string") {
// Mostrar también como texto para compatibilidad, y dejar que el renderer agregue imagen si soporta
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
blocks.push(textBlock(`**🖼️ Mapa/Imagen:** ${img}`));
// Si el renderer soporta bloque de imagen, los consumidores podrán usarlo
// @ts-ignore: el builder acepta bloques extendidos
blocks.push({ kind: "image", url: img });
}
return blocks;
}
export interface KeyPickerOption {
value: string;
label: string;

View File

@@ -1,21 +1,36 @@
import type { CommandMessage } from '../../../core/types/commands';
import type Amayo from '../../../core/client';
import { runMinigame } from '../../../game/minigames/service';
import { getDefaultLevel, findBestToolKey, parseGameArgs, resolveGuildAreaWithFallback, resolveAreaByType, sendDisplayReply, fetchItemBasics, formatItemLabel } from './_helpers';
import { updateStats } from '../../../game/stats/service';
import { updateQuestProgress } from '../../../game/quests/service';
import { checkAchievements } from '../../../game/achievements/service';
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
import type { CommandMessage } from "../../../core/types/commands";
import type Amayo from "../../../core/client";
import { runMinigame } from "../../../game/minigames/service";
import {
getDefaultLevel,
findBestToolKey,
parseGameArgs,
resolveGuildAreaWithFallback,
resolveAreaByType,
sendDisplayReply,
fetchItemBasics,
formatItemLabel,
} from "./_helpers";
import { updateStats } from "../../../game/stats/service";
import { updateQuestProgress } from "../../../game/quests/service";
import { checkAchievements } from "../../../game/achievements/service";
import {
buildDisplay,
dividerBlock,
textBlock,
} from "../../../core/lib/componentsV2";
import { buildAreaMetadataBlocks } from "./_helpers";
const MINING_ACCENT = 0xC27C0E;
const MINING_ACCENT = 0xc27c0e;
export const command: CommandMessage = {
name: 'mina',
type: 'message',
aliases: ['minar'],
name: "mina",
type: "message",
aliases: ["minar"],
cooldown: 5,
description: 'Ir a la mina (usa pico si está disponible) y obtener recompensas según el nivel.',
usage: 'mina [nivel] [toolKey] [area:clave] (ej: mina 2 tool.pickaxe.basic)',
description:
"Ir a la mina (usa pico si está disponible) y obtener recompensas según el nivel.",
usage: "mina [nivel] [toolKey] [area:clave] (ej: mina 2 tool.pickaxe.basic)",
run: async (message, args, _client: Amayo) => {
const userId = message.author.id;
const guildId = message.guild!.id;
@@ -23,59 +38,89 @@ export const command: CommandMessage = {
const areaInfo = areaOverride
? await resolveGuildAreaWithFallback(guildId, areaOverride)
: await resolveAreaByType(guildId, 'MINE');
: await resolveAreaByType(guildId, "MINE");
if (!areaInfo.area) {
if (areaOverride) {
await message.reply(`⚠️ No existe un área con key \`${areaOverride}\` para este servidor.`);
await message.reply(
`⚠️ No existe un área con key \`${areaOverride}\` para este servidor.`
);
} else {
await message.reply('⚠️ No hay un área de tipo **MINE** configurada. Crea una con `!area-crear` o especifica `area:<key>`.');
await message.reply(
"⚠️ No hay un área de tipo **MINE** configurada. Crea una con `!area-crear` o especifica `area:<key>`."
);
}
return;
}
const { area, source } = areaInfo;
const globalNotice = source === 'global'
? ` Usando configuración global para \`${area.key}\`. Puedes crear \`gameArea\` tipo **MINE** para personalizarla en este servidor.`
: null;
const globalNotice =
source === "global"
? ` Usando configuración global para \`${area.key}\`. Puedes crear \`gameArea\` tipo **MINE** para personalizarla en este servidor.`
: null;
const level = levelArg ?? await getDefaultLevel(userId, guildId, area.id);
const toolKey = providedTool ?? await findBestToolKey(userId, guildId, 'pickaxe');
const level = levelArg ?? (await getDefaultLevel(userId, guildId, area.id));
const toolKey =
providedTool ?? (await findBestToolKey(userId, guildId, "pickaxe"));
try {
const result = await runMinigame(userId, guildId, area.key, level, { toolKey: toolKey ?? undefined });
const result = await runMinigame(userId, guildId, area.key, level, {
toolKey: toolKey ?? undefined,
});
const rewardKeys = result.rewards
.filter((r): r is { type: 'item'; itemKey: string; qty?: number } => r.type === 'item' && Boolean(r.itemKey))
.filter(
(r): r is { type: "item"; itemKey: string; qty?: number } =>
r.type === "item" && Boolean(r.itemKey)
)
.map((r) => r.itemKey!);
if (result.tool?.key) rewardKeys.push(result.tool.key);
const rewardItems = await fetchItemBasics(guildId, rewardKeys);
// Actualizar stats
await updateStats(userId, guildId, { minesCompleted: 1 });
// Actualizar progreso de misiones
await updateQuestProgress(userId, guildId, 'mine_count', 1);
// Verificar logros
const newAchievements = await checkAchievements(userId, guildId, 'mine_count');
const rewardLines = result.rewards.length
? result.rewards.map((r) => {
if (r.type === 'coins') return `• 🪙 +${r.amount}`;
const info = rewardItems.get(r.itemKey!);
const label = formatItemLabel(info ?? { key: r.itemKey!, name: null, icon: null });
return `${label} x${r.qty ?? 1}`;
}).join('\n')
: '• —';
const mobsLines = result.mobs.length
? result.mobs.map(m => `${m}`).join('\n')
: '• —';
const toolInfo = result.tool?.key
? `${formatItemLabel(rewardItems.get(result.tool.key) ?? { key: result.tool.key, name: null, icon: null }, { fallbackIcon: '🔧' })}${result.tool.broken ? ' (rota)' : ` (-${result.tool.durabilityDelta ?? 0} dur.)`}`
: '—';
await updateQuestProgress(userId, guildId, "mine_count", 1);
const blocks = [textBlock('# ⛏️ Mina')];
// Verificar logros
const newAchievements = await checkAchievements(
userId,
guildId,
"mine_count"
);
const rewardLines = result.rewards.length
? result.rewards
.map((r) => {
if (r.type === "coins") return `• 🪙 +${r.amount}`;
const info = rewardItems.get(r.itemKey!);
const label = formatItemLabel(
info ?? { key: r.itemKey!, name: null, icon: null }
);
return `${label} x${r.qty ?? 1}`;
})
.join("\n")
: "• —";
const mobsLines = result.mobs.length
? result.mobs.map((m) => `${m}`).join("\n")
: "• —";
const toolInfo = result.tool?.key
? `${formatItemLabel(
rewardItems.get(result.tool.key) ?? {
key: result.tool.key,
name: null,
icon: null,
},
{ fallbackIcon: "🔧" }
)}${
result.tool.broken
? " (rota)"
: ` (-${result.tool.durabilityDelta ?? 0} dur.)`
}`
: "—";
const blocks = [textBlock("# ⛏️ Mina")];
if (globalNotice) {
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
@@ -83,16 +128,32 @@ export const command: CommandMessage = {
}
blocks.push(dividerBlock());
const areaScope = source === 'global' ? '🌐 Configuración global' : '📍 Configuración local';
blocks.push(textBlock(`**Área:** \`${area.key}\`${areaScope}\n**Nivel:** ${level}\n**Herramienta:** ${toolInfo}`));
const areaScope =
source === "global"
? "🌐 Configuración global"
: "📍 Configuración local";
blocks.push(
textBlock(
`**Área:** \`${area.key}\`${areaScope}\n**Nivel:** ${level}\n**Herramienta:** ${toolInfo}`
)
);
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
blocks.push(textBlock(`**Recompensas**\n${rewardLines}`));
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
blocks.push(textBlock(`**Mobs**\n${mobsLines}`));
// Añadir metadata del área (imagen/descripcion) si existe
const metaBlocks = buildAreaMetadataBlocks(area);
if (metaBlocks.length) {
blocks.push(dividerBlock());
blocks.push(...metaBlocks);
}
if (newAchievements.length > 0) {
blocks.push(dividerBlock({ divider: false, spacing: 2 }));
const achLines = newAchievements.map(ach => `✨ **${ach.name}** — ${ach.description}`).join('\n');
const achLines = newAchievements
.map((ach) => `✨ **${ach.name}** — ${ach.description}`)
.join("\n");
blocks.push(textBlock(`🏆 ¡Logro desbloqueado!\n${achLines}`));
}
@@ -101,6 +162,5 @@ export const command: CommandMessage = {
} catch (e: any) {
await message.reply(`❌ No se pudo minar: ${e?.message ?? e}`);
}
}
},
};

View File

@@ -1,21 +1,36 @@
import type { CommandMessage } from '../../../core/types/commands';
import type Amayo from '../../../core/client';
import { runMinigame } from '../../../game/minigames/service';
import { getDefaultLevel, findBestToolKey, parseGameArgs, resolveGuildAreaWithFallback, resolveAreaByType, sendDisplayReply, fetchItemBasics, formatItemLabel } from './_helpers';
import { updateStats } from '../../../game/stats/service';
import { updateQuestProgress } from '../../../game/quests/service';
import { checkAchievements } from '../../../game/achievements/service';
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
import type { CommandMessage } from "../../../core/types/commands";
import type Amayo from "../../../core/client";
import { runMinigame } from "../../../game/minigames/service";
import {
getDefaultLevel,
findBestToolKey,
parseGameArgs,
resolveGuildAreaWithFallback,
resolveAreaByType,
sendDisplayReply,
fetchItemBasics,
formatItemLabel,
} from "./_helpers";
import { updateStats } from "../../../game/stats/service";
import { updateQuestProgress } from "../../../game/quests/service";
import { checkAchievements } from "../../../game/achievements/service";
import {
buildDisplay,
dividerBlock,
textBlock,
} from "../../../core/lib/componentsV2";
import { buildAreaMetadataBlocks } from "./_helpers";
const FIGHT_ACCENT = 0x992D22;
const FIGHT_ACCENT = 0x992d22;
export const command: CommandMessage = {
name: 'pelear',
type: 'message',
aliases: ['fight','arena'],
name: "pelear",
type: "message",
aliases: ["fight", "arena"],
cooldown: 8,
description: 'Entra a la arena y pelea (usa espada si está disponible).',
usage: 'pelear [nivel] [toolKey] [area:clave] (ej: pelear 1 weapon.sword.iron)',
description: "Entra a la arena y pelea (usa espada si está disponible).",
usage:
"pelear [nivel] [toolKey] [area:clave] (ej: pelear 1 weapon.sword.iron)",
run: async (message, args, _client: Amayo) => {
const userId = message.author.id;
const guildId = message.guild!.id;
@@ -23,63 +38,98 @@ export const command: CommandMessage = {
const areaInfo = areaOverride
? await resolveGuildAreaWithFallback(guildId, areaOverride)
: await resolveAreaByType(guildId, 'FIGHT');
: await resolveAreaByType(guildId, "FIGHT");
if (!areaInfo.area) {
if (areaOverride) {
await message.reply(`⚠️ No existe un área con key \`${areaOverride}\` para este servidor.`);
await message.reply(
`⚠️ No existe un área con key \`${areaOverride}\` para este servidor.`
);
} else {
await message.reply('⚠️ No hay un área de tipo **FIGHT** configurada. Crea una con `!area-crear` o especifica `area:<key>`.');
await message.reply(
"⚠️ No hay un área de tipo **FIGHT** configurada. Crea una con `!area-crear` o especifica `area:<key>`."
);
}
return;
}
const { area, source } = areaInfo;
const globalNotice = source === 'global'
? ` Usando configuración global para \`${area.key}\`. Puedes crear \`gameArea\` tipo **FIGHT** para personalizarla en este servidor.`
: null;
const globalNotice =
source === "global"
? ` Usando configuración global para \`${area.key}\`. Puedes crear \`gameArea\` tipo **FIGHT** para personalizarla en este servidor.`
: null;
const level = levelArg ?? await getDefaultLevel(userId, guildId, area.id);
const toolKey = providedTool ?? await findBestToolKey(userId, guildId, 'sword');
const level = levelArg ?? (await getDefaultLevel(userId, guildId, area.id));
const toolKey =
providedTool ?? (await findBestToolKey(userId, guildId, "sword"));
try {
const result = await runMinigame(userId, guildId, area.key, level, { toolKey: toolKey ?? undefined });
const result = await runMinigame(userId, guildId, area.key, level, {
toolKey: toolKey ?? undefined,
});
const rewardKeys = result.rewards
.filter((r): r is { type: 'item'; itemKey: string; qty?: number } => r.type === 'item' && Boolean(r.itemKey))
.filter(
(r): r is { type: "item"; itemKey: string; qty?: number } =>
r.type === "item" && Boolean(r.itemKey)
)
.map((r) => r.itemKey!);
if (result.tool?.key) rewardKeys.push(result.tool.key);
const rewardItems = await fetchItemBasics(guildId, rewardKeys);
// Actualizar stats y misiones
await updateStats(userId, guildId, { fightsCompleted: 1 });
await updateQuestProgress(userId, guildId, 'fight_count', 1);
await updateQuestProgress(userId, guildId, "fight_count", 1);
// Contar mobs derrotados
const mobsCount = result.mobs.length;
if (mobsCount > 0) {
await updateStats(userId, guildId, { mobsDefeated: mobsCount });
await updateQuestProgress(userId, guildId, 'mob_defeat_count', mobsCount);
await updateQuestProgress(
userId,
guildId,
"mob_defeat_count",
mobsCount
);
}
const newAchievements = await checkAchievements(userId, guildId, 'fight_count');
const rewardLines = result.rewards.length
? result.rewards.map((r) => {
if (r.type === 'coins') return `• 🪙 +${r.amount}`;
const info = rewardItems.get(r.itemKey!);
const label = formatItemLabel(info ?? { key: r.itemKey!, name: null, icon: null });
return `${label} x${r.qty ?? 1}`;
}).join('\n')
: '• —';
const mobsLines = result.mobs.length
? result.mobs.map(m => `${m}`).join('\n')
: '• —';
const toolInfo = result.tool?.key
? `${formatItemLabel(rewardItems.get(result.tool.key) ?? { key: result.tool.key, name: null, icon: null }, { fallbackIcon: '🗡️' })}${result.tool.broken ? ' (rota)' : ` (-${result.tool.durabilityDelta ?? 0} dur.)`}`
: '—';
const blocks = [textBlock('# ⚔️ Arena')];
const newAchievements = await checkAchievements(
userId,
guildId,
"fight_count"
);
const rewardLines = result.rewards.length
? result.rewards
.map((r) => {
if (r.type === "coins") return `• 🪙 +${r.amount}`;
const info = rewardItems.get(r.itemKey!);
const label = formatItemLabel(
info ?? { key: r.itemKey!, name: null, icon: null }
);
return `${label} x${r.qty ?? 1}`;
})
.join("\n")
: "• —";
const mobsLines = result.mobs.length
? result.mobs.map((m) => `${m}`).join("\n")
: "• —";
const toolInfo = result.tool?.key
? `${formatItemLabel(
rewardItems.get(result.tool.key) ?? {
key: result.tool.key,
name: null,
icon: null,
},
{ fallbackIcon: "🗡️" }
)}${
result.tool.broken
? " (rota)"
: ` (-${result.tool.durabilityDelta ?? 0} dur.)`
}`
: "—";
const blocks = [textBlock("# ⚔️ Arena")];
if (globalNotice) {
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
@@ -87,16 +137,32 @@ export const command: CommandMessage = {
}
blocks.push(dividerBlock());
const areaScope = source === 'global' ? '🌐 Configuración global' : '📍 Configuración local';
blocks.push(textBlock(`**Área:** \`${area.key}\`${areaScope}\n**Nivel:** ${level}\n**Arma:** ${toolInfo}`));
const areaScope =
source === "global"
? "🌐 Configuración global"
: "📍 Configuración local";
blocks.push(
textBlock(
`**Área:** \`${area.key}\`${areaScope}\n**Nivel:** ${level}\n**Arma:** ${toolInfo}`
)
);
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
blocks.push(textBlock(`**Recompensas**\n${rewardLines}`));
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
blocks.push(textBlock(`**Enemigos**\n${mobsLines}`));
// Añadir metadata del área
const metaBlocks = buildAreaMetadataBlocks(area);
if (metaBlocks.length) {
blocks.push(dividerBlock());
blocks.push(...metaBlocks);
}
if (newAchievements.length > 0) {
blocks.push(dividerBlock({ divider: false, spacing: 2 }));
const achLines = newAchievements.map(ach => `✨ **${ach.name}** — ${ach.description}`).join('\n');
const achLines = newAchievements
.map((ach) => `✨ **${ach.name}** — ${ach.description}`)
.join("\n");
blocks.push(textBlock(`🏆 ¡Logro desbloqueado!\n${achLines}`));
}
@@ -105,6 +171,5 @@ export const command: CommandMessage = {
} catch (e: any) {
await message.reply(`❌ No se pudo pelear: ${e?.message ?? e}`);
}
}
},
};

View File

@@ -1,21 +1,36 @@
import type { CommandMessage } from '../../../core/types/commands';
import type Amayo from '../../../core/client';
import { runMinigame } from '../../../game/minigames/service';
import { getDefaultLevel, findBestToolKey, parseGameArgs, resolveGuildAreaWithFallback, resolveAreaByType, sendDisplayReply, fetchItemBasics, formatItemLabel } from './_helpers';
import { updateStats } from '../../../game/stats/service';
import { updateQuestProgress } from '../../../game/quests/service';
import { checkAchievements } from '../../../game/achievements/service';
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
import type { CommandMessage } from "../../../core/types/commands";
import type Amayo from "../../../core/client";
import { runMinigame } from "../../../game/minigames/service";
import {
getDefaultLevel,
findBestToolKey,
parseGameArgs,
resolveGuildAreaWithFallback,
resolveAreaByType,
sendDisplayReply,
fetchItemBasics,
formatItemLabel,
} from "./_helpers";
import { updateStats } from "../../../game/stats/service";
import { updateQuestProgress } from "../../../game/quests/service";
import { checkAchievements } from "../../../game/achievements/service";
import {
buildDisplay,
dividerBlock,
textBlock,
} from "../../../core/lib/componentsV2";
import { buildAreaMetadataBlocks } from "./_helpers";
const FISHING_ACCENT = 0x1ABC9C;
const FISHING_ACCENT = 0x1abc9c;
export const command: CommandMessage = {
name: 'pescar',
type: 'message',
aliases: ['fish'],
name: "pescar",
type: "message",
aliases: ["fish"],
cooldown: 5,
description: 'Pesca en la laguna (usa caña si está disponible) y obtén recompensas.',
usage: 'pescar [nivel] [toolKey] [area:clave] (ej: pescar 1 tool.rod.basic)',
description:
"Pesca en la laguna (usa caña si está disponible) y obtén recompensas.",
usage: "pescar [nivel] [toolKey] [area:clave] (ej: pescar 1 tool.rod.basic)",
run: async (message, args, _client: Amayo) => {
const userId = message.author.id;
const guildId = message.guild!.id;
@@ -23,55 +38,85 @@ export const command: CommandMessage = {
const areaInfo = areaOverride
? await resolveGuildAreaWithFallback(guildId, areaOverride)
: await resolveAreaByType(guildId, 'LAGOON');
: await resolveAreaByType(guildId, "LAGOON");
if (!areaInfo.area) {
if (areaOverride) {
await message.reply(`⚠️ No existe un área con key \`${areaOverride}\` para este servidor.`);
await message.reply(
`⚠️ No existe un área con key \`${areaOverride}\` para este servidor.`
);
} else {
await message.reply('⚠️ No hay un área de tipo **LAGOON** configurada. Crea una con `!area-crear` o especifica `area:<key>`.');
await message.reply(
"⚠️ No hay un área de tipo **LAGOON** configurada. Crea una con `!area-crear` o especifica `area:<key>`."
);
}
return;
}
const { area, source } = areaInfo;
const globalNotice = source === 'global'
? ` Usando configuración global para \`${area.key}\`. Puedes crear \`gameArea\` tipo **LAGOON** para personalizarla en este servidor.`
: null;
const globalNotice =
source === "global"
? ` Usando configuración global para \`${area.key}\`. Puedes crear \`gameArea\` tipo **LAGOON** para personalizarla en este servidor.`
: null;
const level = levelArg ?? await getDefaultLevel(userId, guildId, area.id);
const toolKey = providedTool ?? await findBestToolKey(userId, guildId, 'rod');
const level = levelArg ?? (await getDefaultLevel(userId, guildId, area.id));
const toolKey =
providedTool ?? (await findBestToolKey(userId, guildId, "rod"));
try {
const result = await runMinigame(userId, guildId, area.key, level, { toolKey: toolKey ?? undefined });
const result = await runMinigame(userId, guildId, area.key, level, {
toolKey: toolKey ?? undefined,
});
const rewardKeys = result.rewards
.filter((r): r is { type: 'item'; itemKey: string; qty?: number } => r.type === 'item' && Boolean(r.itemKey))
.filter(
(r): r is { type: "item"; itemKey: string; qty?: number } =>
r.type === "item" && Boolean(r.itemKey)
)
.map((r) => r.itemKey!);
if (result.tool?.key) rewardKeys.push(result.tool.key);
const rewardItems = await fetchItemBasics(guildId, rewardKeys);
// Actualizar stats y misiones
await updateStats(userId, guildId, { fishingCompleted: 1 });
await updateQuestProgress(userId, guildId, 'fish_count', 1);
const newAchievements = await checkAchievements(userId, guildId, 'fish_count');
await updateQuestProgress(userId, guildId, "fish_count", 1);
const newAchievements = await checkAchievements(
userId,
guildId,
"fish_count"
);
const rewardLines = result.rewards.length
? result.rewards.map((r) => {
if (r.type === 'coins') return `• 🪙 +${r.amount}`;
const info = rewardItems.get(r.itemKey!);
const label = formatItemLabel(info ?? { key: r.itemKey!, name: null, icon: null });
return `${label} x${r.qty ?? 1}`;
}).join('\n')
: '• —';
? result.rewards
.map((r) => {
if (r.type === "coins") return `• 🪙 +${r.amount}`;
const info = rewardItems.get(r.itemKey!);
const label = formatItemLabel(
info ?? { key: r.itemKey!, name: null, icon: null }
);
return `${label} x${r.qty ?? 1}`;
})
.join("\n")
: "• —";
const mobsLines = result.mobs.length
? result.mobs.map(m => `${m}`).join('\n')
: '• —';
? result.mobs.map((m) => `${m}`).join("\n")
: "• —";
const toolInfo = result.tool?.key
? `${formatItemLabel(rewardItems.get(result.tool.key) ?? { key: result.tool.key, name: null, icon: null }, { fallbackIcon: '🎣' })}${result.tool.broken ? ' (rota)' : ` (-${result.tool.durabilityDelta ?? 0} dur.)`}`
: '—';
? `${formatItemLabel(
rewardItems.get(result.tool.key) ?? {
key: result.tool.key,
name: null,
icon: null,
},
{ fallbackIcon: "🎣" }
)}${
result.tool.broken
? " (rota)"
: ` (-${result.tool.durabilityDelta ?? 0} dur.)`
}`
: "—";
const blocks = [textBlock('# 🎣 Pesca')];
const blocks = [textBlock("# 🎣 Pesca")];
if (globalNotice) {
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
@@ -79,16 +124,32 @@ export const command: CommandMessage = {
}
blocks.push(dividerBlock());
const areaScope = source === 'global' ? '🌐 Configuración global' : '📍 Configuración local';
blocks.push(textBlock(`**Área:** \`${area.key}\`${areaScope}\n**Nivel:** ${level}\n**Herramienta:** ${toolInfo}`));
const areaScope =
source === "global"
? "🌐 Configuración global"
: "📍 Configuración local";
blocks.push(
textBlock(
`**Área:** \`${area.key}\`${areaScope}\n**Nivel:** ${level}\n**Herramienta:** ${toolInfo}`
)
);
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
blocks.push(textBlock(`**Recompensas**\n${rewardLines}`));
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
blocks.push(textBlock(`**Mobs**\n${mobsLines}`));
// Añadir metadata del área
const metaBlocks = buildAreaMetadataBlocks(area);
if (metaBlocks.length) {
blocks.push(dividerBlock());
blocks.push(...metaBlocks);
}
if (newAchievements.length > 0) {
blocks.push(dividerBlock({ divider: false, spacing: 2 }));
const achLines = newAchievements.map(ach => `✨ **${ach.name}** — ${ach.description}`).join('\n');
const achLines = newAchievements
.map((ach) => `✨ **${ach.name}** — ${ach.description}`)
.join("\n");
blocks.push(textBlock(`🏆 ¡Logro desbloqueado!\n${achLines}`));
}
@@ -97,6 +158,5 @@ export const command: CommandMessage = {
} catch (e: any) {
await message.reply(`❌ No se pudo pescar: ${e?.message ?? e}`);
}
}
},
};

View File

@@ -1,71 +1,119 @@
import type { CommandMessage } from '../../../core/types/commands';
import type Amayo from '../../../core/client';
import { runMinigame } from '../../../game/minigames/service';
import { resolveArea, getDefaultLevel, findBestToolKey, sendDisplayReply, fetchItemBasics, formatItemLabel } from './_helpers';
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
import type { CommandMessage } from "../../../core/types/commands";
import type Amayo from "../../../core/client";
import { runMinigame } from "../../../game/minigames/service";
import {
resolveArea,
getDefaultLevel,
findBestToolKey,
sendDisplayReply,
fetchItemBasics,
formatItemLabel,
} from "./_helpers";
import {
buildDisplay,
dividerBlock,
textBlock,
} from "../../../core/lib/componentsV2";
import { buildAreaMetadataBlocks } from "./_helpers";
const FARM_ACCENT = 0x2ECC71;
const FARM_ACCENT = 0x2ecc71;
export const command: CommandMessage = {
name: 'plantar',
type: 'message',
aliases: ['farm'],
name: "plantar",
type: "message",
aliases: ["farm"],
cooldown: 5,
description: 'Planta/cosecha en el campo (usa azada si está disponible).',
usage: 'plantar [nivel] [toolKey] (ej: plantar 1 tool.hoe.basic)',
description: "Planta/cosecha en el campo (usa azada si está disponible).",
usage: "plantar [nivel] [toolKey] (ej: plantar 1 tool.hoe.basic)",
run: async (message, args, _client: Amayo) => {
const userId = message.author.id;
const guildId = message.guild!.id;
const areaKey = 'farm.field';
const areaKey = "farm.field";
const area = await resolveArea(guildId, areaKey);
if (!area) { await message.reply('⚠️ Área de cultivo no configurada. Crea `gameArea` con key `farm.field`.'); return; }
if (!area) {
await message.reply(
"⚠️ Área de cultivo no configurada. Crea `gameArea` con key `farm.field`."
);
return;
}
const levelArg = args[0] && /^\d+$/.test(args[0]) ? parseInt(args[0], 10) : null;
const levelArg =
args[0] && /^\d+$/.test(args[0]) ? parseInt(args[0], 10) : null;
const providedTool = args.find((a) => a && !/^\d+$/.test(a));
const level = levelArg ?? await getDefaultLevel(userId, guildId, area.id);
const toolKey = providedTool ?? await findBestToolKey(userId, guildId, 'hoe');
const level = levelArg ?? (await getDefaultLevel(userId, guildId, area.id));
const toolKey =
providedTool ?? (await findBestToolKey(userId, guildId, "hoe"));
try {
const result = await runMinigame(userId, guildId, areaKey, level, { toolKey: toolKey ?? undefined });
const result = await runMinigame(userId, guildId, areaKey, level, {
toolKey: toolKey ?? undefined,
});
const rewardKeys = result.rewards
.filter((r): r is { type: 'item'; itemKey: string; qty?: number } => r.type === 'item' && Boolean(r.itemKey))
.filter(
(r): r is { type: "item"; itemKey: string; qty?: number } =>
r.type === "item" && Boolean(r.itemKey)
)
.map((r) => r.itemKey!);
if (result.tool?.key) rewardKeys.push(result.tool.key);
const rewardItems = await fetchItemBasics(guildId, rewardKeys);
const rewardLines = result.rewards.length
? result.rewards.map((r) => {
if (r.type === 'coins') return `• 🪙 +${r.amount}`;
const info = rewardItems.get(r.itemKey!);
const label = formatItemLabel(info ?? { key: r.itemKey!, name: null, icon: null });
return `${label} x${r.qty ?? 1}`;
}).join('\n')
: '• —';
? result.rewards
.map((r) => {
if (r.type === "coins") return `• 🪙 +${r.amount}`;
const info = rewardItems.get(r.itemKey!);
const label = formatItemLabel(
info ?? { key: r.itemKey!, name: null, icon: null }
);
return `${label} x${r.qty ?? 1}`;
})
.join("\n")
: "• —";
const mobsLines = result.mobs.length
? result.mobs.map(m => `${m}`).join('\n')
: '• —';
? result.mobs.map((m) => `${m}`).join("\n")
: "• —";
const toolInfo = result.tool?.key
? `${formatItemLabel(rewardItems.get(result.tool.key) ?? { key: result.tool.key, name: null, icon: null }, { fallbackIcon: '🪓' })}${result.tool.broken ? ' (rota)' : ` (-${result.tool.durabilityDelta ?? 0} dur.)`}`
: '—';
? `${formatItemLabel(
rewardItems.get(result.tool.key) ?? {
key: result.tool.key,
name: null,
icon: null,
},
{ fallbackIcon: "🪓" }
)}${
result.tool.broken
? " (rota)"
: ` (-${result.tool.durabilityDelta ?? 0} dur.)`
}`
: "—";
const blocks = [
textBlock('# 🌱 Campo'),
textBlock("# 🌱 Campo"),
dividerBlock(),
textBlock(`**Área:** \`${area.key}\`\n**Nivel:** ${level}\n**Herramienta:** ${toolInfo}`),
textBlock(
`**Área:** \`${area.key}\`\n**Nivel:** ${level}\n**Herramienta:** ${toolInfo}`
),
dividerBlock({ divider: false, spacing: 1 }),
textBlock(`**Recompensas**\n${rewardLines}`),
dividerBlock({ divider: false, spacing: 1 }),
textBlock(`**Eventos**\n${mobsLines}`),
];
// Añadir metadata del área
const metaBlocks = buildAreaMetadataBlocks(area);
if (metaBlocks.length) {
blocks.push(dividerBlock());
// @ts-ignore: extended block type allowed at runtime
blocks.push(...metaBlocks);
}
const display = buildDisplay(FARM_ACCENT, blocks);
await sendDisplayReply(message, display);
} catch (e: any) {
await message.reply(`❌ No se pudo plantar: ${e?.message ?? e}`);
}
}
},
};