Add HP toggle, new conditions, fix turn order sync bug
- Add DM toggle (default on) to hide player HP bars on player display; persisted in activeDisplay Firestore doc for real-time sync - Add Alchemist Fire and Bardic Inspiration conditions; sort all conditions alphabetically - Fix turn order skipping when participants are deleted, deactivated, or killed mid-combat: turnOrderIds was never updated, causing handleNextTurn to resolve currentIndex as -1 and snap back to the first participant. Now all mutation paths (delete, toggle active, HP death/resurrection) keep turnOrderIds in sync and advance the turn pointer correctly when the current participant is removed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+55
-6
@@ -152,6 +152,29 @@ const sortParticipantsByInitiative = (participants, originalOrder) => {
|
||||
});
|
||||
};
|
||||
|
||||
// Returns turnOrderIds/currentTurnParticipantId updates when a participant leaves active combat.
|
||||
const computeTurnOrderAfterRemoval = (encounter, removedId, updatedParticipants) => {
|
||||
if (!encounter.isStarted) return {};
|
||||
const currentIds = encounter.turnOrderIds || [];
|
||||
const newIds = currentIds.filter(id => id !== removedId);
|
||||
const updates = { turnOrderIds: newIds };
|
||||
if (encounter.currentTurnParticipantId === removedId) {
|
||||
const removedPos = currentIds.indexOf(removedId);
|
||||
const candidates = [...currentIds.slice(removedPos + 1), ...currentIds.slice(0, removedPos)];
|
||||
const nextId = candidates.find(id => updatedParticipants.find(p => p.id === id && p.isActive)) ?? null;
|
||||
updates.currentTurnParticipantId = nextId;
|
||||
}
|
||||
return updates;
|
||||
};
|
||||
|
||||
// Returns turnOrderIds update when a participant re-enters active combat mid-encounter.
|
||||
const computeTurnOrderAfterAddition = (encounter, addedId) => {
|
||||
if (!encounter.isStarted) return {};
|
||||
const currentIds = encounter.turnOrderIds || [];
|
||||
if (currentIds.includes(addedId)) return {};
|
||||
return { turnOrderIds: [...currentIds, addedId] };
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// CUSTOM HOOKS
|
||||
// ============================================================================
|
||||
@@ -956,7 +979,10 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
const updatedParticipants = participants.filter(p => p.id !== itemToDelete.id);
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
||||
await updateDoc(doc(db, encounterPath), {
|
||||
participants: updatedParticipants,
|
||||
...computeTurnOrderAfterRemoval(encounter, itemToDelete.id, updatedParticipants)
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Error deleting participant:", err);
|
||||
alert("Failed to delete participant. Please try again.");
|
||||
@@ -969,12 +995,20 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
const toggleParticipantActive = async (participantId) => {
|
||||
if (!db) return;
|
||||
|
||||
const participant = participants.find(p => p.id === participantId);
|
||||
if (!participant) return;
|
||||
const newIsActive = !participant.isActive;
|
||||
|
||||
const updatedParticipants = participants.map(p =>
|
||||
p.id === participantId ? { ...p, isActive: !p.isActive } : p
|
||||
p.id === participantId ? { ...p, isActive: newIsActive } : p
|
||||
);
|
||||
|
||||
const turnUpdates = newIsActive
|
||||
? computeTurnOrderAfterAddition(encounter, participantId)
|
||||
: computeTurnOrderAfterRemoval(encounter, participantId, updatedParticipants);
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants, ...turnUpdates });
|
||||
} catch (err) {
|
||||
console.error("Error toggling active state:", err);
|
||||
}
|
||||
@@ -1030,8 +1064,14 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
|
||||
return p;
|
||||
});
|
||||
|
||||
const turnUpdates = (isDead && !wasDead)
|
||||
? computeTurnOrderAfterRemoval(encounter, participantId, updatedParticipants)
|
||||
: wasResurrected
|
||||
? computeTurnOrderAfterAddition(encounter, participantId)
|
||||
: {};
|
||||
|
||||
try {
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
|
||||
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants, ...turnUpdates });
|
||||
setHpChangeValues(prev => ({ ...prev, [participantId]: '' }));
|
||||
} catch (err) {
|
||||
console.error("Error applying HP change:", err);
|
||||
@@ -1594,10 +1634,19 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentIndex = activePsInOrder.findIndex(p => p.id === encounter.currentTurnParticipantId);
|
||||
let nextIndex = (currentIndex + 1) % activePsInOrder.length;
|
||||
let currentIndex = activePsInOrder.findIndex(p => p.id === encounter.currentTurnParticipantId);
|
||||
let nextRound = encounter.round;
|
||||
|
||||
// Current participant was removed; find the next one after their old position in turnOrderIds
|
||||
if (currentIndex === -1) {
|
||||
const rawPos = (encounter.turnOrderIds || []).indexOf(encounter.currentTurnParticipantId);
|
||||
const candidateIds = [...(encounter.turnOrderIds || []).slice(rawPos + 1), ...(encounter.turnOrderIds || []).slice(0, rawPos)];
|
||||
const nextP = candidateIds.map(id => activePsInOrder.find(p => p.id === id)).find(Boolean);
|
||||
currentIndex = nextP ? activePsInOrder.findIndex(p => p.id === nextP.id) - 1 : -1;
|
||||
}
|
||||
|
||||
let nextIndex = (currentIndex + 1) % activePsInOrder.length;
|
||||
|
||||
if (nextIndex === 0 && currentIndex !== -1) {
|
||||
nextRound += 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user