diff --git a/slazero2.mjs b/slazero2.mjs index 2b17162..7c1db86 100644 --- a/slazero2.mjs +++ b/slazero2.mjs @@ -1382,9 +1382,9 @@ class ZeroEngineActorSheet extends foundry.appv1.sheets.ActorSheet { return; } - // Deduct flux before rolling + // Flux is deducted only when the roll is confirmed (see onConfirm below) + // — cancelling the dialog must not burn Flux. const newFlux = Math.max(0, currentFlux - fluxCost); - await this.actor.update({ "system.flux.value": newFlux }); // Build dice pool: Empathy attribute + Ebb skill // Use parseInt() || 0 — robust against corrupt stored values ("3,3", "NaN", etc.) @@ -1414,7 +1414,10 @@ class ZeroEngineActorSheet extends foundry.appv1.sheets.ActorSheet { duration: formula.duration || "" }; - return this._rollYZEDice(totalDice, label, formulaStr, "empathy", null, { ebbInfo }); + return this._rollYZEDice(totalDice, label, formulaStr, "empathy", null, { + ebbInfo, + onConfirm: async () => this.actor.update({ "system.flux.value": newFlux }) + }); } /** @@ -2548,7 +2551,7 @@ class ZeroEngineActorSheet extends foundry.appv1.sheets.ActorSheet { }, default: "roll", close: () => resolve(null) - }).render(true); + }, { classes: ["slazero2", "dialog"] }).render(true); }); if (!config) return; @@ -2665,6 +2668,11 @@ class ZeroEngineActorSheet extends foundry.appv1.sheets.ActorSheet { modifiedDice = rollOptions.preModifiedDice; } + // Costs (e.g. Flux) are only paid once the roll is confirmed + if (typeof rollOptions.onConfirm === "function") { + await rollOptions.onConfirm(); + } + const normalDice = Math.max(0, parseInt(modifiedDice) || 0); const stressDice = this._getStressValue(this.actor); @@ -3622,11 +3630,39 @@ class ZeroEngineActorSheet extends foundry.appv1.sheets.ActorSheet { const criticals = { "1.1": { name: "Shaken", lethal: false, effects: "+1 Stress; -1 die to all rolls for 1 round.", recovery: "1 stretch" }, "1.2": { name: "Tunnel vision", lethal: false, effects: "-2 dice to Observation; you miss side details.", recovery: "1 shift" }, - "2.1": { name: "Anxiety spiral", lethal: false, effects: "+1 Stress each shift until safe rest stretch.", recovery: "D6 shifts" }, - "3.1": { name: "Rage trigger", lethal: false, effects: "When harmed/insulted: pass Resolve check or retaliate next action.", recovery: "2D6 days" }, - "4.1": { name: "Major breakdown", lethal: false, effects: "Broken for D6 rounds; afterward -2 dice to mental rolls for 1 day.", recovery: "D6 days" }, - "5.1": { name: "Permanent phobia", lethal: false, effects: "As Phobia but only therapy/arc resolution removes it.", recovery: "Permanent until treated" }, - "6.1": { name: "Heart seizure", lethal: true, timeLimit: "1 shift", effects: "Each shift make Stamina; fail = death.", recovery: "—" }, + "1.3": { name: "Jitters", lethal: false, effects: "-1 die to AGI rolls until you rest a stretch.", recovery: "1 stretch" }, + "1.4": { name: "Cold sweat", lethal: false, effects: "+1 Stress. -1 die on your next roll.", recovery: "1 stretch" }, + "1.5": { name: "Freeze response", lethal: false, effects: "Lose your next action. May defend normally.", recovery: "1 round" }, + "1.6": { name: "Camera fright", lethal: false, effects: "-2 dice while you know you are being filmed (on Mort: usually).", recovery: "1 shift" }, + "2.1": { name: "Anxiety spiral", lethal: false, effects: "+1 Stress each shift until a safe rest stretch.", recovery: "D6 shifts" }, + "2.2": { name: "Intrusive flashback", lethal: false, effects: "First action each scene is at -2 dice.", recovery: "D6 shifts" }, + "2.3": { name: "Hypervigilance", lethal: false, effects: "Cannot rest to recover Stress; +1 Observation die (wired).", recovery: "D6 shifts" }, + "2.4": { name: "Night terrors", lethal: false, effects: "Shift rest recovers no Resolve until treated.", recovery: "D6 shifts" }, + "2.5": { name: "Paranoia", lethal: false, effects: "Treat every stranger as hostile; Persuasion rolls -2 dice.", recovery: "D6 shifts" }, + "2.6": { name: "Compulsive ritual", lethal: false, effects: "Must complete your ritual before acting each scene or take +1 Stress.", recovery: "D6 shifts" }, + "3.1": { name: "Rage trigger", lethal: false, effects: "When harmed or insulted: pass a Resolve save or retaliate next action.", recovery: "2D6 days" }, + "3.2": { name: "Phobia (confined)", lethal: false, effects: "-2 dice while in tight spaces, tunnels, elevators.", recovery: "2D6 days" }, + "3.3": { name: "Phobia (crowds)", lethal: false, effects: "-2 dice while pressed in a crowd; +1 Stress whenever you enter one.", recovery: "2D6 days" }, + "3.4": { name: "Dissociation", lethal: false, effects: "On a failed roll, pass a Resolve save or lose your next action staring through the world.", recovery: "2D6 days" }, + "3.5": { name: "Mutism", lethal: false, effects: "Cannot speak under pressure. Persuasion impossible; comms text-only.", recovery: "2D6 days" }, + "3.6": { name: "Substance reliance", lethal: false, effects: "As Frother Drug Dependency: -1 die to all rolls with no drug active.", recovery: "2D6 days" }, + "4.1": { name: "Major breakdown", lethal: false, effects: "Broken for D6 rounds; afterwards -2 dice to mental rolls for 1 day.", recovery: "D6 days" }, + "4.2": { name: "Berserk fugue", lethal: false, effects: "Attack the nearest creature for D3 rounds — ally or enemy.", recovery: "D6 days" }, + "4.3": { name: "Catatonia", lethal: false, effects: "Incapacitated D6 rounds; wake at 0 Stress.", recovery: "D6 days" }, + "4.4": { name: "Identity confusion", lethal: false, effects: "Forget who you work for until rested. GM may redirect one action per scene.", recovery: "D6 days" }, + "4.5": { name: "Hallucinations", lethal: false, effects: "GM narrates one false detail per scene; you believe it.", recovery: "D6 days" }, + "4.6": { name: "Amnesia (recent)", lethal: false, effects: "Lose all memory of this shift, including the mission briefing.", recovery: "D6 days" }, + "5.1": { name: "Permanent phobia", lethal: false, effects: "As Phobia, but only therapy or story resolution removes it.", recovery: "Permanent until treated" }, + "5.2": { name: "Permanent tremor", lethal: false, effects: "-1 die to fine-motor tasks (Crafting, Healing, long-range shots). Permanent until treated.", recovery: "Permanent until treated" }, + "5.3": { name: "Personality fracture", lethal: false, effects: "A second persona surfaces under Stress 5+; GM plays it for one action per scene.", recovery: "Permanent until treated" }, + "5.4": { name: "Deathwish", lethal: false, effects: "You cannot voluntarily Seek Cover or retreat; Showing Off costs no dice (you no longer care).", recovery: "Permanent until treated" }, + "5.5": { name: "Total burnout", lethal: false, effects: "Resolve max -1 until treated. LAD premiums double (flagged unstable).", recovery: "Permanent until treated" }, + "5.6": { name: "Ebb-touched", lethal: false, effects: "You hear the Ebb. Non-Ebb users take +1 Stress in the presence of active formulae.", recovery: "Permanent until treated" }, + "6.1": { name: "Heart seizure", lethal: true, timeLimit: "1 shift", effects: "Each shift make a Stamina roll; failure = death.", recovery: "—" }, + "6.2": { name: "Stroke", lethal: true, timeLimit: "1 shift", effects: "-2 dice to everything. Each shift make a Stamina roll; failure = death.", recovery: "—" }, + "6.3": { name: "Psychotic break", lethal: true, timeLimit: "1 day", effects: "GM controls the character. Medical intervention within a day or they are gone.", recovery: "—" }, + "6.4": { name: "Aneurysm", lethal: true, timeLimit: "1 stretch", effects: "Collapse. Surgery within a stretch or death.", recovery: "—" }, + "6.5": { name: "Total psychic collapse", lethal: true, timeLimit: "1 round", effects: "Resolve locked at 0. Stabilise (Healing, 2 successes) within a round or death.", recovery: "—" }, "6.6": { name: "Heart stops", lethal: true, timeLimit: "Instant", effects: "You die immediately.", recovery: "—" } }; @@ -3730,18 +3766,42 @@ class ZeroEngineActorSheet extends foundry.appv1.sheets.ActorSheet { _getPhysicalCritical(key) { // Simplified lookup - in production, load from tables-data.json const criticals = { - "1.1": { name: "Winded", lethal: false, effects: "Lose next Fast action; -1 die to physical rolls for 1 round.", healing: "1 stretch" }, - "1.2": { name: "Sprained wrist", lethal: false, effects: "-2 dice to one-handed actions with that hand.", healing: "D6 shifts" }, - "2.1": { name: "Twisted ankle", lethal: false, effects: "-2 dice to Mobility; half movement speed.", healing: "D6 shifts" }, - "2.2": { name: "Concussion", lethal: false, effects: "-2 dice to Observation & Wits rolls; cannot Push.", healing: "D6 days" }, - "3.1": { name: "Broken ribs", lethal: false, effects: "-2 dice to Stamina; Slow actions cause 1 damage.", healing: "2D6 days" }, - "3.2": { name: "Deep laceration", lethal: false, effects: "-1 die to all physical rolls; 1 damage per shift unhealed.", healing: "D6 days" }, - "4.1": { name: "Punctured lung", lethal: true, timeLimit: "1 shift", effects: "-2 dice to Stamina & Mobility; each round exert = 1 damage.", healing: "D6 days" }, - "4.2": { name: "Broken arm", lethal: false, effects: "Cannot use that arm for any action.", healing: "2D6 days" }, - "5.1": { name: "Ruptured jugular", lethal: true, timeLimit: "1 round", effects: "Broken; -1 Health per round until stabilized.", healing: "D6 days after stabilized" }, - "5.2": { name: "Internal bleeding", lethal: true, timeLimit: "1 shift", effects: "-1 Health per shift until stabilized.", healing: "2D6 days" }, - "6.1": { name: "Crushed skull", lethal: true, timeLimit: "Instant", effects: "You die immediately.", healing: "—" }, - "6.6": { name: "Catastrophic destruction", lethal: true, timeLimit: "Instant", effects: "You die immediately.", healing: "—" } + "1.1": { name: "Winded", lethal: false, effects: "-1 die to all rolls; clears end of round.", healing: "1 round" }, + "1.2": { name: "Bruised ribs", lethal: false, effects: "-1 die to STR rolls.", healing: "1 shift" }, + "1.3": { name: "Bloody nose", lethal: false, effects: "Cosmetic and humiliating. -1 Persuasion until cleaned up.", healing: "1 stretch" }, + "1.4": { name: "Sprained wrist", lethal: false, effects: "-1 die with two-handed weapons.", healing: "1 shift" }, + "1.5": { name: "Shallow cut", lethal: false, effects: "Bleeding until bandaged (any healing action).", healing: "1 stretch" }, + "1.6": { name: "Deep graze", lethal: false, effects: "Bleeding. -1 die on AGI rolls until treated.", healing: "1 shift" }, + "2.1": { name: "Concussion (mild)", lethal: false, effects: "-1 die to WIT rolls.", healing: "D6 shifts" }, + "2.2": { name: "Fractured hand", lethal: false, effects: "Broken Arm effects until splinted.", healing: "D6 shifts" }, + "2.3": { name: "Gashed thigh", lethal: false, effects: "Bleeding. Movement halved until treated.", healing: "D6 shifts" }, + "2.4": { name: "Broken arm", lethal: false, effects: "Arm unusable: -2 Melee, -1 Marksmanship, -1 Force.", healing: "D6 days" }, + "2.5": { name: "Torn shoulder", lethal: false, effects: "Bleeding. -1 die on all STR and AGI rolls.", healing: "D6 shifts" }, + "2.6": { name: "Lacerated back", lethal: false, effects: "Bleeding. Cannot wear rigid armour until treated.", healing: "D6 shifts" }, + "3.1": { name: "Broken leg", lethal: false, effects: "Cannot run: -2 Mobility, -1 Stealth.", healing: "2D6 days" }, + "3.2": { name: "Shattered kneecap", lethal: false, effects: "Broken Leg effects; -1 die on Mobility permanently until surgery.", healing: "2D6 days" }, + "3.3": { name: "Concussion (severe)", lethal: false, effects: "Concussed: -1 all dice, extra -1 Observation/WIT.", healing: "2D6 days" }, + "3.4": { name: "Crushed fingers", lethal: false, effects: "Broken Arm effects. Fine work (Crafting, Healing) at -2 dice.", healing: "2D6 days" }, + "3.5": { name: "Punctured side", lethal: false, effects: "Bleeding + Gut Wound (-2 all dice) until stabilised.", healing: "2D6 days" }, + "3.6": { name: "Ruptured gut", lethal: false, effects: "Gut Wound. Cannot benefit from drugs until treated.", healing: "2D6 days" }, + "4.1": { name: "Sucking chest wound", lethal: false, effects: "Bleeding + Suppressed until stabilised; Stamina rolls at -2.", healing: "D6 weeks" }, + "4.2": { name: "Shattered forearm", lethal: false, effects: "Broken Arm. Weapon dropped. Surgery required to fully heal.", healing: "D6 weeks" }, + "4.3": { name: "Severed artery (arm)", lethal: false, effects: "Bleeding — lose 2 HP per turn instead of 1 until treated.", healing: "D6 weeks" }, + "4.4": { name: "Perforated bowel", lethal: false, effects: "Gut Wound. Each shift untreated: Stamina roll or lose 1 HP.", healing: "D6 weeks" }, + "4.5": { name: "Skull fracture", lethal: false, effects: "Bleeding + Concussed. KO'd on any further head hit.", healing: "D6 weeks" }, + "4.6": { name: "Collapsed lung", lethal: false, effects: "Bleeding. Stamina and Mobility at -2 dice.", healing: "D6 weeks" }, + "5.1": { name: "Internal bleeding", lethal: true, timeLimit: "1 day", effects: "Bleeding (hidden). Each shift: Stamina roll or lose 1 HP. Surgery ends it.", healing: "D6 weeks" }, + "5.2": { name: "Crushed pelvis", lethal: true, timeLimit: "1 day", effects: "Bleeding. Cannot stand. Surgery within a day or death.", healing: "2D6 weeks" }, + "5.3": { name: "Deep torso trauma", lethal: true, timeLimit: "1 shift", effects: "Bleeding. Stabilise (Healing, 2 successes) within a shift or death.", healing: "2D6 weeks" }, + "5.4": { name: "Severed hand", lethal: false, effects: "Hand gone. Bleeding until cauterised. Cybernetics exist — for a price.", healing: "Permanent (prosthetic)" }, + "5.5": { name: "Spinal damage", lethal: false, effects: "Stunned, then legs numb: Mobility 0 until surgery.", healing: "Permanent until surgery" }, + "5.6": { name: "Throat wound", lethal: true, timeLimit: "1 stretch", effects: "Bleeding + cannot speak. Stabilise within a stretch or death.", healing: "2D6 weeks" }, + "6.1": { name: "Severed artery (neck)", lethal: true, timeLimit: "1 round", effects: "Stabilise (Healing, 2 successes) within a round or death.", healing: "—" }, + "6.2": { name: "Pierced heart", lethal: true, timeLimit: "1 round", effects: "Death next round unless a medic reaches you immediately.", healing: "—" }, + "6.3": { name: "Crushed skull", lethal: true, timeLimit: "Instant", effects: "You die immediately. LAD collects the remains.", healing: "—" }, + "6.4": { name: "Torn aorta", lethal: true, timeLimit: "Instant", effects: "You die immediately.", healing: "—" }, + "6.5": { name: "Decapitating blow", lethal: true, timeLimit: "Instant", effects: "You die immediately. Closed-casket sponsorship footage.", healing: "—" }, + "6.6": { name: "Obliterated", lethal: true, timeLimit: "Instant", effects: "You die immediately. There is not enough left for LAD.", healing: "—" } }; return criticals[key] || { name: "Winded", lethal: false, effects: "Lose next Fast action; -1 die to physical rolls for 1 round.", healing: "1 stretch" }; @@ -4854,6 +4914,7 @@ Hooks.on("updateActor", async (actor, change, options) => { // ─── RACE DATA ─────────────────────────────────────────────────────────────── const SLA_RACES = { human: { + art: 'systems/slazero2/assets/SLA_Assets/Species/Human.png', label: 'Human', icon: '👤', flavor: 'The backbone of SLA Industries. Born, raised, and usually died on Mort. Adaptable, expendable, and everywhere.', baseAttrs: { strength: 2, agility: 2, wits: 2, empathy: 2 }, @@ -4863,6 +4924,7 @@ const SLA_RACES = { isEbbUser: false, healthMod: 0, resolveMod: 0 }, ebon: { + art: 'systems/slazero2/assets/SLA_Assets/Species/Ebon.png', label: 'Ebon', icon: '🔮', flavor: 'Born from the Ebb itself. Their minds reach where others cannot. SLA prizes them — and fears what they might become.', baseAttrs: { strength: 2, agility: 2, wits: 3, empathy: 3 }, @@ -4872,6 +4934,7 @@ const SLA_RACES = { isEbbUser: true, healthMod: 0, resolveMod: 1 }, brainwaster: { + art: 'systems/slazero2/assets/SLA_Assets/Species/Brain Waster.png', label: 'Brain Waster', icon: '🧠', flavor: 'Ebon mutation gone raw. The Ebb tears through them and takes pieces every time. Brilliantly, terrifyingly unstable.', baseAttrs: { strength: 2, agility: 2, wits: 4, empathy: 3 }, @@ -4881,6 +4944,7 @@ const SLA_RACES = { isEbbUser: true, healthMod: 0, resolveMod: 0 }, stormer: { + art: 'systems/slazero2/assets/SLA_Assets/Species/Stormer 313.png', label: 'Stormer (313-S Malice)', icon: '⚡', flavor: 'Vatgrown for war. A Stormer does not stop until the objective is complete — or it is dead. Usually the objective.', baseAttrs: { strength: 4, agility: 3, wits: 2, empathy: 2 }, @@ -4890,6 +4954,7 @@ const SLA_RACES = { isEbbUser: false, healthMod: 2, resolveMod: 0 }, shaktar: { + art: 'systems/slazero2/assets/SLA_Assets/Species/Shaktar.png', label: 'Shaktar', icon: '🦂', flavor: 'Alien warrior caste. Honour is their law; violence is their art. Each kill is recorded. Each debt is paid.', baseAttrs: { strength: 3, agility: 3, wits: 2, empathy: 2 }, @@ -4899,6 +4964,7 @@ const SLA_RACES = { isEbbUser: false, healthMod: 1, resolveMod: 0 }, wraithraider: { + art: 'systems/slazero2/assets/SLA_Assets/Species/Wraith Raider.png', label: 'Wraith Raider', icon: '👁️', flavor: 'They move through walls of shadow and kill without a sound. You never see them first. If you see them at all.', baseAttrs: { strength: 2, agility: 4, wits: 2, empathy: 2 }, @@ -4908,6 +4974,7 @@ const SLA_RACES = { isEbbUser: false, healthMod: 0, resolveMod: 1 }, frother: { + art: 'systems/slazero2/assets/SLA_Assets/Species/Frother.png', label: 'Frother', icon: '💊', flavor: 'The battle chemicals make them something else entirely. Pain is irrelevant. Fear is gone. Only the job remains.', baseAttrs: { strength: 4, agility: 2, wits: 2, empathy: 2 }, @@ -4917,6 +4984,7 @@ const SLA_RACES = { isEbbUser: false, healthMod: 1, resolveMod: -1 }, vevaphon: { + art: 'systems/slazero2/assets/SLA_Assets/Species/Stormer 711.png', label: 'Stormer Vevaphon', icon: '🔀', flavor: "Sleight Industries' cancelled polymorphic bioweapon line. Shape-shifting combat chassis with three selectable morph forms. Every shift costs Instability — and enough shifts will shatter whoever they were.", @@ -5628,7 +5696,7 @@ class SLAPCCreator extends Application { _buildHTML() { const raceCards = Object.entries(SLA_RACES).map(([key, r]) => `