Feature: implement loot resolution system with gold and item tracking from defeated creatures

This commit is contained in:
Keith Solomon
2026-03-15 14:24:39 -05:00
parent cf98636a52
commit 6c2257b032
10 changed files with 478 additions and 7 deletions

View File

@@ -9,6 +9,7 @@ import type { LogEntry } from "@/types/rules";
import { findCreatureById } from "@/data/contentHelpers";
import { startCombatFromRoom } from "./combat";
import { resolveCombatLoot } from "./loot";
import {
resolveEnemyTurn,
resolvePlayerAttack,
@@ -163,6 +164,10 @@ function cloneRun(run: RunState): RunState {
globalFlags: [...run.dungeon.globalFlags],
},
activeCombat: run.activeCombat ? cloneCombat(run.activeCombat) : undefined,
defeatedCreatureIds: [...run.defeatedCreatureIds],
xpGained: run.xpGained,
goldGained: run.goldGained,
lootedItems: run.lootedItems.map((entry) => ({ ...entry })),
log: run.log.map((entry) => ({
...entry,
relatedIds: entry.relatedIds ? [...entry.relatedIds] : undefined,
@@ -252,6 +257,7 @@ function applyCombatRewards(
content: ContentPack,
run: RunState,
completedCombat: CombatState,
roller: DiceRoller | undefined,
at: string,
) {
const defeatedCreatureIds = completedCombat.enemies
@@ -265,18 +271,45 @@ function applyCombatRewards(
run.xpGained += xpAwarded;
run.adventurerSnapshot.xp += xpAwarded;
if (xpAwarded === 0) {
return [] as LogEntry[];
const lootResult = resolveCombatLoot({
content,
combat: completedCombat,
inventory: run.adventurerSnapshot.inventory,
roller,
at,
});
run.adventurerSnapshot.inventory = lootResult.inventory;
run.goldGained += lootResult.goldAwarded;
for (const item of lootResult.itemsAwarded) {
const existing = run.lootedItems.find(
(entry) => entry.definitionId === item.definitionId,
);
if (existing) {
existing.quantity += item.quantity;
continue;
}
run.lootedItems.push({ ...item });
}
const rewardLogs = [...lootResult.logEntries];
if (xpAwarded === 0 && lootResult.goldAwarded === 0 && lootResult.itemsAwarded.length === 0) {
return rewardLogs;
}
return [
rewardLogs.push(
createRewardLog(
`${completedCombat.id}.rewards`,
at,
`Victory rewards: gained ${xpAwarded} XP from ${defeatedCreatureIds.length} defeated creature${defeatedCreatureIds.length === 1 ? "" : "s"}.`,
`Victory rewards: gained ${xpAwarded} XP, ${lootResult.goldAwarded} gold, and ${lootResult.itemsAwarded.reduce((total, item) => total + item.quantity, 0)} loot item${lootResult.itemsAwarded.reduce((total, item) => total + item.quantity, 0) === 1 ? "" : "s"} from ${defeatedCreatureIds.length} defeated creature${defeatedCreatureIds.length === 1 ? "" : "s"}.`,
[completedCombat.id, ...defeatedCreatureIds],
),
];
);
return rewardLogs;
}
export function createRunState(options: CreateRunOptions): RunState {
@@ -305,6 +338,8 @@ export function createRunState(options: CreateRunOptions): RunState {
adventurerSnapshot: options.adventurer,
defeatedCreatureIds: [],
xpGained: 0,
goldGained: 0,
lootedItems: [],
log: [],
pendingEffects: [],
};
@@ -494,6 +529,7 @@ export function resolveRunPlayerTurn(
options.content,
run,
completedCombat,
options.roller,
options.at ?? new Date().toISOString(),
);