Combatants can now be assigned to party or enemy side via a toggle in the difficulty breakdown panel. Party-side NPCs subtract their XP from the encounter total, letting allied NPCs reduce difficulty. PCs default to party, non-PCs to enemy — users who don't use sides see no change. Side persists across reload and export/import. Closes #22 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import type {
|
|
Combatant,
|
|
CombatantDescriptor,
|
|
CreatureId,
|
|
DifficultyResult,
|
|
PlayerCharacter,
|
|
} from "@initiative/domain";
|
|
import { calculateEncounterDifficulty } from "@initiative/domain";
|
|
import { useMemo } from "react";
|
|
import { useBestiaryContext } from "../contexts/bestiary-context.js";
|
|
import { useEncounterContext } from "../contexts/encounter-context.js";
|
|
import { usePlayerCharactersContext } from "../contexts/player-characters-context.js";
|
|
|
|
export function resolveSide(c: Combatant): "party" | "enemy" {
|
|
if (c.side) return c.side;
|
|
return c.playerCharacterId ? "party" : "enemy";
|
|
}
|
|
|
|
function buildDescriptors(
|
|
combatants: readonly Combatant[],
|
|
characters: readonly PlayerCharacter[],
|
|
getCreature: (id: CreatureId) => { cr: string } | undefined,
|
|
): CombatantDescriptor[] {
|
|
const descriptors: CombatantDescriptor[] = [];
|
|
for (const c of combatants) {
|
|
const side = resolveSide(c);
|
|
const level = c.playerCharacterId
|
|
? characters.find((p) => p.id === c.playerCharacterId)?.level
|
|
: undefined;
|
|
const cr = c.creatureId
|
|
? getCreature(c.creatureId)?.cr
|
|
: (c.cr ?? undefined);
|
|
|
|
if (level !== undefined || cr !== undefined) {
|
|
descriptors.push({ level, cr, side });
|
|
}
|
|
}
|
|
return descriptors;
|
|
}
|
|
|
|
export function useDifficulty(): DifficultyResult | null {
|
|
const { encounter } = useEncounterContext();
|
|
const { characters } = usePlayerCharactersContext();
|
|
const { getCreature } = useBestiaryContext();
|
|
|
|
return useMemo(() => {
|
|
const descriptors = buildDescriptors(
|
|
encounter.combatants,
|
|
characters,
|
|
getCreature,
|
|
);
|
|
|
|
const hasPartyLevel = descriptors.some(
|
|
(d) => d.side === "party" && d.level !== undefined,
|
|
);
|
|
const hasCr = descriptors.some((d) => d.cr !== undefined);
|
|
|
|
if (!hasPartyLevel || !hasCr) return null;
|
|
|
|
return calculateEncounterDifficulty(descriptors);
|
|
}, [encounter.combatants, characters, getCreature]);
|
|
}
|