More rules

This commit is contained in:
WBHarry 2025-08-04 13:31:53 +02:00
parent e53b0a8a73
commit 323fff73cd
10 changed files with 194 additions and 33 deletions

View file

@ -318,6 +318,7 @@
"DamageReduction": {
"armorMarks": "Armor Marks",
"armorWithStress": "Spend 1 stress to use an extra mark",
"thresholdImmunities": "Threshold Immunities",
"stress": "Stress",
"stressReduction": "Reduce By Stress",
"title": "Damage Reduction",
@ -952,6 +953,12 @@
"name": "Dice Set"
}
},
"RuleChoice": {
"off": "Off",
"offWithToggle": "Off With Toggle",
"on": "On",
"onWithToggle": "On With Toggle"
},
"SelectAction": {
"selectType": "Select Action Type",
"selectAction": "Action Selection"
@ -1662,7 +1669,8 @@
"major": "Major",
"severe": "Severe",
"majorThreshold": "Major Damage Threshold",
"severeThreshold": "Severe Damage Threshold"
"severeThreshold": "Severe Damage Threshold",
"with": "{threshold} Damage Threshold"
},
"Dice": {
"single": "Die",
@ -1772,6 +1780,10 @@
"hint": "If this value is set you can use up to that much stress to spend additional Armor Marks beyond your normal maximum."
},
"stress": {
"any": {
"label": "Stress Damage Reduction: Any",
"hint": "The cost in stress you can pay to reduce incoming damage down one threshold"
},
"severe": {
"label": "Stress Damage Reduction: Severe",
"hint": "The cost in stress you can pay to reduce severe damage down to major."
@ -1850,6 +1862,7 @@
},
"actorName": "Actor Name",
"amount": "Amount",
"any": "Any",
"armorScore": "Armor Score",
"activeEffects": "Active Effects",
"armorSlots": "Armor Slots",
@ -2059,6 +2072,10 @@
"hint": "Automatically increase the GM's fear pool on a fear duality roll result."
},
"FIELDS": {
"damageReductionRulesDefault": {
"label": "Damage Reduction Rules Default",
"hint": "Wether using armor and reductions has rules on by default"
},
"hopeFear": {
"label": "Hope & Fear",
"gm": { "label": "GM" },
@ -2312,7 +2329,9 @@
"appliedEvenIfSuccessful": "Applied even if save succeeded",
"diceIsRerolled": "The dice has been rerolled (x{times})",
"pendingSaves": "Pending Reaction Rolls",
"openSheetSettings": "Open Settings"
"openSheetSettings": "Open Settings",
"rulesOn": "Rules On",
"rulesOff": "Rules Off"
}
}
}

View file

@ -10,14 +10,18 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
this.reject = reject;
this.actor = actor;
this.damage = damage;
this.rulesDefault = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Automation
).damageReductionRulesDefault;
this.rulesOn = [CONFIG.DH.GENERAL.ruleChoice.on.id, CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id].includes(
this.rulesDefault
);
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
const maxArmorMarks = canApplyArmor
? Math.min(
actor.system.armorScore - actor.system.armor.system.marks.value,
actor.system.rules.damageReduction.maxArmorMarked.value
)
: 0;
const availableArmor = actor.system.armorScore - actor.system.armor.system.marks.value;
const maxArmorMarks = canApplyArmor ? availableArmor : 0;
const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => {
acc[foundry.utils.randomID()] = { selected: false };
@ -42,6 +46,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
acc[damage] = {
cost: dr.cost,
selected: false,
any: key === 'any',
from: getDamageLabel(damage),
to: getDamageLabel(damage - 1)
};
@ -51,16 +56,28 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
},
null
);
this.thresholdImmunities = Object.keys(actor.system.rules.damageReduction.thresholdImmunities).reduce(
(acc, key) => {
if (actor.system.rules.damageReduction.thresholdImmunities[key])
acc[damageKeyToNumber(key)] = game.i18n.format(`DAGGERHEART.GENERAL.DamageThresholds.with`, {
threshold: game.i18n.localize(`DAGGERHEART.GENERAL.DamageThresholds.${key}`)
});
return acc;
},
{}
);
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'views', 'damage-reduction'],
position: {
width: 240,
width: 280,
height: 'auto'
},
actions: {
toggleRules: this.toggleRules,
setMarks: this.setMarks,
useStressReduction: this.useStressReduction,
takeDamage: this.takeDamage
@ -89,6 +106,12 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.rulesOn = this.rulesOn;
context.rulesToggleable = [
CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id,
CONFIG.DH.GENERAL.ruleChoice.offWithToggle.id
].includes(this.rulesDefault);
context.thresholdImmunities = this.thresholdImmunities;
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } =
this.getDamageInfo();
@ -110,12 +133,22 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
}
: null;
context.marks = this.marks;
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
context.marks = {
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => {
const mark = this.marks.armor[key];
if (!this.rulesOn || index + 1 <= maxArmor) acc[key] = mark;
return acc;
}, {}),
stress: this.marks.stress
};
context.availableStressReductions = this.availableStressReductions;
context.damage = getDamageLabel(this.damage);
context.reducedDamage = currentDamage !== this.damage ? getDamageLabel(currentDamage) : null;
context.currentDamage = context.reducedDamage ?? context.damage;
context.currentDamageNr = currentDamage;
return context;
}
@ -136,22 +169,48 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
const armorMarkReduction =
selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark;
const currentDamage = this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length;
let currentDamage = Math.max(
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
0
);
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage };
};
static toggleRules() {
this.rulesOn = !this.rulesOn;
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
this.marks = {
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => {
const mark = this.marks.armor[key];
const keepSelectValue = !this.rulesOn || index + 1 <= maxArmor;
acc[key] = { ...mark, selected: keepSelectValue ? mark.selected : false };
return acc;
}, {}),
stress: this.marks.stress
};
this.render();
}
static setMarks(_, target) {
const currentMark = this.marks[target.dataset.type][target.dataset.key];
const { selectedStressMarks, stressReductions, currentMarks, currentDamage } = this.getDamageInfo();
if (!currentMark.selected && currentDamage === 0) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.damageAlreadyNone'));
return;
}
if (!currentMark.selected && currentMarks === this.actor.system.armorScore) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks'));
return;
if (this.rulesOn) {
if (!currentMark.selected && currentMarks === this.actor.system.armorScore) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks'));
return;
}
}
if (currentMark.selected) {

View file

@ -2,6 +2,25 @@ export const compendiumJournals = {
welcome: 'Compendium.daggerheart.journals.JournalEntry.g7NhKvwltwafmMyR'
};
export const ruleChoice = {
on: {
id: 'on',
label: 'DAGGERHEART.CONFIG.RuleChoice.on'
},
of: {
id: 'off',
label: 'DAGGERHEART.CONFIG.RuleChoice.off'
},
onWithToggle: {
id: 'onWithToggle',
label: 'DAGGERHEART.CONFIG.RuleChoice.onWithToggle'
},
offWithToggle: {
id: 'offWithToggle',
label: 'DAGGERHEART.CONFIG.RuleChoice.offWithToggle'
}
};
export const range = {
self: {
id: 'self',

View file

@ -239,7 +239,8 @@ export default class DhCharacter extends BaseDataActor {
stressDamageReduction: new fields.SchemaField({
severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor')
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
}),
increasePerArmorMark: new fields.NumberField({
integer: true,
@ -248,7 +249,11 @@ export default class DhCharacter extends BaseDataActor {
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
}),
magical: new fields.BooleanField({ initial: false }),
physical: new fields.BooleanField({ initial: false })
physical: new fields.BooleanField({ initial: false }),
thresholdImmunities: new fields.SchemaField({
minor: new fields.BooleanField({ initial: false })
}),
disabledArmor: new fields.BooleanField({ intial: false })
}),
attack: new fields.SchemaField({
damage: new fields.SchemaField({

View file

@ -34,6 +34,12 @@ export default class DhAutomation extends foundry.abstract.DataModel {
initial: true,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.effects.rangeDependent.label'
})
}),
damageReductionRulesDefault: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.ruleChoice,
initial: CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.damageReductionRulesDefault.label'
})
};
}

View file

@ -464,14 +464,17 @@ export default class DhpActor extends Actor {
}
#canReduceDamage(hpDamage, type) {
const { stressDamageReduction, disabledArmor } = this.system.rules.damageReduction;
if (disabledArmor) return false;
const availableStress = this.system.resources.stress.max - this.system.resources.stress.value;
const canUseArmor =
this.system.armor &&
this.system.armor.system.marks.value < this.system.armorScore &&
type.every(t => this.system.armorApplicableDamageTypes[t] === true);
const canUseStress = Object.keys(this.system.rules.damageReduction.stressDamageReduction).reduce((acc, x) => {
const rule = this.system.rules.damageReduction.stressDamageReduction[x];
const canUseStress = Object.keys(stressDamageReduction).reduce((acc, x) => {
const rule = stressDamageReduction[x];
if (damageKeyToNumber(x) <= hpDamage) return acc || (rule.enabled && availableStress >= rule.cost);
return acc;
}, false);

View file

@ -2,11 +2,35 @@
.daggerheart.views.damage-reduction {
.damage-reduction-container {
position: relative;
padding: 8px 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
.rules-button {
position: absolute;
top: 4px;
right: 4px;
border-radius: 50%;
&.inactive {
opacity: 0.4;
::after {
position: absolute;
content: '/';
color: red;
font-weight: 700;
font-size: 1.8em;
left: 5px;
top: 0;
rotate: 13deg;
}
}
}
.section-container {
display: flex;
flex-direction: column;
@ -44,7 +68,7 @@
.mark-selection-inner {
display: flex;
gap: 2px;
gap: 8px;
.mark-container {
cursor: pointer;
@ -58,10 +82,6 @@
justify-content: center;
opacity: 0.4;
&:not(:last-child) {
margin-right: 8px;
}
&.selected {
opacity: 1;
}
@ -79,11 +99,11 @@
}
}
.stress-reduction-container {
.chip-container {
margin: 0;
width: 100%;
.stress-reduction {
.chip-inner-container {
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
height: 26px;
@ -113,6 +133,14 @@
}
}
.threshold-label {
opacity: 0.6;
&.active {
opacity: 1;
}
}
.markers-subtitle {
margin: -4px 0 0 0;

View file

@ -2,6 +2,6 @@
.daggerheart.views.damage-reduction {
.window-content {
padding: 8px 0;
padding: 0;
}
}

View file

@ -1,4 +1,9 @@
<div class="damage-reduction-container">
{{#if rulesToggleable}}
<button type="button" class="rules-button {{#unless rulesOn}}inactive{{/unless}}" data-action="toggleRules" data-tooltip-text="{{#if rulesOn}}{{localize "DAGGERHEART.UI.Tooltip.rulesOn"}}{{else}}{{localize "DAGGERHEART.UI.Tooltip.rulesOff"}}{{/if}}">
<i class="fa-solid fa-book"></i>
</button>
{{/if}}
<div class="section-container padded">
<div class="resources-container">
<div class="resource-container">
@ -25,8 +30,6 @@
<i class="fa-solid fa-shield"></i>
</div>
{{/each}}
</div>
<div class="mark-selection-inner">
{{#each marks.stress}}
<div
class="mark-container {{#if this.selected}}selected{{/if}} {{#if (not @root.basicMarksUsed)}}inactive{{/if}}"
@ -40,19 +43,25 @@
<div class="markers-subtitle bold">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.usedMarks"}}</div>
</div>
{{#if availableStressReductions}}
<div class="resources-container">
<div class="resource-container">
<h4 class="armor-title">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.stressReduction"}}</h4>
</div>
</div>
{{/if}}
{{#each availableStressReductions}}
<div class="section-container">
<h4 class="stress-reduction-container divider">
<div class="stress-reduction {{#if (eq this.from @root.currentDamage)}}active{{/if}} {{#if this.selected}}selected{{/if}}" data-action="useStressReduction" data-reduction="{{@key}}">
{{this.from}}
<i class="fa-solid fa-arrow-right-long"></i>
{{this.to}}
<h4 class="chip-container divider">
<div class="chip-inner-container selectable {{#if (or this.any (eq this.from @root.currentDamage))}}active{{/if}} {{#if this.selected}}selected{{/if}}" data-action="useStressReduction" data-reduction="{{@key}}">
{{#if this.any}}
{{localize "DAGGERHEART.GENERAL.any"}}
{{else}}
{{this.from}}
<i class="fa-solid fa-arrow-right-long"></i>
{{this.to}}
{{/if}}
<div class="stress-reduction-cost">
{{this.cost}}
<i class="fa-solid fa-bolt"></i>
@ -62,6 +71,18 @@
</div>
{{/each}}
{{#if thresholdImmunities}}
<div class="resources-container">
<div class="resource-container">
<h4 class="armor-title">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.thresholdImmunities"}}</h4>
</div>
</div>
{{/if}}
{{#each thresholdImmunities as | immunity key |}}
<div class="threshold-label {{#if (gte key @root.currentDamageNr)}}active{{/if}}">{{immunity}}</div>
{{/each}}
<footer class="padded">
<button type="button" data-action="takeDamage">
{{localize "Take"}}

View file

@ -12,6 +12,7 @@
{{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}}
{{formGroup settingFields.schema.fields.effects.fields.rangeDependent value=settingFields._source.effects.rangeDependent localize=true}}
{{formGroup settingFields.schema.fields.levelupAuto value=settingFields._source.levelupAuto localize=true}}
{{formGroup settingFields.schema.fields.damageReductionRulesDefault value=settingFields._source.damageReductionRulesDefault localize=true}}
<footer class="form-footer">
<button data-action="reset">