feat: agregar barra de durabilidad y resumen de combate en comandos de mina, pelear y pescar; implementar comando para inspeccionar herramientas
This commit is contained in:
@@ -105,21 +105,52 @@ export const command: CommandMessage = {
|
|||||||
const mobsLines = result.mobs.length
|
const mobsLines = result.mobs.length
|
||||||
? result.mobs.map((m) => `• ${m}`).join("\n")
|
? result.mobs.map((m) => `• ${m}`).join("\n")
|
||||||
: "• —";
|
: "• —";
|
||||||
|
|
||||||
|
const durabilityBar = () => {
|
||||||
|
if (
|
||||||
|
!result.tool ||
|
||||||
|
result.tool.remaining == null ||
|
||||||
|
result.tool.max == null
|
||||||
|
)
|
||||||
|
return "";
|
||||||
|
const rem = Math.max(0, result.tool.remaining);
|
||||||
|
const max = Math.max(1, result.tool.max);
|
||||||
|
const ratio = rem / max;
|
||||||
|
const totalSegs = 10;
|
||||||
|
const filled = Math.round(ratio * totalSegs);
|
||||||
|
const bar = Array.from({ length: totalSegs })
|
||||||
|
.map((_, i) => (i < filled ? "█" : "░"))
|
||||||
|
.join("");
|
||||||
|
return `\nDurabilidad: [${bar}] ${rem}/${max}`;
|
||||||
|
};
|
||||||
|
|
||||||
const toolInfo = result.tool?.key
|
const toolInfo = result.tool?.key
|
||||||
? `${formatItemLabel(
|
? (() => {
|
||||||
rewardItems.get(result.tool.key) ?? {
|
const base = formatItemLabel(
|
||||||
key: result.tool.key,
|
rewardItems.get(result.tool.key) ?? {
|
||||||
name: null,
|
key: result.tool.key,
|
||||||
icon: null,
|
name: null,
|
||||||
},
|
icon: null,
|
||||||
{ fallbackIcon: "🔧" }
|
},
|
||||||
)}${
|
{ fallbackIcon: "🔧" }
|
||||||
result.tool.broken
|
);
|
||||||
? " (rota)"
|
if (result.tool.broken) {
|
||||||
: ` (-${result.tool.durabilityDelta ?? 0} dur.)`
|
return `${base} (agotada)${durabilityBar()}`;
|
||||||
}`
|
}
|
||||||
|
if (result.tool.brokenInstance) {
|
||||||
|
return `${base} (se rompió una instancia, quedan ${result.tool.instancesRemaining}) (-${result.tool.durabilityDelta ?? 0} dur.)${durabilityBar()}`;
|
||||||
|
}
|
||||||
|
const multi = result.tool.instancesRemaining && result.tool.instancesRemaining > 1 ? ` (x${result.tool.instancesRemaining})` : "";
|
||||||
|
return `${base}${multi} (-${result.tool.durabilityDelta ?? 0} dur.)${durabilityBar()}`;
|
||||||
|
})()
|
||||||
: "—";
|
: "—";
|
||||||
|
|
||||||
|
const combatSummary = (() => {
|
||||||
|
if (!result.combat) return null;
|
||||||
|
const c = result.combat;
|
||||||
|
return `**Combate**\n• Mobs: ${c.mobs.length} | Derrotados: ${c.mobsDefeated}/${result.mobs.length}\n• Daño hecho: ${c.totalDamageDealt} | Daño recibido: ${c.totalDamageTaken}`;
|
||||||
|
})();
|
||||||
|
|
||||||
const blocks = [textBlock("# ⛏️ Mina")];
|
const blocks = [textBlock("# ⛏️ Mina")];
|
||||||
|
|
||||||
if (globalNotice) {
|
if (globalNotice) {
|
||||||
@@ -141,6 +172,10 @@ export const command: CommandMessage = {
|
|||||||
blocks.push(textBlock(`**Recompensas**\n${rewardLines}`));
|
blocks.push(textBlock(`**Recompensas**\n${rewardLines}`));
|
||||||
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
||||||
blocks.push(textBlock(`**Mobs**\n${mobsLines}`));
|
blocks.push(textBlock(`**Mobs**\n${mobsLines}`));
|
||||||
|
if (combatSummary) {
|
||||||
|
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
||||||
|
blocks.push(textBlock(combatSummary));
|
||||||
|
}
|
||||||
|
|
||||||
// Añadir metadata del área (imagen/descripcion) si existe
|
// Añadir metadata del área (imagen/descripcion) si existe
|
||||||
const metaBlocks = buildAreaMetadataBlocks(area);
|
const metaBlocks = buildAreaMetadataBlocks(area);
|
||||||
|
|||||||
@@ -115,20 +115,45 @@ export const command: CommandMessage = {
|
|||||||
const mobsLines = result.mobs.length
|
const mobsLines = result.mobs.length
|
||||||
? result.mobs.map((m) => `• ${m}`).join("\n")
|
? result.mobs.map((m) => `• ${m}`).join("\n")
|
||||||
: "• —";
|
: "• —";
|
||||||
|
const durabilityBar = () => {
|
||||||
|
if (
|
||||||
|
!result.tool ||
|
||||||
|
result.tool.remaining == null ||
|
||||||
|
result.tool.max == null
|
||||||
|
)
|
||||||
|
return "";
|
||||||
|
const rem = Math.max(0, result.tool.remaining);
|
||||||
|
const max = Math.max(1, result.tool.max);
|
||||||
|
const ratio = rem / max;
|
||||||
|
const totalSegs = 10;
|
||||||
|
const filled = Math.round(ratio * totalSegs);
|
||||||
|
const bar = Array.from({ length: totalSegs })
|
||||||
|
.map((_, i) => (i < filled ? "█" : "░"))
|
||||||
|
.join("");
|
||||||
|
return `\nDurabilidad: [${bar}] ${rem}/${max}`;
|
||||||
|
};
|
||||||
const toolInfo = result.tool?.key
|
const toolInfo = result.tool?.key
|
||||||
? `${formatItemLabel(
|
? (() => {
|
||||||
rewardItems.get(result.tool.key) ?? {
|
const base = formatItemLabel(
|
||||||
key: result.tool.key,
|
rewardItems.get(result.tool.key) ?? {
|
||||||
name: null,
|
key: result.tool.key,
|
||||||
icon: null,
|
name: null,
|
||||||
},
|
icon: null,
|
||||||
{ fallbackIcon: "🗡️" }
|
},
|
||||||
)}${
|
{ fallbackIcon: "🗡️" }
|
||||||
result.tool.broken
|
);
|
||||||
? " (rota)"
|
if (result.tool.broken) return `${base} (agotada)${durabilityBar()}`;
|
||||||
: ` (-${result.tool.durabilityDelta ?? 0} dur.)`
|
if (result.tool.brokenInstance)
|
||||||
}`
|
return `${base} (se rompió una instancia, quedan ${result.tool.instancesRemaining}) (-${result.tool.durabilityDelta ?? 0} dur.)${durabilityBar()}`;
|
||||||
|
const multi = result.tool.instancesRemaining && result.tool.instancesRemaining > 1 ? ` (x${result.tool.instancesRemaining})` : "";
|
||||||
|
return `${base}${multi} (-${result.tool.durabilityDelta ?? 0} dur.)${durabilityBar()}`;
|
||||||
|
})()
|
||||||
: "—";
|
: "—";
|
||||||
|
const combatSummary = (() => {
|
||||||
|
if (!result.combat) return null;
|
||||||
|
const c = result.combat;
|
||||||
|
return `**Combate**\n• Mobs: ${c.mobs.length} | Derrotados: ${c.mobsDefeated}/${result.mobs.length}\n• Daño hecho: ${c.totalDamageDealt} | Daño recibido: ${c.totalDamageTaken}`;
|
||||||
|
})();
|
||||||
|
|
||||||
const blocks = [textBlock("# ⚔️ Arena")];
|
const blocks = [textBlock("# ⚔️ Arena")];
|
||||||
|
|
||||||
@@ -151,6 +176,10 @@ export const command: CommandMessage = {
|
|||||||
blocks.push(textBlock(`**Recompensas**\n${rewardLines}`));
|
blocks.push(textBlock(`**Recompensas**\n${rewardLines}`));
|
||||||
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
||||||
blocks.push(textBlock(`**Enemigos**\n${mobsLines}`));
|
blocks.push(textBlock(`**Enemigos**\n${mobsLines}`));
|
||||||
|
if (combatSummary) {
|
||||||
|
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
||||||
|
blocks.push(textBlock(combatSummary));
|
||||||
|
}
|
||||||
|
|
||||||
// Añadir metadata del área
|
// Añadir metadata del área
|
||||||
const metaBlocks = buildAreaMetadataBlocks(area);
|
const metaBlocks = buildAreaMetadataBlocks(area);
|
||||||
|
|||||||
@@ -101,20 +101,45 @@ export const command: CommandMessage = {
|
|||||||
const mobsLines = result.mobs.length
|
const mobsLines = result.mobs.length
|
||||||
? result.mobs.map((m) => `• ${m}`).join("\n")
|
? result.mobs.map((m) => `• ${m}`).join("\n")
|
||||||
: "• —";
|
: "• —";
|
||||||
|
const durabilityBar = () => {
|
||||||
|
if (
|
||||||
|
!result.tool ||
|
||||||
|
result.tool.remaining == null ||
|
||||||
|
result.tool.max == null
|
||||||
|
)
|
||||||
|
return "";
|
||||||
|
const rem = Math.max(0, result.tool.remaining);
|
||||||
|
const max = Math.max(1, result.tool.max);
|
||||||
|
const ratio = rem / max;
|
||||||
|
const totalSegs = 10;
|
||||||
|
const filled = Math.round(ratio * totalSegs);
|
||||||
|
const bar = Array.from({ length: totalSegs })
|
||||||
|
.map((_, i) => (i < filled ? "█" : "░"))
|
||||||
|
.join("");
|
||||||
|
return `\nDurabilidad: [${bar}] ${rem}/${max}`;
|
||||||
|
};
|
||||||
const toolInfo = result.tool?.key
|
const toolInfo = result.tool?.key
|
||||||
? `${formatItemLabel(
|
? (() => {
|
||||||
rewardItems.get(result.tool.key) ?? {
|
const base = formatItemLabel(
|
||||||
key: result.tool.key,
|
rewardItems.get(result.tool.key) ?? {
|
||||||
name: null,
|
key: result.tool.key,
|
||||||
icon: null,
|
name: null,
|
||||||
},
|
icon: null,
|
||||||
{ fallbackIcon: "🎣" }
|
},
|
||||||
)}${
|
{ fallbackIcon: "🎣" }
|
||||||
result.tool.broken
|
);
|
||||||
? " (rota)"
|
if (result.tool.broken) return `${base} (agotada)${durabilityBar()}`;
|
||||||
: ` (-${result.tool.durabilityDelta ?? 0} dur.)`
|
if (result.tool.brokenInstance)
|
||||||
}`
|
return `${base} (se rompió una instancia, quedan ${result.tool.instancesRemaining}) (-${result.tool.durabilityDelta ?? 0} dur.)${durabilityBar()}`;
|
||||||
|
const multi = result.tool.instancesRemaining && result.tool.instancesRemaining > 1 ? ` (x${result.tool.instancesRemaining})` : "";
|
||||||
|
return `${base}${multi} (-${result.tool.durabilityDelta ?? 0} dur.)${durabilityBar()}`;
|
||||||
|
})()
|
||||||
: "—";
|
: "—";
|
||||||
|
const combatSummary = (() => {
|
||||||
|
if (!result.combat) return null;
|
||||||
|
const c = result.combat;
|
||||||
|
return `**Combate**\n• Mobs: ${c.mobs.length} | Derrotados: ${c.mobsDefeated}/${result.mobs.length}\n• Daño hecho: ${c.totalDamageDealt} | Daño recibido: ${c.totalDamageTaken}`;
|
||||||
|
})();
|
||||||
|
|
||||||
const blocks = [textBlock("# 🎣 Pesca")];
|
const blocks = [textBlock("# 🎣 Pesca")];
|
||||||
|
|
||||||
@@ -137,6 +162,10 @@ export const command: CommandMessage = {
|
|||||||
blocks.push(textBlock(`**Recompensas**\n${rewardLines}`));
|
blocks.push(textBlock(`**Recompensas**\n${rewardLines}`));
|
||||||
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
||||||
blocks.push(textBlock(`**Mobs**\n${mobsLines}`));
|
blocks.push(textBlock(`**Mobs**\n${mobsLines}`));
|
||||||
|
if (combatSummary) {
|
||||||
|
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
||||||
|
blocks.push(textBlock(combatSummary));
|
||||||
|
}
|
||||||
|
|
||||||
// Añadir metadata del área
|
// Añadir metadata del área
|
||||||
const metaBlocks = buildAreaMetadataBlocks(area);
|
const metaBlocks = buildAreaMetadataBlocks(area);
|
||||||
|
|||||||
93
src/commands/messages/game/toolinfo.ts
Normal file
93
src/commands/messages/game/toolinfo.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import type { CommandMessage } from "../../../core/types/commands";
|
||||||
|
import type Amayo from "../../../core/client";
|
||||||
|
import { getInventoryEntry } from "../../../game/economy/service";
|
||||||
|
import {
|
||||||
|
buildDisplay,
|
||||||
|
textBlock,
|
||||||
|
dividerBlock,
|
||||||
|
} from "../../../core/lib/componentsV2";
|
||||||
|
import { formatItemLabel, sendDisplayReply } from "./_helpers";
|
||||||
|
|
||||||
|
// Inspecciona la durabilidad de una herramienta (no apilable) mostrando barra.
|
||||||
|
|
||||||
|
function parseJSON<T>(v: unknown): T | null {
|
||||||
|
if (!v || typeof v !== "object") return null;
|
||||||
|
return v as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const command: CommandMessage = {
|
||||||
|
name: "tool-info",
|
||||||
|
type: "message",
|
||||||
|
aliases: ["toolinfo", "herramienta", "inspectar", "inspeccionar"],
|
||||||
|
cooldown: 3,
|
||||||
|
description: "Muestra la durabilidad restante de una herramienta por su key.",
|
||||||
|
usage: "tool-info <itemKey>",
|
||||||
|
run: async (message, args, _client: Amayo) => {
|
||||||
|
const userId = message.author.id;
|
||||||
|
const guildId = message.guild!.id;
|
||||||
|
const key = args[0];
|
||||||
|
if (!key) {
|
||||||
|
await message.reply(
|
||||||
|
"⚠️ Debes indicar la key del item. Ej: `tool-info tool.pickaxe.basic`"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { item, entry } = await getInventoryEntry(userId, guildId, key);
|
||||||
|
if (!entry || !item) {
|
||||||
|
await message.reply("❌ No tienes este ítem en tu inventario.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const props = parseJSON<any>(item.props) ?? {};
|
||||||
|
const breakable = props.breakable;
|
||||||
|
if (!breakable || breakable.enabled === false) {
|
||||||
|
await message.reply("ℹ️ Este ítem no tiene durabilidad activa.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (item.stackable) {
|
||||||
|
await message.reply(`ℹ️ Ítem apilable. Cantidad: ${entry.quantity}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const state = parseJSON<any>(entry.state) ?? {};
|
||||||
|
const instances: any[] = Array.isArray(state.instances)
|
||||||
|
? state.instances
|
||||||
|
: [];
|
||||||
|
const max = Math.max(1, breakable.maxDurability ?? 1);
|
||||||
|
const label = formatItemLabel(
|
||||||
|
{ key: item.key, name: item.name, icon: item.icon },
|
||||||
|
{ fallbackIcon: "🛠️" }
|
||||||
|
);
|
||||||
|
const renderBar = (cur: number) => {
|
||||||
|
const ratio = cur / max;
|
||||||
|
const totalSegs = 20;
|
||||||
|
const filled = Math.round(ratio * totalSegs);
|
||||||
|
return Array.from({ length: totalSegs })
|
||||||
|
.map((_, i) => (i < filled ? "█" : "░"))
|
||||||
|
.join("");
|
||||||
|
};
|
||||||
|
const durLines = instances.length
|
||||||
|
? instances
|
||||||
|
.map((inst, idx) => {
|
||||||
|
const cur = Math.min(
|
||||||
|
Math.max(0, inst?.durability ?? max),
|
||||||
|
max
|
||||||
|
);
|
||||||
|
return `#${idx + 1} [${renderBar(cur)}] ${cur}/${max}`;
|
||||||
|
})
|
||||||
|
.join("\n")
|
||||||
|
: "(sin instancias)";
|
||||||
|
const blocks = [
|
||||||
|
textBlock("# 🔍 Herramienta"),
|
||||||
|
dividerBlock(),
|
||||||
|
textBlock(`**Item:** ${label}`),
|
||||||
|
textBlock(`Instancias: ${instances.length}`),
|
||||||
|
textBlock(durLines),
|
||||||
|
];
|
||||||
|
const accent = 0x95a5a6;
|
||||||
|
const display = buildDisplay(accent, blocks);
|
||||||
|
await sendDisplayReply(message, display);
|
||||||
|
} catch (e: any) {
|
||||||
|
await message.reply(`❌ No se pudo inspeccionar: ${e?.message ?? e}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -161,7 +161,17 @@ export async function addItemByKey(
|
|||||||
0,
|
0,
|
||||||
Math.min(qty, Math.max(0, max - state.instances.length))
|
Math.min(qty, Math.max(0, max - state.instances.length))
|
||||||
);
|
);
|
||||||
for (let i = 0; i < canAdd; i++) state.instances.push({});
|
// Inicializar durabilidad si corresponde
|
||||||
|
const props = parseItemProps(item.props);
|
||||||
|
const breakable = props.breakable;
|
||||||
|
const maxDurability = breakable?.enabled !== false ? breakable?.maxDurability : undefined;
|
||||||
|
for (let i = 0; i < canAdd; i++) {
|
||||||
|
if (maxDurability && maxDurability > 0) {
|
||||||
|
state.instances.push({ durability: maxDurability });
|
||||||
|
} else {
|
||||||
|
state.instances.push({});
|
||||||
|
}
|
||||||
|
}
|
||||||
const updated = await prisma.inventoryEntry.update({
|
const updated = await prisma.inventoryEntry.update({
|
||||||
where: { userId_guildId_itemId: { userId, guildId, itemId: item.id } },
|
where: { userId_guildId_itemId: { userId, guildId, itemId: item.id } },
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type {
|
|||||||
RunResult,
|
RunResult,
|
||||||
RewardsTable,
|
RewardsTable,
|
||||||
MobsTable,
|
MobsTable,
|
||||||
|
CombatSummary,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import type { Prisma } from "@prisma/client";
|
import type { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
@@ -178,12 +179,27 @@ async function reduceToolDurability(
|
|||||||
toolKey: string
|
toolKey: string
|
||||||
) {
|
) {
|
||||||
const { item, entry } = await getInventoryEntry(userId, guildId, toolKey);
|
const { item, entry } = await getInventoryEntry(userId, guildId, toolKey);
|
||||||
if (!entry) return { broken: false, delta: 0 } as const;
|
if (!entry)
|
||||||
|
return {
|
||||||
|
broken: false,
|
||||||
|
brokenInstance: false,
|
||||||
|
delta: 0,
|
||||||
|
remaining: undefined,
|
||||||
|
max: undefined,
|
||||||
|
instancesRemaining: 0,
|
||||||
|
} as const;
|
||||||
const props = parseItemProps(item.props);
|
const props = parseItemProps(item.props);
|
||||||
const breakable = props.breakable;
|
const breakable = props.breakable;
|
||||||
// Si el item no es breakable o la durabilidad está deshabilitada, no hacemos nada
|
// Si el item no es breakable o la durabilidad está deshabilitada, no hacemos nada
|
||||||
if (!breakable || breakable.enabled === false) {
|
if (!breakable || breakable.enabled === false) {
|
||||||
return { broken: false, delta: 0 } as const;
|
return {
|
||||||
|
broken: false,
|
||||||
|
brokenInstance: false,
|
||||||
|
delta: 0,
|
||||||
|
remaining: undefined,
|
||||||
|
max: undefined,
|
||||||
|
instancesRemaining: entry.quantity ?? 0,
|
||||||
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valores base
|
// Valores base
|
||||||
@@ -197,30 +213,38 @@ async function reduceToolDurability(
|
|||||||
if (item.stackable) {
|
if (item.stackable) {
|
||||||
// Herramientas deberían ser no apilables; si lo son, solo decrementamos cantidad como fallback
|
// Herramientas deberían ser no apilables; si lo son, solo decrementamos cantidad como fallback
|
||||||
const consumed = Math.min(1, entry.quantity);
|
const consumed = Math.min(1, entry.quantity);
|
||||||
|
let broken = false;
|
||||||
if (consumed > 0) {
|
if (consumed > 0) {
|
||||||
await prisma.inventoryEntry.update({
|
const updated = await prisma.inventoryEntry.update({
|
||||||
where: { userId_guildId_itemId: { userId, guildId, itemId: item.id } },
|
where: { userId_guildId_itemId: { userId, guildId, itemId: item.id } },
|
||||||
data: { quantity: { decrement: consumed } },
|
data: { quantity: { decrement: consumed } },
|
||||||
});
|
});
|
||||||
|
// Consideramos "rota" sólo si después de consumir ya no queda ninguna unidad
|
||||||
|
broken = (updated.quantity ?? 0) <= 0;
|
||||||
}
|
}
|
||||||
return { broken: consumed > 0, delta } as const;
|
return { broken, brokenInstance: broken, delta, remaining: undefined, max: maxConfigured, instancesRemaining: broken ? 0 : (entry.quantity ?? 1) - 1 } as const;
|
||||||
}
|
}
|
||||||
const state = parseInvState(entry.state);
|
const state = parseInvState(entry.state);
|
||||||
state.instances ??= [{}];
|
state.instances ??= [{}];
|
||||||
if (state.instances.length === 0) state.instances.push({});
|
if (state.instances.length === 0) state.instances.push({});
|
||||||
|
// Seleccionar instancia: ahora usamos la primera, en futuro se puede elegir la de mayor durabilidad restante
|
||||||
const inst = state.instances[0];
|
const inst = state.instances[0];
|
||||||
const max = maxConfigured; // ya calculado arriba
|
const max = maxConfigured; // ya calculado arriba
|
||||||
|
// Si la instancia no tiene durabilidad inicial, la inicializamos
|
||||||
|
if (inst.durability == null) (inst as any).durability = max;
|
||||||
const current = Math.min(Math.max(0, inst.durability ?? max), max);
|
const current = Math.min(Math.max(0, inst.durability ?? max), max);
|
||||||
const next = current - delta;
|
const next = current - delta;
|
||||||
let broken = false;
|
let brokenInstance = false;
|
||||||
if (next <= 0) {
|
if (next <= 0) {
|
||||||
// romper: eliminar instancia
|
// romper sólo esta instancia
|
||||||
state.instances.shift();
|
state.instances.shift();
|
||||||
broken = true;
|
brokenInstance = true;
|
||||||
} else {
|
} else {
|
||||||
(inst as any).durability = next;
|
(inst as any).durability = next;
|
||||||
state.instances[0] = inst;
|
state.instances[0] = inst;
|
||||||
}
|
}
|
||||||
|
const instancesRemaining = state.instances.length;
|
||||||
|
const broken = instancesRemaining === 0; // Ítem totalmente agotado
|
||||||
await prisma.inventoryEntry.update({
|
await prisma.inventoryEntry.update({
|
||||||
where: { userId_guildId_itemId: { userId, guildId, itemId: item.id } },
|
where: { userId_guildId_itemId: { userId, guildId, itemId: item.id } },
|
||||||
data: {
|
data: {
|
||||||
@@ -228,7 +252,14 @@ async function reduceToolDurability(
|
|||||||
quantity: state.instances.length,
|
quantity: state.instances.length,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return { broken, delta } as const;
|
// Placeholder: logging de ruptura (migrar a ToolBreakLog futuro)
|
||||||
|
if (brokenInstance) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`[tool-break] user=${userId} guild=${guildId} toolKey=${toolKey}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return { broken, brokenInstance, delta, remaining: broken ? 0 : next, max, instancesRemaining } as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { reduceToolDurability };
|
export { reduceToolDurability };
|
||||||
@@ -280,6 +311,80 @@ export async function runMinigame(
|
|||||||
key: reqRes.toolKeyUsed,
|
key: reqRes.toolKeyUsed,
|
||||||
durabilityDelta: t.delta,
|
durabilityDelta: t.delta,
|
||||||
broken: t.broken,
|
broken: t.broken,
|
||||||
|
remaining: t.remaining,
|
||||||
|
max: t.max,
|
||||||
|
brokenInstance: t.brokenInstance,
|
||||||
|
instancesRemaining: t.instancesRemaining,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Combate Básico (placeholder) ---
|
||||||
|
// Objetivo: procesar mobs spawneados y generar resumen estadístico simple.
|
||||||
|
// Futuro: stats jugador, equipo, habilidades. Ahora: valores fijos pseudo-aleatorios.
|
||||||
|
let combatSummary: CombatSummary | undefined;
|
||||||
|
if (mobsSpawned.length > 0) {
|
||||||
|
const mobLogs: CombatSummary["mobs"] = [];
|
||||||
|
let totalDealt = 0;
|
||||||
|
let totalTaken = 0;
|
||||||
|
let defeated = 0;
|
||||||
|
const basePlayerAtk = 5 + Math.random() * 3; // placeholder
|
||||||
|
const basePlayerDef = 1 + Math.random() * 2; // placeholder
|
||||||
|
for (const mobKey of mobsSpawned) {
|
||||||
|
// Se podría leer tabla real de mobs (stats json en prisma.mob) en futuro.
|
||||||
|
// Por ahora HP aleatorio controlado.
|
||||||
|
const maxHp = 8 + Math.floor(Math.random() * 8); // 8-15
|
||||||
|
let hp = maxHp;
|
||||||
|
const rounds = [] as any[];
|
||||||
|
let roundIndex = 1;
|
||||||
|
let mobTotalDealt = 0;
|
||||||
|
let mobTotalTaken = 0;
|
||||||
|
while (hp > 0 && roundIndex <= 10) {
|
||||||
|
// limite de 10 rondas por mob
|
||||||
|
const playerDamage = Math.max(
|
||||||
|
1,
|
||||||
|
Math.round(basePlayerAtk + Math.random() * 2)
|
||||||
|
);
|
||||||
|
hp -= playerDamage;
|
||||||
|
mobTotalDealt += playerDamage;
|
||||||
|
totalDealt += playerDamage;
|
||||||
|
let playerTaken = 0;
|
||||||
|
if (hp > 0) {
|
||||||
|
// Mob sólo pega si sigue vivo
|
||||||
|
const mobAtk = 2 + Math.random() * 3; // 2-5
|
||||||
|
const mitigated = Math.max(0, mobAtk - basePlayerDef * 0.5);
|
||||||
|
playerTaken = Math.round(mitigated);
|
||||||
|
totalTaken += playerTaken;
|
||||||
|
mobTotalTaken += playerTaken;
|
||||||
|
}
|
||||||
|
rounds.push({
|
||||||
|
mobKey,
|
||||||
|
round: roundIndex,
|
||||||
|
playerDamageDealt: playerDamage,
|
||||||
|
playerDamageTaken: playerTaken,
|
||||||
|
mobRemainingHp: Math.max(0, hp),
|
||||||
|
mobDefeated: hp <= 0,
|
||||||
|
});
|
||||||
|
if (hp <= 0) {
|
||||||
|
defeated++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
roundIndex++;
|
||||||
|
}
|
||||||
|
mobLogs.push({
|
||||||
|
mobKey,
|
||||||
|
maxHp,
|
||||||
|
defeated: hp <= 0,
|
||||||
|
totalDamageDealt: mobTotalDealt,
|
||||||
|
totalDamageTakenFromMob: mobTotalTaken,
|
||||||
|
rounds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
combatSummary = {
|
||||||
|
mobs: mobLogs,
|
||||||
|
totalDamageDealt: totalDealt,
|
||||||
|
totalDamageTaken: totalTaken,
|
||||||
|
mobsDefeated: defeated,
|
||||||
|
victory: defeated === mobsSpawned.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,6 +393,7 @@ export async function runMinigame(
|
|||||||
rewards: delivered,
|
rewards: delivered,
|
||||||
mobs: mobsSpawned,
|
mobs: mobsSpawned,
|
||||||
tool: toolInfo,
|
tool: toolInfo,
|
||||||
|
combat: combatSummary,
|
||||||
notes: "auto",
|
notes: "auto",
|
||||||
} as unknown as Prisma.InputJsonValue;
|
} as unknown as Prisma.InputJsonValue;
|
||||||
|
|
||||||
@@ -334,6 +440,7 @@ export async function runMinigame(
|
|||||||
rewards: delivered,
|
rewards: delivered,
|
||||||
mobs: mobsSpawned,
|
mobs: mobsSpawned,
|
||||||
tool: toolInfo,
|
tool: toolInfo,
|
||||||
|
combat: combatSummary,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
export type ToolRequirement = {
|
export type ToolRequirement = {
|
||||||
required?: boolean; // si se requiere herramienta
|
required?: boolean; // si se requiere herramienta
|
||||||
toolType?: string; // 'pickaxe' | 'rod' | 'sword' | ...
|
toolType?: string; // 'pickaxe' | 'rod' | 'sword' | ...
|
||||||
minTier?: number; // nivel mínimo de herramienta
|
minTier?: number; // nivel mínimo de herramienta
|
||||||
allowedKeys?: string[]; // lista blanca de item keys específicos
|
allowedKeys?: string[]; // lista blanca de item keys específicos
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -15,8 +15,8 @@ export type LevelRequirements = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type WeightedReward =
|
export type WeightedReward =
|
||||||
| { type: 'coins'; amount: number; weight: number }
|
| { type: "coins"; amount: number; weight: number }
|
||||||
| { type: 'item'; itemKey: string; qty: number; weight: number };
|
| { type: "item"; itemKey: string; qty: number; weight: number };
|
||||||
|
|
||||||
export type RewardsTable = {
|
export type RewardsTable = {
|
||||||
draws?: number; // cuántas extracciones realizar (default 1)
|
draws?: number; // cuántas extracciones realizar (default 1)
|
||||||
@@ -44,8 +44,48 @@ export type RunMinigameOptions = {
|
|||||||
|
|
||||||
export type RunResult = {
|
export type RunResult = {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
rewards: Array<{ type: 'coins' | 'item'; amount?: number; itemKey?: string; qty?: number }>;
|
rewards: Array<{
|
||||||
|
type: "coins" | "item";
|
||||||
|
amount?: number;
|
||||||
|
itemKey?: string;
|
||||||
|
qty?: number;
|
||||||
|
}>;
|
||||||
mobs: string[]; // keys de mobs spawneados
|
mobs: string[]; // keys de mobs spawneados
|
||||||
tool?: { key?: string; durabilityDelta?: number; broken?: boolean };
|
tool?: {
|
||||||
|
key?: string;
|
||||||
|
durabilityDelta?: number; // cuanto se redujo en esta ejecución
|
||||||
|
broken?: boolean; // si se rompió en este uso
|
||||||
|
remaining?: number; // durabilidad restante después de aplicar delta (si aplica)
|
||||||
|
max?: number; // durabilidad máxima configurada
|
||||||
|
brokenInstance?: boolean; // true si solo se rompió una instancia
|
||||||
|
instancesRemaining?: number; // instancias que quedan después del uso
|
||||||
|
};
|
||||||
|
combat?: CombatSummary; // resumen de combate si hubo mobs y se procesó
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- Combate Básico ---
|
||||||
|
export type CombatRound = {
|
||||||
|
mobKey: string;
|
||||||
|
round: number;
|
||||||
|
playerDamageDealt: number; // daño infligido al mob en esta ronda
|
||||||
|
playerDamageTaken: number; // daño recibido del mob en esta ronda
|
||||||
|
mobRemainingHp: number; // hp restante del mob tras la ronda
|
||||||
|
mobDefeated?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CombatMobLog = {
|
||||||
|
mobKey: string;
|
||||||
|
maxHp: number;
|
||||||
|
defeated: boolean;
|
||||||
|
totalDamageDealt: number;
|
||||||
|
totalDamageTakenFromMob: number; // daño que el jugador recibió de este mob
|
||||||
|
rounds: CombatRound[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CombatSummary = {
|
||||||
|
mobs: CombatMobLog[];
|
||||||
|
totalDamageDealt: number;
|
||||||
|
totalDamageTaken: number;
|
||||||
|
mobsDefeated: number;
|
||||||
|
victory: boolean; // true si el jugador sobrevivió a todos los mobs
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user