feat: condition durations — 'Hexed (3)' ticks down each turn, drops at 0
CI / frontend (push) Successful in 30s
CI / rust (push) Successful in 5m26s

Numbered conditions decrement when the combatant's turn starts and
expire at zero; plain conditions still toggle manually. The custom
condition input hints the syntax. Roadmap 1.8 — Phase 1 complete.
This commit is contained in:
itsamejms
2026-09-07 10:28:30 +01:00
parent 630a025e77
commit 160788d63b
+28 -1
View File
@@ -157,10 +157,34 @@ export function InitiativeTracker() {
);
}, []);
// ponytail: conditions written as "Name (N)" auto-expire: the number
// decrements each time that combatant's turn starts, and the condition
// drops at zero. Plain conditions run until toggled off manually.
function tickDurations(id: string) {
setCombatants((prev) =>
prev.map((c) => {
if (c.id !== id) return c;
const next: string[] = [];
for (const cn of c.conditions) {
const m = cn.match(/^(.*?)\s*\((\d+)\)$/);
if (!m) {
next.push(cn);
continue;
}
const n = parseInt(m[2], 10);
if (n > 1) next.push(`${m[1]} (${n - 1})`);
// n === 1 → expired, dropped
}
return { ...c, conditions: next };
}),
);
}
const nextTurn = useCallback(() => {
if (combatants.length === 0) return;
if (!activeId) {
setActiveId(combatants[0].id);
tickDurations(combatants[0].id);
bus.emit(Events.LogEntry, { text: `⚔ Combat starts — ${combatants[0].name}`, tag: "#combat" });
return;
}
@@ -168,9 +192,11 @@ export function InitiativeTracker() {
if (idx === combatants.length - 1) {
setRound((r) => r + 1);
setActiveId(combatants[0].id);
tickDurations(combatants[0].id);
bus.emit(Events.LogEntry, { text: `⚔ Round ${round + 1} — ${combatants[0].name}'s turn`, tag: "#combat" });
} else {
setActiveId(combatants[idx + 1].id);
tickDurations(combatants[idx + 1].id);
bus.emit(Events.LogEntry, { text: `⚔ ${combatants[idx + 1].name}'s turn`, tag: "#combat" });
}
setTimerLeft(TURN_SECONDS); // ponytail: reset the per-turn timer.
@@ -513,7 +539,8 @@ export function InitiativeTracker() {
<div className="flex gap-1 mt-1">
<input
className="flex-1 min-w-0 rounded bg-[var(--color-bg-deep)] border border-[var(--color-gold-bright)] px-1.5 py-0.5 text-[10px] text-[var(--color-text-primary)] focus:outline-none"
placeholder="Custom condition…"
placeholder="Custom condition… e.g. Hexed (3)"
title="A number in (rounds) makes the condition auto-expire: it ticks down each turn and drops at zero."
value={condInput[c.id] ?? ""}
onChange={(e) => setCondInput((p) => ({ ...p, [c.id]: e.target.value }))}
onKeyDown={(e) => {