Skip to content

Commit

Permalink
Save after save fixes (#61)
Browse files Browse the repository at this point in the history
## Description

- GHB2020 introduced a rule change where only a single save after save roll can be used (closes #60)
    - Instead of letting the modifiers stack, it should pick the best one for the situation
  • Loading branch information
damonhook authored Sep 17, 2020
1 parent bf2c2bf commit fc9fa1a
Show file tree
Hide file tree
Showing 8 changed files with 44 additions and 29 deletions.
6 changes: 4 additions & 2 deletions api/models/target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ class Target {
}

resolveMortalSave(profile: WeaponProfile): number {
return this.resolveChainedModifier(profile, t.TARGET_MORTAL_NEGATE);
const mod = this.modifiers.getSaveAfterSaveModifier(true);
return mod ? mod.resolve(profile, this) : 0;
}

resolveFNP(profile: WeaponProfile): number {
return this.resolveChainedModifier(profile, t.TARGET_FNP);
const mod = this.modifiers.getSaveAfterSaveModifier(false);
return mod ? mod.resolve(profile, this) : 0;
}

resolveModifier(profile: WeaponProfile, modifier: typeof BaseTargetModifier): number {
Expand Down
26 changes: 24 additions & 2 deletions api/models/targetModifiers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ export class TargetModifierManager {
}

/**
* Fetch the most prominent reroll modifier from the list of managed modifiers based on its
* characteristic property
* Fetch the most prominent reroll modifier from the list of managed modifiers
*/
getRerollModifier() {
return (
Expand All @@ -66,6 +65,29 @@ export class TargetModifierManager {
);
}

getSaveAfterSaveModifier(mortalWounds: boolean): TargetFeelNoPain | TargetMortalNegate | null {
let maxModifier: TargetFeelNoPain | TargetMortalNegate | null = null;
const fnpModifiers = this.modifiers.filter((m) => m instanceof TargetFeelNoPain) as TargetFeelNoPain[];
if (fnpModifiers?.length) {
maxModifier = fnpModifiers.reduce<TargetFeelNoPain | null>(
(max, m) => (max === null || m.on < max.on ? m : max),
maxModifier,
);
}
if (mortalWounds) {
const mwModifiers = this.modifiers.filter(
(m) => m instanceof TargetMortalNegate,
) as TargetMortalNegate[];
if (mwModifiers) {
maxModifier = mwModifiers.reduce<TargetFeelNoPain | TargetMortalNegate | null>(
(max, m) => (max === null || m.on < max.on ? m : max),
maxModifier,
);
}
}
return maxModifier;
}

/**
* Fetch a list of stackable modifiers from the list of managed modifiers based
* on their class definition and characteristic property
Expand Down
2 changes: 0 additions & 2 deletions api/processors/averageDamageProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export default class AverageDamageProcessor {
const mortalHits = attacks * mwModifier.resolve(this.profile);
mortalDamage += mortalHits * mwModifier.getMortalWounds();
mortalDamage -= mortalDamage * this.target.resolveMortalSave(this.profile);
mortalDamage -= mortalDamage * this.target.resolveFNP(this.profile);
hits -= !mwModifier.inAddition ? mortalHits : 0;
}

Expand Down Expand Up @@ -78,7 +77,6 @@ export default class AverageDamageProcessor {
const mortalToWounds = hits * mwModifier.resolve(this.profile);
mortalDamage += mortalToWounds * mwModifier.getMortalWounds();
mortalDamage -= mortalDamage * this.target.resolveMortalSave(this.profile);
mortalDamage -= mortalDamage * this.target.resolveFNP(this.profile);
wounds -= !mwModifier.inAddition ? mortalToWounds : 0;
}

Expand Down
23 changes: 8 additions & 15 deletions api/processors/simulationProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Characteristic as C } from '../constants';
import { D6 } from '../models/dice';
import { MODIFIERS as m } from '../models/modifiers';
import type Target from '../models/target';
import { TARGET_MODIFIERS as t } from '../models/targetModifiers';
import type WeaponProfile from '../models/weaponProfile';

class SimulationProcessor {
Expand Down Expand Up @@ -139,24 +138,18 @@ class SimulationProcessor {
return roll;
}

performMortalSaveRolls(damage) {
const mortalModifiers = this.target.modifiers.getStackableModifier(t.TARGET_MORTAL_NEGATE);
if (mortalModifiers && mortalModifiers.length) {
return [...Array(damage)].reduce(
(acc) => (mortalModifiers.some((mod) => D6.roll() >= mod.on) ? acc : acc + 1),
0,
);
performMortalSaveRolls(damage: number): number {
const mod = this.target.modifiers.getSaveAfterSaveModifier(true);
if (mod) {
return [...Array(damage)].reduce((acc) => (D6.roll() >= mod.on ? acc : acc + 1), 0);
}
return damage;
}

performFNPRolls(damage) {
const fnpModifiers = this.target.modifiers.getStackableModifier(t.TARGET_FNP);
if (fnpModifiers && fnpModifiers.length) {
return [...Array(damage)].reduce(
(acc) => (fnpModifiers.some((mod) => D6.roll() >= mod.on) ? acc : acc + 1),
0,
);
performFNPRolls(damage: number): number {
const mod = this.target.modifiers.getSaveAfterSaveModifier(false);
if (mod) {
return [...Array(damage)].reduce((acc) => (D6.roll() >= mod.on ? acc : acc + 1), 0);
}
return damage;
}
Expand Down
4 changes: 2 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "statshammer",
"version": "2.1.3",
"version": "2.1.4",
"private": true,
"proxy": "http://localhost:5000/",
"dependencies": {
Expand All @@ -11,7 +11,7 @@
"@reduxjs/toolkit": "^1.2.1",
"@sandstreamdev/react-swipeable-list": "^0.5.0",
"@types/jspdf": "^1.3.3",
"@types/lodash": "^4.14.160",
"@types/lodash": "^4.14.161",
"@types/nanoid": "^2.1.0",
"@types/react": "^16.9.17",
"@types/react-dom": "^16.9.4",
Expand Down
8 changes: 4 additions & 4 deletions client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1590,10 +1590,10 @@
resolved "https://registry.yarnpkg.com/@types/jspdf/-/jspdf-1.3.3.tgz#6940e892da69fdbe0969b742c6bdd9e4c5da320b"
integrity sha512-DqwyAKpVuv+7DniCp2Deq1xGvfdnKSNgl9Agun2w6dFvR5UKamiv4VfYUgcypd8S9ojUyARFIlZqBrYrBMQlew==

"@types/lodash@^4.14.160":
version "4.14.160"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.160.tgz#2f1bba6500bc3cb9a732c6d66a083378fb0b0b29"
integrity sha512-aP03BShJoO+WVndoVj/WNcB/YBPt+CIU1mvaao2GRAHy2yg4pT/XS4XnVHEQBjPJGycWf/9seKEO9vopTJGkvA==
"@types/lodash@^4.14.161":
version "4.14.161"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18"
integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA==

"@types/minimatch@*":
version "3.0.3"
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"*",
"."
],
"version": "2.1.3",
"version": "2.1.4",
"npmClient": "yarn"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "statshammer-express",
"private": true,
"version": "2.1.3",
"version": "2.1.4",
"engines": {
"node": "12.18.3",
"yarn": "1.22.4"
Expand Down

0 comments on commit fc9fa1a

Please sign in to comment.