diff --git a/src/commands/messages/game/itemCreate.ts b/src/commands/messages/game/itemCreate.ts new file mode 100644 index 0000000..bb9bcc8 --- /dev/null +++ b/src/commands/messages/game/itemCreate.ts @@ -0,0 +1,227 @@ +import { Message, MessageFlags, MessageComponentInteraction, ButtonInteraction } from 'discord.js'; +import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10'; +import type { CommandMessage } from '../../../core/types/commands'; +import { hasManageGuildOrStaff } from '../../../core/lib/permissions'; +import logger from '../../../core/lib/logger'; +import type Amayo from '../../../core/client'; + +interface ItemEditorState { + key: string; + name?: string; + description?: string; + category?: string; + icon?: string; + stackable?: boolean; + maxPerInventory?: number | null; + tags: string[]; + props?: any; +} + +export const command: CommandMessage = { + name: 'item-crear', + type: 'message', + aliases: ['crear-item','itemcreate'], + cooldown: 10, + description: 'Crea un EconomyItem para este servidor con un pequeño editor interactivo.', + category: 'Economía', + usage: 'item-crear ', + run: async (message: Message, args: string[], client: Amayo) => { + const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma); + if (!allowed) { + await message.reply('❌ No tienes permisos de ManageGuild ni rol de staff.'); + return; + } + + const key = args[0]?.trim(); + if (!key) { + await message.reply('Uso: `!item-crear `'); + return; + } + + const guildId = message.guild!.id; + + const exists = await client.prisma.economyItem.findFirst({ where: { key, guildId } }); + if (exists) { + await message.reply('❌ Ya existe un item con esa key en este servidor.'); + return; + } + + const state: ItemEditorState = { + key, + tags: [], + stackable: true, + maxPerInventory: null, + props: {}, + }; + + const editorMsg = await message.channel.send({ + content: `🛠️ Editor de Item: \`${key}\`\nUsa los botones para configurar los campos y luego guarda.`, + flags: MessageFlags.IsComponentsV2, + components: [ + { type: 1, components: [ + { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'it_base' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Tags', custom_id: 'it_tags' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Props (JSON)', custom_id: 'it_props' }, + { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'it_save' }, + { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'it_cancel' }, + ]}, + ], + }); + + const collector = editorMsg.createMessageComponentCollector({ time: 30 * 60_000, filter: (i) => i.user.id === message.author.id }); + + collector.on('collect', async (i: MessageComponentInteraction) => { + try { + if (!i.isButton()) return; + if (i.customId === 'it_cancel') { + await i.deferUpdate(); + await editorMsg.edit({ content: '❌ Editor cancelado.', components: [] }); + collector.stop('cancel'); + return; + } + if (i.customId === 'it_base') { + await showBaseModal(i as ButtonInteraction, state); + return; + } + if (i.customId === 'it_tags') { + await showTagsModal(i as ButtonInteraction, state); + return; + } + if (i.customId === 'it_props') { + await showPropsModal(i as ButtonInteraction, state); + return; + } + if (i.customId === 'it_save') { + // Validar + if (!state.name) { + await i.reply({ content: '❌ Falta el nombre del item (configura en Base).', flags: MessageFlags.Ephemeral }); + return; + } + // Guardar + await client.prisma.economyItem.create({ + data: { + guildId, + key: state.key, + name: state.name!, + description: state.description, + category: state.category, + icon: state.icon, + stackable: state.stackable ?? true, + maxPerInventory: state.maxPerInventory ?? undefined, + tags: state.tags, + props: state.props ?? {}, + }, + }); + await i.reply({ content: '✅ Item guardado!', flags: MessageFlags.Ephemeral }); + await editorMsg.edit({ content: `✅ Item \`${state.key}\` creado.`, components: [] }); + collector.stop('saved'); + return; + } + } catch (err) { + logger.error({ err }, 'item-crear interaction error'); + if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral }); + } + }); + + collector.on('end', async (_c, r) => { + if (r === 'time') { + try { await editorMsg.edit({ content: '⏰ Editor expirado.', components: [] }); } catch {} + } + }); + }, +}; + +async function showBaseModal(i: ButtonInteraction, state: ItemEditorState) { + const modal = { + title: 'Configuración base del Item', + customId: 'it_base_modal', + components: [ + { type: ComponentType.Label, label: 'Nombre', component: { type: ComponentType.TextInput, customId: 'name', style: TextInputStyle.Short, required: true, value: state.name ?? '' } }, + { type: ComponentType.Label, label: 'Descripción', component: { type: ComponentType.TextInput, customId: 'desc', style: TextInputStyle.Paragraph, required: false, value: state.description ?? '' } }, + { type: ComponentType.Label, label: 'Categoría', component: { type: ComponentType.TextInput, customId: 'cat', style: TextInputStyle.Short, required: false, value: state.category ?? '' } }, + { type: ComponentType.Label, label: 'Icon URL', component: { type: ComponentType.TextInput, customId: 'icon', style: TextInputStyle.Short, required: false, value: state.icon ?? '' } }, + { type: ComponentType.Label, label: 'Stackable,true/false; MaxPerInv (número o vacío)', component: { type: ComponentType.TextInput, customId: 'stack_max', style: TextInputStyle.Short, required: false, placeholder: 'true,10', value: state.stackable !== undefined ? `${state.stackable},${state.maxPerInventory ?? ''}` : '' } }, + ], + } as const; + + await i.showModal(modal); + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + const name = sub.components.getTextInputValue('name').trim(); + const desc = sub.components.getTextInputValue('desc').trim(); + const cat = sub.components.getTextInputValue('cat').trim(); + const icon = sub.components.getTextInputValue('icon').trim(); + const stackMax = sub.components.getTextInputValue('stack_max').trim(); + + state.name = name; + state.description = desc || undefined; + state.category = cat || undefined; + state.icon = icon || undefined; + + if (stackMax) { + const [s, m] = stackMax.split(','); + state.stackable = String(s).toLowerCase() !== 'false'; + const mv = m?.trim(); + state.maxPerInventory = mv ? Math.max(0, parseInt(mv, 10) || 0) : null; + } + + await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); + } catch {} +} + +async function showTagsModal(i: ButtonInteraction, state: ItemEditorState) { + const modal = { + title: 'Tags del Item (separados por coma)', + customId: 'it_tags_modal', + components: [ + { type: ComponentType.Label, label: 'Tags', component: { type: ComponentType.TextInput, customId: 'tags', style: TextInputStyle.Paragraph, required: false, value: state.tags.join(', ') } }, + ], + } as const; + await i.showModal(modal); + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + const tags = sub.components.getTextInputValue('tags'); + state.tags = tags ? tags.split(',').map((t) => t.trim()).filter(Boolean) : []; + await sub.reply({ content: '✅ Tags actualizados.', flags: MessageFlags.Ephemeral }); + } catch {} +} + +async function showPropsModal(i: ButtonInteraction, state: ItemEditorState) { + const template = state.props && Object.keys(state.props).length ? JSON.stringify(state.props) : JSON.stringify({ + tool: undefined, + breakable: undefined, + chest: undefined, + eventCurrency: undefined, + passiveEffects: [], + mutationPolicy: undefined, + craftingOnly: false, + food: undefined, + damage: undefined, + defense: undefined, + maxHpBonus: undefined, + }); + const modal = { + title: 'Props (JSON) del Item', + customId: 'it_props_modal', + components: [ + { type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'props', style: TextInputStyle.Paragraph, required: false, value: template.slice(0,4000) } }, + ], + } as const; + await i.showModal(modal); + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + const raw = sub.components.getTextInputValue('props'); + if (raw) { + try { + const parsed = JSON.parse(raw); + state.props = parsed; + await sub.reply({ content: '✅ Props guardados.', flags: MessageFlags.Ephemeral }); + } catch (e) { + await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); + } + } else { + state.props = {}; + await sub.reply({ content: 'ℹ️ Props limpiados.', flags: MessageFlags.Ephemeral }); + } + } catch {} +} diff --git a/src/commands/messages/game/itemEdit.ts b/src/commands/messages/game/itemEdit.ts new file mode 100644 index 0000000..7724bb5 --- /dev/null +++ b/src/commands/messages/game/itemEdit.ts @@ -0,0 +1,144 @@ +import { Message, MessageFlags, MessageComponentInteraction, ButtonInteraction } from 'discord.js'; +import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10'; +import type { CommandMessage } from '../../../core/types/commands'; +import { hasManageGuildOrStaff } from '../../../core/lib/permissions'; +import logger from '../../../core/lib/logger'; +import type Amayo from '../../../core/client'; + +interface ItemEditorState { + key: string; + name?: string; + description?: string; + category?: string; + icon?: string; + stackable?: boolean; + maxPerInventory?: number | null; + tags: string[]; + props?: any; +} + +export const command: CommandMessage = { + name: 'item-editar', + type: 'message', + aliases: ['editar-item','itemedit'], + cooldown: 10, + description: 'Edita un EconomyItem de este servidor con un editor interactivo.', + category: 'Economía', + usage: 'item-editar ', + run: async (message: Message, args: string[], client: Amayo) => { + const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma); + if (!allowed) { await message.reply('❌ No tienes permisos de ManageGuild ni rol de staff.'); return; } + const key = args[0]?.trim(); + if (!key) { await message.reply('Uso: `!item-editar `'); return; } + const guildId = message.guild!.id; + + const item = await client.prisma.economyItem.findFirst({ where: { key, guildId } }); + if (!item) { await message.reply('❌ No existe un item con esa key en este servidor.'); return; } + + const state: ItemEditorState = { + key, + name: item.name, + description: item.description ?? undefined, + category: item.category ?? undefined, + icon: item.icon ?? undefined, + stackable: item.stackable ?? true, + maxPerInventory: item.maxPerInventory ?? null, + tags: item.tags ?? [], + props: item.props ?? {}, + }; + + const editorMsg = await message.channel.send({ + content: `🛠️ Editor de Item (editar): \`${key}\``, + flags: MessageFlags.IsComponentsV2, + components: [ { type: 1, components: [ + { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'it_base' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Tags', custom_id: 'it_tags' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Props (JSON)', custom_id: 'it_props' }, + { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'it_save' }, + { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'it_cancel' }, + ] } ], + }); + + const collector = editorMsg.createMessageComponentCollector({ time: 30 * 60_000, filter: (i) => i.user.id === message.author.id }); + + collector.on('collect', async (i: MessageComponentInteraction) => { + try { + if (!i.isButton()) return; + if (i.customId === 'it_cancel') { await i.deferUpdate(); await editorMsg.edit({ content: '❌ Editor cancelado.', components: [] }); collector.stop('cancel'); return; } + if (i.customId === 'it_base') { await showBaseModal(i as ButtonInteraction, state); return; } + if (i.customId === 'it_tags') { await showTagsModal(i as ButtonInteraction, state); return; } + if (i.customId === 'it_props') { await showPropsModal(i as ButtonInteraction, state); return; } + if (i.customId === 'it_save') { + if (!state.name) { await i.reply({ content: '❌ Falta el nombre del item.', flags: MessageFlags.Ephemeral }); return; } + await client.prisma.economyItem.update({ + where: { id: item.id }, + data: { + name: state.name!, + description: state.description, + category: state.category, + icon: state.icon, + stackable: state.stackable ?? true, + maxPerInventory: state.maxPerInventory ?? undefined, + tags: state.tags, + props: state.props ?? {}, + }, + }); + await i.reply({ content: '✅ Item actualizado!', flags: MessageFlags.Ephemeral }); + await editorMsg.edit({ content: `✅ Item \`${state.key}\` actualizado.`, components: [] }); + collector.stop('saved'); + return; + } + } catch (err) { + logger.error({ err }, 'item-editar interaction error'); + if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral }); + } + }); + + collector.on('end', async (_c, r) => { if (r === 'time') { try { await editorMsg.edit({ content: '⏰ Editor expirado.', components: [] }); } catch {} } }); + }, +}; + +async function showBaseModal(i: ButtonInteraction, state: ItemEditorState) { + const modal = { + title: 'Configuración base del Item', customId: 'it_base_modal', components: [ + { type: ComponentType.Label, label: 'Nombre', component: { type: ComponentType.TextInput, customId: 'name', style: TextInputStyle.Short, required: true, value: state.name ?? '' } }, + { type: ComponentType.Label, label: 'Descripción', component: { type: ComponentType.TextInput, customId: 'desc', style: TextInputStyle.Paragraph, required: false, value: state.description ?? '' } }, + { type: ComponentType.Label, label: 'Categoría', component: { type: ComponentType.TextInput, customId: 'cat', style: TextInputStyle.Short, required: false, value: state.category ?? '' } }, + { type: ComponentType.Label, label: 'Icon URL', component: { type: ComponentType.TextInput, customId: 'icon', style: TextInputStyle.Short, required: false, value: state.icon ?? '' } }, + { type: ComponentType.Label, label: 'Stackable,true/false; MaxPerInv (número o vacío)', component: { type: ComponentType.TextInput, customId: 'stack_max', style: TextInputStyle.Short, required: false, placeholder: 'true,10', value: state.stackable !== undefined ? `${state.stackable},${state.maxPerInventory ?? ''}` : '' } }, + ], } as const; + await i.showModal(modal); + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + state.name = sub.components.getTextInputValue('name').trim(); + state.description = sub.components.getTextInputValue('desc').trim() || undefined; + state.category = sub.components.getTextInputValue('cat').trim() || undefined; + state.icon = sub.components.getTextInputValue('icon').trim() || undefined; + const stackMax = sub.components.getTextInputValue('stack_max').trim(); + if (stackMax) { const [s,m] = stackMax.split(','); state.stackable = String(s).toLowerCase() !== 'false'; const mv = m?.trim(); state.maxPerInventory = mv ? Math.max(0, parseInt(mv,10)||0) : null; } + await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); + } catch {} +} + +async function showTagsModal(i: ButtonInteraction, state: ItemEditorState) { + const modal = { title: 'Tags del Item (separados por coma)', customId: 'it_tags_modal', components: [ + { type: ComponentType.Label, label: 'Tags', component: { type: ComponentType.TextInput, customId: 'tags', style: TextInputStyle.Paragraph, required: false, value: state.tags.join(', ') } }, + ], } as const; + await i.showModal(modal); + try { const sub = await i.awaitModalSubmit({ time: 300_000 }); const tags = sub.components.getTextInputValue('tags'); state.tags = tags ? tags.split(',').map(t=>t.trim()).filter(Boolean) : []; await sub.reply({ content: '✅ Tags actualizados.', flags: MessageFlags.Ephemeral }); } catch {} +} + +async function showPropsModal(i: ButtonInteraction, state: ItemEditorState) { + const template = state.props && Object.keys(state.props).length ? JSON.stringify(state.props) : JSON.stringify({}); + const modal = { title: 'Props (JSON) del Item', customId: 'it_props_modal', components: [ + { type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'props', style: TextInputStyle.Paragraph, required: false, value: template.slice(0,4000) } }, + ], } as const; + await i.showModal(modal); + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + const raw = sub.components.getTextInputValue('props'); + if (raw) { try { state.props = JSON.parse(raw); await sub.reply({ content: '✅ Props guardados.', flags: MessageFlags.Ephemeral }); } catch { await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); } } + else { state.props = {}; await sub.reply({ content: 'ℹ️ Props limpiados.', flags: MessageFlags.Ephemeral }); } + } catch {} +} + diff --git a/src/commands/messages/game/mobCreate.ts b/src/commands/messages/game/mobCreate.ts new file mode 100644 index 0000000..eb62076 --- /dev/null +++ b/src/commands/messages/game/mobCreate.ts @@ -0,0 +1,96 @@ +import { Message, MessageFlags, MessageComponentInteraction, ButtonInteraction } from 'discord.js'; +import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10'; +import type { CommandMessage } from '../../../core/types/commands'; +import { hasManageGuildOrStaff } from '../../../core/lib/permissions'; +import logger from '../../../core/lib/logger'; +import type Amayo from '../../../core/client'; + +interface MobEditorState { + key: string; + name?: string; + category?: string; + stats?: any; // JSON libre, ej: { attack, hp, defense } + drops?: any; // JSON libre, tabla de recompensas +} + +export const command: CommandMessage = { + name: 'mob-crear', + type: 'message', + aliases: ['crear-mob','mobcreate'], + cooldown: 10, + description: 'Crea un Mob (enemigo) para este servidor con editor interactivo.', + category: 'Minijuegos', + usage: 'mob-crear ', + run: async (message: Message, args: string[], client: Amayo) => { + const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma); + if (!allowed) { await message.reply('❌ No tienes permisos de ManageGuild ni rol de staff.'); return; } + const key = args[0]?.trim(); + if (!key) { await message.reply('Uso: `!mob-crear `'); return; } + + const guildId = message.guild!.id; + const exists = await client.prisma.mob.findFirst({ where: { key, guildId } }); + if (exists) { await message.reply('❌ Ya existe un mob con esa key.'); return; } + + const state: MobEditorState = { key, stats: { attack: 5 }, drops: {} }; + + const editorMsg = await message.channel.send({ + content: `👾 Editor de Mob: \`${key}\``, + flags: MessageFlags.IsComponentsV2, + components: [ { type: 1, components: [ + { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'mb_base' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Stats (JSON)', custom_id: 'mb_stats' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Drops (JSON)', custom_id: 'mb_drops' }, + { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'mb_save' }, + { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'mb_cancel' }, + ] } ], + }); + + const collector = editorMsg.createMessageComponentCollector({ time: 30*60_000, filter: (i)=> i.user.id === message.author.id }); + collector.on('collect', async (i: MessageComponentInteraction) => { + try { + if (!i.isButton()) return; + if (i.customId === 'mb_cancel') { await i.deferUpdate(); await editorMsg.edit({ content: '❌ Editor cancelado.', components: [] }); collector.stop('cancel'); return; } + if (i.customId === 'mb_base') { await showBaseModal(i as ButtonInteraction, state); return; } + if (i.customId === 'mb_stats') { await showJsonModal(i as ButtonInteraction, state, 'stats', 'Stats del Mob (JSON)'); return; } + if (i.customId === 'mb_drops') { await showJsonModal(i as ButtonInteraction, state, 'drops', 'Drops del Mob (JSON)'); return; } + if (i.customId === 'mb_save') { + if (!state.name) { await i.reply({ content: '❌ Falta el nombre del mob.', flags: MessageFlags.Ephemeral }); return; } + await client.prisma.mob.create({ data: { guildId, key: state.key, name: state.name!, category: state.category ?? null, stats: state.stats ?? {}, drops: state.drops ?? {} } }); + await i.reply({ content: '✅ Mob guardado!', flags: MessageFlags.Ephemeral }); + await editorMsg.edit({ content: `✅ Mob \`${state.key}\` creado.`, components: [] }); + collector.stop('saved'); + return; + } + } catch (err) { + logger.error({err}, 'mob-crear'); + if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral }); + } + }); + collector.on('end', async (_c,r)=> { if (r==='time') { try { await editorMsg.edit({ content:'⏰ Editor expirado.', components: [] }); } catch {} } }); + }, +}; + +async function showBaseModal(i: ButtonInteraction, state: MobEditorState) { + const modal = { title: 'Configuración base del Mob', customId: 'mb_base_modal', components: [ + { type: ComponentType.Label, label: 'Nombre', component: { type: ComponentType.TextInput, customId: 'name', style: TextInputStyle.Short, required: true, value: state.name ?? '' } }, + { type: ComponentType.Label, label: 'Categoría', component: { type: ComponentType.TextInput, customId: 'cat', style: TextInputStyle.Short, required: false, value: state.category ?? '' } }, + ] } as const; + await i.showModal(modal); + try { const sub = await i.awaitModalSubmit({ time: 300_000 }); state.name = sub.components.getTextInputValue('name').trim(); state.category = sub.components.getTextInputValue('cat').trim() || undefined; await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); } catch {} +} + +async function showJsonModal(i: ButtonInteraction, state: MobEditorState, field: 'stats'|'drops', label: string) { + const current = JSON.stringify(state[field] ?? (field==='stats'? { attack: 5 }: {})); + const modal = { title: label, customId: `mb_json_${field}`, components: [ + { type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'json', style: TextInputStyle.Paragraph, required: false, value: current.slice(0, 4000) } }, + ] } as const; + await i.showModal(modal); + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + const raw = sub.components.getTextInputValue('json'); + if (raw) { + try { state[field] = JSON.parse(raw); await sub.reply({ content: '✅ Guardado.', flags: MessageFlags.Ephemeral }); } catch { await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); } + } else { state[field] = field==='stats' ? { attack: 5 } : {}; await sub.reply({ content: 'ℹ️ Limpio.', flags: MessageFlags.Ephemeral }); } + } catch {} +} + diff --git a/src/commands/messages/game/mobEdit.ts b/src/commands/messages/game/mobEdit.ts new file mode 100644 index 0000000..94a4163 --- /dev/null +++ b/src/commands/messages/game/mobEdit.ts @@ -0,0 +1,101 @@ +import { Message, MessageFlags, MessageComponentInteraction, ButtonInteraction } from 'discord.js'; +import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10'; +import type { CommandMessage } from '../../../core/types/commands'; +import { hasManageGuildOrStaff } from '../../../core/lib/permissions'; +import logger from '../../../core/lib/logger'; +import type Amayo from '../../../core/client'; + +interface MobEditorState { + key: string; + name?: string; + category?: string; + stats?: any; + drops?: any; +} + +export const command: CommandMessage = { + name: 'mob-editar', + type: 'message', + aliases: ['editar-mob','mobedit'], + cooldown: 10, + description: 'Edita un Mob (enemigo) de este servidor con editor interactivo.', + category: 'Minijuegos', + usage: 'mob-editar ', + run: async (message: Message, args: string[], client: Amayo) => { + const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma); + if (!allowed) { await message.reply('❌ No tienes permisos de ManageGuild ni rol de staff.'); return; } + const key = args[0]?.trim(); + if (!key) { await message.reply('Uso: `!mob-editar `'); return; } + const guildId = message.guild!.id; + + const mob = await client.prisma.mob.findFirst({ where: { key, guildId } }); + if (!mob) { await message.reply('❌ No existe un mob con esa key en este servidor.'); return; } + + const state: MobEditorState = { + key, + name: mob.name, + category: mob.category ?? undefined, + stats: mob.stats ?? {}, + drops: mob.drops ?? {}, + }; + + const editorMsg = await message.channel.send({ + content: `👾 Editor de Mob (editar): \`${key}\``, + flags: MessageFlags.IsComponentsV2, + components: [ { type: 1, components: [ + { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'mb_base' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Stats (JSON)', custom_id: 'mb_stats' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Drops (JSON)', custom_id: 'mb_drops' }, + { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'mb_save' }, + { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'mb_cancel' }, + ] } ], + }); + + const collector = editorMsg.createMessageComponentCollector({ time: 30*60_000, filter: (i)=> i.user.id === message.author.id }); + collector.on('collect', async (i: MessageComponentInteraction) => { + try { + if (!i.isButton()) return; + if (i.customId === 'mb_cancel') { await i.deferUpdate(); await editorMsg.edit({ content: '❌ Editor cancelado.', components: [] }); collector.stop('cancel'); return; } + if (i.customId === 'mb_base') { await showBaseModal(i as ButtonInteraction, state); return; } + if (i.customId === 'mb_stats') { await showJsonModal(i as ButtonInteraction, state, 'stats', 'Stats del Mob (JSON)'); return; } + if (i.customId === 'mb_drops') { await showJsonModal(i as ButtonInteraction, state, 'drops', 'Drops del Mob (JSON)'); return; } + if (i.customId === 'mb_save') { + if (!state.name) { await i.reply({ content: '❌ Falta el nombre del mob.', flags: MessageFlags.Ephemeral }); return; } + await client.prisma.mob.update({ where: { id: mob.id }, data: { name: state.name!, category: state.category ?? null, stats: state.stats ?? {}, drops: state.drops ?? {} } }); + await i.reply({ content: '✅ Mob actualizado!', flags: MessageFlags.Ephemeral }); + await editorMsg.edit({ content: `✅ Mob \`${state.key}\` actualizado.`, components: [] }); + collector.stop('saved'); + return; + } + } catch (err) { + logger.error({err}, 'mob-editar'); + if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral }); + } + }); + collector.on('end', async (_c,r)=> { if (r==='time') { try { await editorMsg.edit({ content:'⏰ Editor expirado.', components: [] }); } catch {} } }); + }, +}; + +async function showBaseModal(i: ButtonInteraction, state: MobEditorState) { + const modal = { title: 'Configuración base del Mob', customId: 'mb_base_modal', components: [ + { type: ComponentType.Label, label: 'Nombre', component: { type: ComponentType.TextInput, customId: 'name', style: TextInputStyle.Short, required: true, value: state.name ?? '' } }, + { type: ComponentType.Label, label: 'Categoría', component: { type: ComponentType.TextInput, customId: 'cat', style: TextInputStyle.Short, required: false, value: state.category ?? '' } }, + ] } as const; + await i.showModal(modal); + try { const sub = await i.awaitModalSubmit({ time: 300_000 }); state.name = sub.components.getTextInputValue('name').trim(); state.category = sub.components.getTextInputValue('cat').trim() || undefined; await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); } catch {} +} + +async function showJsonModal(i: ButtonInteraction, state: MobEditorState, field: 'stats'|'drops', label: string) { + const current = JSON.stringify(state[field] ?? (field==='stats'? { attack: 5 }: {})); + const modal = { title: label, customId: `mb_json_${field}`, components: [ + { type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'json', style: TextInputStyle.Paragraph, required: false, value: current.slice(0, 4000) } }, + ] } as const; + await i.showModal(modal); + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + const raw = sub.components.getTextInputValue('json'); + if (raw) { try { state[field] = JSON.parse(raw); await sub.reply({ content: '✅ Guardado.', flags: MessageFlags.Ephemeral }); } catch { await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); } } + else { state[field] = field==='stats' ? { attack: 5 } : {}; await sub.reply({ content: 'ℹ️ Limpio.', flags: MessageFlags.Ephemeral }); } + } catch {} +} + diff --git a/src/game/minigames/seed.ts b/src/game/minigames/seed.ts index af07023..772a8ba 100644 --- a/src/game/minigames/seed.ts +++ b/src/game/minigames/seed.ts @@ -1,5 +1,5 @@ import { prisma } from '../../core/database/prisma'; -import type { Prisma } from '@prisma/client'; +import { Prisma } from '@prisma/client'; async function upsertEconomyItem(guildId: string | null, key: string, data: Omit[0]['data'], 'key' | 'guildId'>) { if (guildId) {