feat: Health and Resolve rollable saves

Click the Health or Resolve label to roll a save using the current
value as the dice pool (plain d6, no stress, no dialog). Zero successes
adds +1 Stress. Chat card uses standard yze-die styling.
This commit is contained in:
2026-06-03 18:18:01 +00:00
parent 953392b55c
commit c79fedc343
3 changed files with 72 additions and 2 deletions
+9
View File
@@ -257,6 +257,15 @@
letter-spacing: 0.8px;
}
.zero-engine .vital-save-label {
cursor: pointer;
transition: opacity 0.15s;
}
.zero-engine .vital-save-label:hover {
opacity: 0.75;
text-decoration: underline dotted;
}
.zero-engine .vital-values {
display: flex;
align-items: center;
+2 -2
View File
@@ -109,7 +109,7 @@
{{!-- VITALS ROW: Health, Resolve, Stress, Ebb --}}
<div class="header-vitals-row">
<div class="vital-box health-box">
<label>Health</label>
<label class="vital-save-label" data-save-type="health" title="Click to roll Health Save (no successes = +1 Stress)">Health</label>
<div class="vital-values">
<input type="number" name="system.derivedStats.health.value" value="{{system.derivedStats.health.value}}" />
<span class="vital-separator">/</span>
@@ -117,7 +117,7 @@
</div>
</div>
<div class="vital-box resolve-box">
<label>Resolve</label>
<label class="vital-save-label" data-save-type="resolve" title="Click to roll Resolve Save (no successes = +1 Stress)">Resolve</label>
<div class="vital-values">
<input type="number" name="system.derivedStats.resolve.value" value="{{system.derivedStats.resolve.value}}" />
<span class="vital-separator">/</span>
+61
View File
@@ -455,6 +455,9 @@ class ZeroEngineActorSheet extends foundry.appv1.sheets.ActorSheet {
html.find('.manual-panic-button').click(this._onManualPanic.bind(this));
html.find('.manual-armor-button').click(this._onManualArmorCheck.bind(this));
// Health / Resolve save rolls — click the vital label name
html.find('.vital-save-label').click(this._onVitalSave.bind(this));
// Delete injury buttons
html.find('.delete-injury-btn').click(this._onDeleteInjury.bind(this));
@@ -1214,6 +1217,64 @@ class ZeroEngineActorSheet extends foundry.appv1.sheets.ActorSheet {
}
}
/**
* Handle Health or Resolve save roll — clicking the vital label name.
* Rolls current value in plain d6 (no stress dice, no dialog).
* A failure (zero successes) adds +1 Stress.
* @param {Event} event
* @private
*/
async _onVitalSave(event) {
event.preventDefault();
event.stopPropagation();
const saveType = event.currentTarget.dataset.saveType; // "health" or "resolve"
const label = saveType === "health" ? "Health Save" : "Resolve Save";
const current = Number(this.actor.system.derivedStats?.[saveType]?.value) || 0;
if (current <= 0) {
ui.notifications.warn(`${this.actor.name} has no ${saveType} remaining — cannot make a ${label}.`);
return;
}
// Roll current value in d6 — no stress dice, no dialog
const roll = new Roll(`${current}d6`);
await roll.evaluate();
if (game.dice3d) await game.dice3d.showForRoll(roll, game.user, true);
const results = roll.dice[0]?.results || [];
const successes = results.filter(r => r.result === 6).length;
const failed = successes === 0;
// Build dice display using existing yze-die classes
const diceHtml = results.map(r => {
let cls = 'yze-die';
if (r.result === 6) cls += ' yze-success';
return `<div class="${cls}">${r.result}</div>`;
}).join('');
let resultLine;
if (failed) {
const curStress = this._getStressValue(this.actor);
const newStress = Math.min(10, curStress + 1);
await this.actor.update(this._buildStressUpdate(this.actor, newStress));
resultLine = `<div class="yze-summary" style="color:#ff4444"><strong>FAILED</strong> — no successes. +1 Stress (now ${newStress}).</div>`;
} else {
resultLine = `<div class="yze-summary" style="color:#44cc66"><strong>SUCCESS</strong> — ${successes} success${successes !== 1 ? 'es' : ''}!</div>`;
}
await ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor: `<strong>${this.actor.name} — ${label}</strong><br><small>${current}d6 (current ${saveType === 'health' ? 'Health' : 'Resolve'})</small>`,
content: `<div class="yze-roll-result"><div class="yze-dice-display">${diceHtml}</div>${resultLine}</div>`,
style: CONST.CHAT_MESSAGE_STYLES?.ROLL,
type: CONST.CHAT_MESSAGE_TYPES?.ROLL,
roll: roll,
rollMode: game.settings.get('core', 'rollMode')
});
}
/**
* Handle Manual Panic button click
* @param {Event} event