feat(economy): add minigame commands for mining, fighting, fishing, and farming
This commit is contained in:
32
src/commands/messages/game/_helpers.ts
Normal file
32
src/commands/messages/game/_helpers.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { prisma } from '../../../core/database/prisma';
|
||||||
|
import type { ItemProps } from '../../../game/economy/types';
|
||||||
|
|
||||||
|
export function parseItemProps(json: unknown): ItemProps {
|
||||||
|
if (!json || typeof json !== 'object') return {};
|
||||||
|
return json as ItemProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveArea(guildId: string, areaKey: string) {
|
||||||
|
const area = await prisma.gameArea.findFirst({ where: { key: areaKey, OR: [{ guildId }, { guildId: null }] }, orderBy: [{ guildId: 'desc' }] });
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDefaultLevel(userId: string, guildId: string, areaId: string): Promise<number> {
|
||||||
|
const prog = await prisma.playerProgress.findUnique({ where: { userId_guildId_areaId: { userId, guildId, areaId } } });
|
||||||
|
return Math.max(1, prog?.highestLevel ?? 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findBestToolKey(userId: string, guildId: string, toolType: string): Promise<string | null> {
|
||||||
|
const inv = await prisma.inventoryEntry.findMany({ where: { userId, guildId, quantity: { gt: 0 } }, include: { item: true } });
|
||||||
|
let best: { key: string; tier: number } | null = null;
|
||||||
|
for (const e of inv) {
|
||||||
|
const it = e.item;
|
||||||
|
const props = parseItemProps(it.props);
|
||||||
|
const t = props.tool;
|
||||||
|
if (!t || t.type !== toolType) continue;
|
||||||
|
const tier = Math.max(0, t.tier ?? 0);
|
||||||
|
if (!best || tier > best.tier) best = { key: it.key, tier };
|
||||||
|
}
|
||||||
|
return best?.key ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
41
src/commands/messages/game/mina.ts
Normal file
41
src/commands/messages/game/mina.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type { CommandMessage } from '../../../core/types/commands';
|
||||||
|
import type Amayo from '../../../core/client';
|
||||||
|
import { runMinigame } from '../../../game/minigames/service';
|
||||||
|
import { resolveArea, getDefaultLevel, findBestToolKey } from './_helpers';
|
||||||
|
|
||||||
|
export const command: CommandMessage = {
|
||||||
|
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] (ej: mina 2 tool.pickaxe.basic)',
|
||||||
|
run: async (message, args, _client: Amayo) => {
|
||||||
|
const userId = message.author.id;
|
||||||
|
const guildId = message.guild!.id;
|
||||||
|
const areaKey = 'mine.cavern';
|
||||||
|
|
||||||
|
const area = await resolveArea(guildId, areaKey);
|
||||||
|
if (!area) { await message.reply('⚠️ Área de mina no configurada. Pide a un admin crear `gameArea` con key `mine.cavern`.'); return; }
|
||||||
|
|
||||||
|
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, 'pickaxe');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await runMinigame(userId, guildId, areaKey, level, { toolKey: toolKey ?? undefined });
|
||||||
|
const rewards = result.rewards.map(r => r.type === 'coins' ? `🪙 +${r.amount}` : `📦 ${r.itemKey} x${r.qty}`).join(' · ') || '—';
|
||||||
|
const mobs = result.mobs.length ? result.mobs.join(', ') : '—';
|
||||||
|
const toolInfo = result.tool?.key ? `🔧 ${result.tool.key}${result.tool.broken ? ' (rota)' : ` (-${result.tool.durabilityDelta} dur.)`}` : '—';
|
||||||
|
await message.reply(`⛏️ Mina (nivel ${level})
|
||||||
|
Recompensas: ${rewards}
|
||||||
|
Mobs: ${mobs}
|
||||||
|
Herramienta: ${toolInfo}`);
|
||||||
|
} catch (e: any) {
|
||||||
|
await message.reply(`❌ No se pudo minar: ${e?.message ?? e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
41
src/commands/messages/game/pelear.ts
Normal file
41
src/commands/messages/game/pelear.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type { CommandMessage } from '../../../core/types/commands';
|
||||||
|
import type Amayo from '../../../core/client';
|
||||||
|
import { runMinigame } from '../../../game/minigames/service';
|
||||||
|
import { resolveArea, getDefaultLevel, findBestToolKey } from './_helpers';
|
||||||
|
|
||||||
|
export const command: CommandMessage = {
|
||||||
|
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] (ej: pelear 1 weapon.sword.iron)',
|
||||||
|
run: async (message, args, _client: Amayo) => {
|
||||||
|
const userId = message.author.id;
|
||||||
|
const guildId = message.guild!.id;
|
||||||
|
const areaKey = 'fight.arena';
|
||||||
|
|
||||||
|
const area = await resolveArea(guildId, areaKey);
|
||||||
|
if (!area) { await message.reply('⚠️ Área de arena no configurada. Crea `gameArea` con key `fight.arena`.'); return; }
|
||||||
|
|
||||||
|
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, 'sword');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await runMinigame(userId, guildId, areaKey, level, { toolKey: toolKey ?? undefined });
|
||||||
|
const rewards = result.rewards.map(r => r.type === 'coins' ? `🪙 +${r.amount}` : `🎁 ${r.itemKey} x${r.qty}`).join(' · ') || '—';
|
||||||
|
const mobs = result.mobs.length ? result.mobs.join(', ') : '—';
|
||||||
|
const toolInfo = result.tool?.key ? `🗡️ ${result.tool.key}${result.tool.broken ? ' (rota)' : ` (-${result.tool.durabilityDelta} dur.)`}` : '—';
|
||||||
|
await message.reply(`⚔️ Arena (nivel ${level})
|
||||||
|
Recompensas: ${rewards}
|
||||||
|
Enemigos: ${mobs}
|
||||||
|
Arma: ${toolInfo}`);
|
||||||
|
} catch (e: any) {
|
||||||
|
await message.reply(`❌ No se pudo pelear: ${e?.message ?? e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
41
src/commands/messages/game/pescar.ts
Normal file
41
src/commands/messages/game/pescar.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type { CommandMessage } from '../../../core/types/commands';
|
||||||
|
import type Amayo from '../../../core/client';
|
||||||
|
import { runMinigame } from '../../../game/minigames/service';
|
||||||
|
import { resolveArea, getDefaultLevel, findBestToolKey } from './_helpers';
|
||||||
|
|
||||||
|
export const command: CommandMessage = {
|
||||||
|
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] (ej: pescar 1 tool.rod.basic)',
|
||||||
|
run: async (message, args, _client: Amayo) => {
|
||||||
|
const userId = message.author.id;
|
||||||
|
const guildId = message.guild!.id;
|
||||||
|
const areaKey = 'lagoon.shore';
|
||||||
|
|
||||||
|
const area = await resolveArea(guildId, areaKey);
|
||||||
|
if (!area) { await message.reply('⚠️ Área de laguna no configurada. Crea `gameArea` con key `lagoon.shore`.'); return; }
|
||||||
|
|
||||||
|
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, 'rod');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await runMinigame(userId, guildId, areaKey, level, { toolKey: toolKey ?? undefined });
|
||||||
|
const rewards = result.rewards.map(r => r.type === 'coins' ? `🪙 +${r.amount}` : `🐟 ${r.itemKey} x${r.qty}`).join(' · ') || '—';
|
||||||
|
const mobs = result.mobs.length ? result.mobs.join(', ') : '—';
|
||||||
|
const toolInfo = result.tool?.key ? `🎣 ${result.tool.key}${result.tool.broken ? ' (rota)' : ` (-${result.tool.durabilityDelta} dur.)`}` : '—';
|
||||||
|
await message.reply(`🎣 Pesca (nivel ${level})
|
||||||
|
Recompensas: ${rewards}
|
||||||
|
Mobs: ${mobs}
|
||||||
|
Herramienta: ${toolInfo}`);
|
||||||
|
} catch (e: any) {
|
||||||
|
await message.reply(`❌ No se pudo pescar: ${e?.message ?? e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
41
src/commands/messages/game/plantar.ts
Normal file
41
src/commands/messages/game/plantar.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type { CommandMessage } from '../../../core/types/commands';
|
||||||
|
import type Amayo from '../../../core/client';
|
||||||
|
import { runMinigame } from '../../../game/minigames/service';
|
||||||
|
import { resolveArea, getDefaultLevel, findBestToolKey } from './_helpers';
|
||||||
|
|
||||||
|
export const command: CommandMessage = {
|
||||||
|
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)',
|
||||||
|
run: async (message, args, _client: Amayo) => {
|
||||||
|
const userId = message.author.id;
|
||||||
|
const guildId = message.guild!.id;
|
||||||
|
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; }
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await runMinigame(userId, guildId, areaKey, level, { toolKey: toolKey ?? undefined });
|
||||||
|
const rewards = result.rewards.map(r => r.type === 'coins' ? `🪙 +${r.amount}` : `🌾 ${r.itemKey} x${r.qty}`).join(' · ') || '—';
|
||||||
|
const mobs = result.mobs.length ? result.mobs.join(', ') : '—';
|
||||||
|
const toolInfo = result.tool?.key ? `🪓 ${result.tool.key}${result.tool.broken ? ' (rota)' : ` (-${result.tool.durabilityDelta} dur.)`}` : '—';
|
||||||
|
await message.reply(`🌱 Campo (nivel ${level})
|
||||||
|
Recompensas: ${rewards}
|
||||||
|
Eventos: ${mobs}
|
||||||
|
Herramienta: ${toolInfo}`);
|
||||||
|
} catch (e: any) {
|
||||||
|
await message.reply(`❌ No se pudo plantar: ${e?.message ?? e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Reference in New Issue
Block a user