Character writeback on encounter end
Campaign-level toggle (CharacterManager, live flip). When ON, ending an encounter writes each character participant's current HP, Max HP, and AC back to the campaign roster so they carry into the next encounter. - shared: buildCharacterParticipant uses defaultCurrentHp (fallback maxHp) - endEncounter: syncs maxHp/ac/currentHp to campaign players if syncCharacters, snapshots old values into undo payload for restore. Works retroactive (ctx.campaignId fallback for encounters lacking campaignId field). - New encounters store campaignId field. - Undo end-encounter restores character values client-side. - CharacterManager: styled sync toggle (checkbox badge), stat badges in list (HP/Current HP/AC/Init), edit box supports Current HP field. Tests: 5 writeback cases (off/on/skip monsters/undo snapshot/missing char).
This commit is contained in:
+34
-1
@@ -370,6 +370,7 @@ function buildCharacterParticipant(character) {
|
||||
const modifier = character.defaultInitMod || 0;
|
||||
const finalInitiative = initiativeRoll + modifier;
|
||||
const maxHp = character.defaultMaxHp || DEFAULT_MAX_HP;
|
||||
const currentHp = character.defaultCurrentHp != null ? character.defaultCurrentHp : maxHp;
|
||||
return {
|
||||
participant: makeParticipant({
|
||||
name: character.name,
|
||||
@@ -377,7 +378,7 @@ function buildCharacterParticipant(character) {
|
||||
originalCharacterId: character.id,
|
||||
initiative: finalInitiative,
|
||||
maxHp,
|
||||
currentHp: maxHp,
|
||||
currentHp,
|
||||
ac: character.defaultAc !== undefined ? character.defaultAc : null,
|
||||
}),
|
||||
roll: { roll: initiativeRoll, mod: modifier, total: finalInitiative },
|
||||
@@ -1054,6 +1055,38 @@ async function endEncounter(encounter, ctx) {
|
||||
delta: {},
|
||||
undo: { isStarted: encounter.isStarted ?? false, isPaused: encounter.isPaused ?? false, round: encounter.round ?? 0, currentTurnParticipantId: encounter.currentTurnParticipantId ?? null, turnOrderIds: [...(encounter.turnOrderIds || [])], endedAt: encounter.endedAt ?? null },
|
||||
};
|
||||
|
||||
// Character writeback: sync maxHp/ac/currentHp to campaign players if enabled.
|
||||
const wbCampaignId = encounter.campaignId || ctx.campaignId;
|
||||
if ((wbCampaignId || ctx.campaign) && ctx.storage) {
|
||||
let campaign;
|
||||
try {
|
||||
campaign = ctx.campaign || await ctx.storage.getDoc(`campaigns/${wbCampaignId}`);
|
||||
} catch { campaign = null; }
|
||||
if (campaign && campaign.syncCharacters && Array.isArray(campaign.players)) {
|
||||
const charParticipants = (encounter.participants || []).filter(p => p.originalCharacterId);
|
||||
const oldValues = [];
|
||||
let changed = false;
|
||||
const updatedPlayers = campaign.players.map(player => {
|
||||
const cp = charParticipants.find(p => p.originalCharacterId === player.id);
|
||||
if (!cp) return player;
|
||||
const oldMaxHp = player.defaultMaxHp;
|
||||
const oldAc = player.defaultAc;
|
||||
const oldCurrentHp = player.defaultCurrentHp;
|
||||
oldValues.push({ id: player.id, defaultMaxHp: oldMaxHp, defaultAc: oldAc, defaultCurrentHp: oldCurrentHp });
|
||||
const newPlayer = { ...player };
|
||||
if (cp.maxHp != null && cp.maxHp !== oldMaxHp) { newPlayer.defaultMaxHp = cp.maxHp; changed = true; }
|
||||
if (cp.ac != null && cp.ac !== oldAc) { newPlayer.defaultAc = cp.ac; changed = true; }
|
||||
if (cp.currentHp != null && cp.currentHp !== oldCurrentHp) { newPlayer.defaultCurrentHp = cp.currentHp; changed = true; }
|
||||
return newPlayer;
|
||||
});
|
||||
if (changed) {
|
||||
await ctx.storage.updateDoc(`campaigns/${wbCampaignId}`, { players: updatedPlayers });
|
||||
log.undo.characterWriteback = oldValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return commit(encounter, patch, log, ctx);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user