feat: condition durations — 'Hexed (3)' ticks down each turn, drops at 0
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:
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user