From 33b768bbb2a593eff6e4ac0fc0c8a306682c9e77 Mon Sep 17 00:00:00 2001 From: shni Date: Thu, 9 Oct 2025 01:36:32 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20agregar=20comando=20de=20registro=20de?= =?UTF-8?q?=20muertes=20y=20penalizaciones;=20implementar=20=C3=ADtem=20de?= =?UTF-8?q?=20purga=20de=20efectos=20y=20mejorar=20la=20documentaci=C3=B3n?= =?UTF-8?q?=20de=20creaci=C3=B3n=20de=20contenido?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/messages/game/deathlog.ts | 49 +++++++++++++ src/game/economy/seedPurgePotion.ts | 35 ++++++++++ src/game/lib/rpgFormat.ts | 4 ++ src/game/minigames/service.ts | 28 +++++++- .../partials/sections/creacion-contenido.ejs | 38 ++++++++-- .../partials/sections/ejemplos-avanzados.ejs | 69 +++++++++++++++++++ 6 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 src/commands/messages/game/deathlog.ts create mode 100644 src/game/economy/seedPurgePotion.ts diff --git a/src/commands/messages/game/deathlog.ts b/src/commands/messages/game/deathlog.ts new file mode 100644 index 0000000..5d45747 --- /dev/null +++ b/src/commands/messages/game/deathlog.ts @@ -0,0 +1,49 @@ +import type { CommandMessage } from "../../../core/types/commands"; +import type Amayo from "../../../core/client"; +import { prisma } from "../../../core/database/prisma"; + +export const command: CommandMessage = { + name: "deathlog", + aliases: ["muertes"], + type: "message", + cooldown: 8, + category: "Economía", + description: "Muestra tus últimas muertes y penalizaciones aplicadas.", + usage: "deathlog [cantidad<=20]", + run: async (message, args, _client: Amayo) => { + const userId = message.author.id; + const guildId = message.guild!.id; + let take = 10; + if (args[0]) { + const n = parseInt(args[0], 10); + if (!isNaN(n) && n > 0) take = Math.min(20, n); + } + + const logs = await prisma.deathLog.findMany({ + where: { userId, guildId }, + orderBy: { createdAt: "desc" }, + take, + }); + if (!logs.length) { + await message.reply("No hay registros de muerte."); + return; + } + + const lines = logs.map((l) => { + const pct = Math.round((l.percentApplied || 0) * 100); + const parts: string[] = []; + parts.push(`💰-${l.goldLost}`); + if (pct) parts.push(`${pct}%`); + if (l.fatigueMagnitude) + parts.push(`Fatiga ${Math.round(l.fatigueMagnitude * 100)}%`); + const area = l.areaKey ? l.areaKey : "?"; + return `${l.createdAt.toISOString().slice(11, 19)} | ${area} L${ + l.level ?? "-" + } | ${parts.join(" | ")}${l.autoDefeatNoWeapon ? " | sin arma" : ""}`; + }); + + await message.reply( + `**DeathLog (últimos ${logs.length})**\n${lines.join("\n")}` + ); + }, +}; diff --git a/src/game/economy/seedPurgePotion.ts b/src/game/economy/seedPurgePotion.ts new file mode 100644 index 0000000..ef7aa73 --- /dev/null +++ b/src/game/economy/seedPurgePotion.ts @@ -0,0 +1,35 @@ +import { prisma } from "../../core/database/prisma"; + +// Seed para crear el ítem de purga de efectos (potion.purga) +// Ejecutar manualmente una vez. +// node -r ts-node/register src/game/economy/seedPurgePotion.ts (según tu setup) + +async function main() { + const key = "potion.purga"; + const existing = await prisma.economyItem.findFirst({ + where: { key, guildId: null }, + }); + if (existing) { + console.log("Ya existe potion.purga (global)"); + return; + } + const item = await prisma.economyItem.create({ + data: { + key, + name: "Poción de Purga", + description: + "Elimina todos tus efectos de estado activos al usar el comando !efectos purgar.", + category: "consumable", + icon: "🧪", + stackable: true, + props: { usable: true, purgeAllEffects: true }, + tags: ["purge", "status", "utility"], + }, + }); + console.log("Creado:", item.id, item.key); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/src/game/lib/rpgFormat.ts b/src/game/lib/rpgFormat.ts index c608c50..98b193b 100644 --- a/src/game/lib/rpgFormat.ts +++ b/src/game/lib/rpgFormat.ts @@ -87,6 +87,7 @@ export function combatSummaryRPG(c: { goldLost?: number; fatigueAppliedMinutes?: number; fatigueMagnitude?: number; + percentApplied?: number; }; }) { const header = `**Combate (${outcomeLabel(c.outcome)})**`; @@ -112,6 +113,9 @@ export function combatSummaryRPG(c: { : 15; parts.push(`Fatiga ${pct}% ${c.deathPenalty.fatigueAppliedMinutes}m`); } + if (typeof c.deathPenalty.percentApplied === "number") { + parts.push(`(${Math.round(c.deathPenalty.percentApplied * 100)}% oro)`); + } if (parts.length) lines.push(`• Penalización: ${parts.join(" | ")}`); } if (c.playerStartHp != null && c.playerEndHp != null) { diff --git a/src/game/minigames/service.ts b/src/game/minigames/service.ts index 6a589d2..17a3387 100644 --- a/src/game/minigames/service.ts +++ b/src/game/minigames/service.ts @@ -451,7 +451,19 @@ export async function runMinigame( }); } } - const fatigueMagnitude = 0.15; + // Fatiga escalada: base 15%, +1% cada 5 de racha previa (cap +10%) + let previousStreak = 0; + try { + const ps = await prisma.playerStats.findUnique({ + where: { userId_guildId: { userId, guildId } }, + }); + previousStreak = ps?.currentWinStreak || 0; + } catch {} + const extraFatigue = Math.min( + 0.1, + Math.floor(previousStreak / 5) * 0.01 + ); + const fatigueMagnitude = 0.15 + extraFatigue; const fatigueMinutes = 5; await applyDeathFatigue( userId, @@ -632,7 +644,19 @@ export async function runMinigame( }); } } - const fatigueMagnitude = 0.15; + // Fatiga escalada + let previousStreak = 0; + try { + const ps = await prisma.playerStats.findUnique({ + where: { userId_guildId: { userId, guildId } }, + }); + previousStreak = ps?.currentWinStreak || 0; + } catch {} + const extraFatigue = Math.min( + 0.1, + Math.floor(previousStreak / 5) * 0.01 + ); + const fatigueMagnitude = 0.15 + extraFatigue; const fatigueMinutes = 5; await applyDeathFatigue( userId, diff --git a/src/server/views/partials/sections/creacion-contenido.ejs b/src/server/views/partials/sections/creacion-contenido.ejs index 4ecec3e..d3ae9d1 100644 --- a/src/server/views/partials/sections/creacion-contenido.ejs +++ b/src/server/views/partials/sections/creacion-contenido.ejs @@ -1,6 +1,6 @@

🎨 Creación de Contenido

-

Guía técnica paso a paso para crear items, áreas/niveles, mobs y ofertas directamente desde Discord. Requiere permiso Manage Guild o rol de staff.

+

Guía técnica paso a paso para crear items, áreas/niveles, mobs, ofertas y ahora componentes RPG avanzados (durabilidad por instancia, efectos de estado, penalizaciones de muerte, rachas, riskFactor de áreas). Requiere permiso Manage Guild o rol de staff.

@@ -26,7 +26,15 @@ Ingredientes: iron_ingot:3, wood_plank:1 4) Guardar → ✅ Item creado Prueba: !craftear iron_sword
-

Usa !item-editar, !item-ver, !items-lista para gestionar.

+

Usa !item-editar, !item-ver, !items-lista. Para herramientas NO apilables la durabilidad se gestiona por instancias dentro de inventory.state.instances.

+
+
Novedades RPG (Items)
+
    +
  • Durabilidad por instancia: si stackable=false y breakable.enabled=true, cada unidad es una instancia con su propia durabilidad.
  • +
  • Mutaciones / Encantamientos: se reflejan sumando bonuses (damageBonus, defenseBonus, maxHpBonus).
  • +
  • Ítem purga efectos: puedes crear tu propia poción local: { "usable": true, "purgeAllEffects": true } y usarla con !efectos purgar.
  • +
+

🧭 Áreas y Niveles (MINE/LAGOON/FIGHT/FARM)

@@ -36,7 +44,8 @@ Prueba: !craftear iron_sword Config (JSON): { "cooldownSeconds": 60, "description": "Caverna rica en hierro", - "icon": "⛏️" + "icon": "⛏️", + "riskFactor": 2 } Guardar → ✅ Área creada @@ -64,6 +73,7 @@ Guardar → ✅ Nivel guardado
  • mobKey o itemKey inexistente → crea primero o corrige la key
  • Pesos mal balanceados → revisa weight (no negativos; no tienen que sumar 100)
  • Herramienta requerida mal configurada → revisa toolType y minTier
  • +
  • riskFactor (0-3) afecta % de oro que pierdes al morir (escala hasta 25% total con nivel).
  • @@ -87,6 +97,14 @@ Drops (JSON): { Guardar → ✅ Mob creado

    Revisa con !mobs-lista y !mob-eliminar <key> si necesitas limpiar datos de prueba.

    +
    +
    Integración combate
    +
      +
    • El daño del jugador usa arma + mutaciones + racha (1% cada 3 victorias, cap 30%).
    • +
    • Defensa reduce daño hasta 60% (5% por punto, cap).
    • +
    • Si daño efectivo = 0 → derrota automática (flag autoDefeatNoWeapon).
    • +
    +

    🛒 Ofertas de Tienda

    @@ -110,6 +128,7 @@ Guardar → ✅ Oferta guardada
  • itemKey no existe → crea el ítem primero
  • Formato de precio inválido → respeta estructura de coins e items
  • Ventana inválida → usa fechas ISO: YYYY-MM-DDTHH:MM:SSZ
  • +
  • Para vender una poción de purga local crea un ítem consumible y ofrece en la tienda.
  • @@ -120,7 +139,18 @@ Guardar → ✅ Oferta guardada +
    +
    Resumen rápido nuevas claves JSON
    + area.config.riskFactor: 0-3 (aumenta % oro perdido) + item.props.breakable.maxDurability / durabilityPerUse + item.props.tool { type, tier } + item.props.purgeAllEffects = true (ítem purga) + status effects: almacenados en DB (PlayerStatusEffect) + death penalty: porcentaje dinámico + fatiga escalada +
    diff --git a/src/server/views/partials/sections/ejemplos-avanzados.ejs b/src/server/views/partials/sections/ejemplos-avanzados.ejs index 43ff744..d29e025 100644 --- a/src/server/views/partials/sections/ejemplos-avanzados.ejs +++ b/src/server/views/partials/sections/ejemplos-avanzados.ejs @@ -51,5 +51,74 @@ Props (JSON): {"tool": {"type": "sword", "tier": 3}, "damage": 25} Props (JSON): {"damage": 45, "breakable": {"enabled": true, "maxDurability": 300}} Receta (modal): steel_sword_base:1, magic_dust:3, dragon_scale:1 + +
    +

    4) Área Avanzada con Riesgo y Mobs

    +
    # Área con factor de riesgo (aumenta penalización oro al morir)
    +!area-crear arena.blood_pit
    +Config (JSON): {"cooldownSeconds": 90, "icon": "⚔️", "riskFactor": 3, "description": "La fosa sangrienta"}
    +!area-nivel arena.blood_pit 1
    +Requisitos (JSON): {"tool": {"required": true, "toolType": "sword", "minTier": 2}}
    +Recompensas (JSON): {"draws": 2, "table": [
    +  {"type": "coins", "amount": 120, "weight": 10},
    +  {"type": "item",  "itemKey": "blood_shard", "qty": 1, "weight": 4}
    +]}
    +Mobs (JSON): {"draws": 2, "table": [
    +  {"mobKey": "goblin", "weight": 8},
    +  {"mobKey": "cave_spider", "weight": 5}
    +]}
    +
    + +
    +

    5) Ítem Poción de Purga Local

    +
    !item-crear purge_potion
    +Props (JSON): {"usable": true, "purgeAllEffects": true, "icon": "🧪"}
    +# Se consume al usar: !efectos purgar
    +# Para venderla: crear oferta o poner drop en cofre.
    +
    + +
    +

    6) Introducción a Status Effects Manuales

    +
    # (Opcional) Aplicar un efecto custom vía comando admin futuro
    +# Ejemplo conceptual JSON (guardado server-side):
    +{
    +  "type": "BLESSING",
    +  "magnitude": 0.10,   # +10% daño (interpretación futura)
    +  "durationMs": 600000 # 10 min
    +}
    +# Los efectos actuales: FATIGUE (reduce daño y defensa)
    +# Ver activos: !efectos
    +
    + +
    +

    7) Auditoría de Muertes

    +
    # Ver últimas muertes y penalizaciones
    +!deathlog           # por defecto 10
    +!deathlog 20        # máximo 20
    +
    +# Interpreta columnas
    +# HH:MM:SS | areaKey Lnivel | -oro | % | Fatiga | sin arma?
    +
    +# Ajusta balance si ves pérdidas demasiado altas en cierto nivel/riskFactor.
    +
    + +
    +

    8) Cadena Completa: Creación → Riesgo → Muerte

    +
    # 1. Crear arma y área con riesgo
    +!item-crear bone_sword
    +Props (JSON): {"tool": {"type": "sword", "tier": 1}, "damage": 9, "breakable": {"enabled": true, "maxDurability": 80}}
    +!area-crear arena.bone_trial
    +Config (JSON): {"cooldownSeconds": 45, "riskFactor": 1, "icon": "🗡️"}
    +!area-nivel arena.bone_trial 1
    +Requisitos (JSON): {"tool": {"required": true, "toolType": "sword", "minTier": 1}}
    +Mobs (JSON): {"draws":1, "table":[{"mobKey":"goblin","weight":10}]}
    +
    +# 2. Pelear varias veces para subir racha y ver bonus daño (!player)
    +# 3. Morir intencionalmente con monedas => verifica !deathlog
    +# 4. Aplicar purga de efectos si acumulaste FATIGUE
    +!efectos purgar
    +
    +# Ajusta riskFactor o nivel si la penalización % es muy baja/alta.
    +