diff --git a/lang/en.json b/lang/en.json index a63f25dd..8822bdc3 100755 --- a/lang/en.json +++ b/lang/en.json @@ -130,7 +130,8 @@ "RangeDependance": { "hint": "Settings for an optional distance at which this effect should activate", "title": "Range Dependant" - } + }, + "immuneStatusText": "Immunity: {status}" }, "ACTORS": { "Adversary": { @@ -396,7 +397,8 @@ "stressReduction": "Reduce By Stress", "title": "Damage Reduction", "unncessaryStress": "You don't need to expend stress", - "usedMarks": "Used Marks" + "usedMarks": "Used Marks", + "reduceSeverity": "Severity Reduced By {nr}" }, "DeathMove": { "selectMove": "Select Move", @@ -1031,7 +1033,8 @@ }, "ItemResourceType": { "simple": "Simple", - "diceValue": "Dice Value" + "diceValue": "Dice Value", + "die": "Die" }, "Range": { "self": { @@ -2526,7 +2529,8 @@ "abilityCheckTitle": "{ability} Check" }, "effectSummary": { - "title": "Effects Applied" + "title": "Effects Applied", + "immunityTo": "Immunity: {immunities}" }, "featureTitle": "Class Feature", "groupRoll": { diff --git a/module/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index b64149c0..cd0a5cf7 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -10,6 +10,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.reject = reject; this.actor = actor; this.damage = damage; + this.damageType = damageType; this.rulesDefault = game.settings.get( CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation @@ -57,6 +58,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap null ); + this.reduceSeverity = this.damageType.reduce((value, curr) => { + return Math.max(this.actor.system.rules.damageReduction.reduceSeverity[curr], value); + }, 0); + this.actor.system.rules.damageReduction.reduceSeverity[this.damageType]; + this.thresholdImmunities = Object.keys(actor.system.rules.damageReduction.thresholdImmunities).reduce( (acc, key) => { if (actor.system.rules.damageReduction.thresholdImmunities[key]) @@ -111,7 +117,9 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id, CONFIG.DH.GENERAL.ruleChoice.offWithToggle.id ].includes(this.rulesDefault); - context.thresholdImmunities = this.thresholdImmunities; + context.reduceSeverity = this.reduceSeverity; + context.thresholdImmunities = + Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null; const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } = this.getDamageInfo(); @@ -173,6 +181,9 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length, 0 ); + if (this.reduceSeverity) { + currentDamage = Math.max(currentDamage - this.reduceSeverity, 0); + } if (this.thresholdImmunities[currentDamage]) currentDamage = 0; diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 85267944..9147a2f2 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -31,6 +31,7 @@ export default class CharacterSheet extends DHBaseActorSheet { toggleEquipItem: CharacterSheet.#toggleEquipItem, toggleResourceDice: CharacterSheet.#toggleResourceDice, handleResourceDice: CharacterSheet.#handleResourceDice, + advanceResourceDie: CharacterSheet.#advanceResourceDie, cancelBeastform: CharacterSheet.#cancelBeastform, useDowntime: this.useDowntime }, @@ -148,6 +149,10 @@ export default class CharacterSheet extends DHBaseActorSheet { htmlElement.querySelectorAll('.armor-marks-input').forEach(element => { element.addEventListener('change', this.updateArmorMarks.bind(this)); }); + + htmlElement.querySelectorAll('.item-resource.die').forEach(element => { + element.addEventListener('contextmenu', this.lowerResourceDie.bind(this)); + }); } /** @inheritdoc */ @@ -858,6 +863,27 @@ export default class CharacterSheet extends DHBaseActorSheet { }); } + /** */ + static #advanceResourceDie(_, target) { + this.updateResourceDie(target, true); + } + + lowerResourceDie(event) { + event.preventDefault(); + event.stopPropagation(); + this.updateResourceDie(event.target, false); + } + + async updateResourceDie(target, advance) { + const item = await getDocFromElement(target); + if (!item) return; + + const advancedValue = item.system.resource.value + (advance ? 1 : -1); + await item.update({ + 'system.resource.value': Math.min(advancedValue, Number(item.system.resource.dieFaces.split('d')[1])) + }); + } + /** * */ diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 09b3b192..367e7682 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -24,7 +24,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { acc.push({ name: game.i18n.localize(statusData.name), statuses: [status], - img: statusData.icon, + img: statusData.icon ?? statusData.img, tint: effect.tint }); } diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index 544d6b2d..0dd7c587 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -1519,6 +1519,10 @@ export const itemResourceTypes = { diceValue: { id: 'diceValue', label: 'DAGGERHEART.CONFIG.ItemResourceType.diceValue' + }, + die: { + id: 'die', + label: 'DAGGERHEART.CONFIG.ItemResourceType.die' } }; diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index 0e74e0c8..6142fd78 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -57,6 +57,12 @@ export default class DhpAdversary extends BaseDataActor { hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true), stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true) }), + rules: new fields.SchemaField({ + conditionImmunities: new fields.SchemaField({ + vulnerable: new fields.BooleanField({ initial: false }), + restrained: new fields.BooleanField({ initial: false }) + }) + }), attack: new ActionField({ initial: { name: 'Attack', diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 645a50da..7b7f4ab7 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -250,6 +250,10 @@ export default class DhCharacter extends BaseDataActor { thresholdImmunities: new fields.SchemaField({ minor: new fields.BooleanField({ initial: false }) }), + reduceSeverity: new fields.SchemaField({ + magical: new fields.NumberField({ initial: 0, min: 0 }), + physical: new fields.NumberField({ initial: 0, min: 0 }) + }), disabledArmor: new fields.BooleanField({ intial: false }) }), attack: new fields.SchemaField({ @@ -279,6 +283,10 @@ export default class DhCharacter extends BaseDataActor { }) }) }), + conditionImmunities: new fields.SchemaField({ + vulnerable: new fields.BooleanField({ initial: false }), + restrained: new fields.BooleanField({ initial: false }) + }), runeWard: new fields.BooleanField({ initial: false }), burden: new fields.SchemaField({ ignore: new fields.BooleanField() diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index 48572460..b5258c5a 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -51,6 +51,12 @@ export default class DhCompanion extends BaseDataActor { } } ), + rules: new fields.SchemaField({ + conditionImmunities: new fields.SchemaField({ + vulnerable: new fields.BooleanField({ initial: false }), + restrained: new fields.BooleanField({ initial: false }) + }) + }), attack: new ActionField({ initial: { name: 'Attack', diff --git a/module/data/fields/action/effectsField.mjs b/module/data/fields/action/effectsField.mjs index 528a8658..d9658736 100644 --- a/module/data/fields/action/effectsField.mjs +++ b/module/data/fields/action/effectsField.mjs @@ -47,6 +47,7 @@ export default class EffectsField extends fields.ArrayField { static async applyEffects(targets) { if (!this.effects?.length || !targets?.length) return; + const conditions = CONFIG.DH.GENERAL.conditions(); let effects = this.effects; const messageTargets = []; targets.forEach(async baseToken => { @@ -56,7 +57,20 @@ export default class EffectsField extends fields.ArrayField { const token = canvas.tokens.get(baseToken.id) ?? foundry.utils.fromUuidSync(baseToken.actorId).prototypeToken; if (!token) return; - messageTargets.push(token.document ?? token); + + const messageToken = token.document ?? token; + const conditionImmunities = messageToken.actor.system.rules.conditionImmunities ?? {}; + messageTargets.push({ + token: messageToken, + conditionImmunities: Object.values(conditionImmunities).some(x => x) + ? game.i18n.format('DAGGERHEART.UI.Chat.effectSummary.immunityTo', { + immunities: Object.keys(conditionImmunities) + .filter(x => conditionImmunities[x]) + .map(x => game.i18n.localize(conditions[x].name)) + .join(', ') + }) + : null + }); effects.forEach(async e => { const effect = this.item.effects.get(e._id); diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index b2896513..1724ec13 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -57,6 +57,27 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { update.img = 'icons/magic/life/heart-cross-blue.webp'; } + const immuneStatuses = + data.statuses?.filter( + status => + this.parent.system.rules?.conditionImmunities && + this.parent.system.rules.conditionImmunities[status] + ) ?? []; + if (immuneStatuses.length > 0) { + update.statuses = data.statuses.filter(x => !immuneStatuses.includes(x)); + const conditions = CONFIG.DH.GENERAL.conditions(); + const scrollingTexts = immuneStatuses.map(status => ({ + text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', { + status: game.i18n.localize(conditions[status].name) + }) + })); + if (update.statuses.length > 0) { + setTimeout(() => scrollingTexts, 500); + } else { + this.parent.queueScrollText(scrollingTexts); + } + } + if (Object.keys(update).length > 0) { await this.updateSource(update); } @@ -76,7 +97,10 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { change.value = change.value.replaceAll(/origin\.@/gi, '@'); try { const effect = foundry.utils.fromUuidSync(change.effect.origin); - const doc = effect.parent?.parent; + const doc = + effect.parent?.parent instanceof game.system.api.documents.DhpActor + ? effect.parent + : effect.parent.parent; if (doc) parseModel = doc; } catch (_) {} } diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index 2b87dda1..32e047fd 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -12,7 +12,10 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/sheets/global/partials/action-item.hbs', 'systems/daggerheart/templates/sheets/global/partials/domain-card-item.hbs', 'systems/daggerheart/templates/sheets/global/partials/item-resource.hbs', - 'systems/daggerheart/templates/sheets/global/partials/resource-section.hbs', + 'systems/daggerheart/templates/sheets/global/partials/resource-section/resource-section.hbs', + 'systems/daggerheart/templates/sheets/global/partials/resource-section/simple.hbs', + 'systems/daggerheart/templates/sheets/global/partials/resource-section/dice-value.hbs', + 'systems/daggerheart/templates/sheets/global/partials/resource-section/die.hbs', 'systems/daggerheart/templates/sheets/global/partials/resource-bar.hbs', 'systems/daggerheart/templates/components/card-preview.hbs', 'systems/daggerheart/templates/levelup/parts/selectable-card-preview.hbs', diff --git a/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json b/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json index dc8f4013..8be83035 100644 --- a/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json +++ b/src/packs/adversaries/adversary_Bear_71qKDLKO3CsrNkdy.json @@ -401,7 +401,7 @@ "description": "
You are Restrained until you break free with a successful Strength Roll.
", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json b/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json index 091b862c..156b1908 100644 --- a/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json +++ b/src/packs/adversaries/adversary_Cult_Adept_0NxCSugvKQ4W8OYZ.json @@ -580,7 +580,7 @@ "description": "You are Restrained in smoky chains until you break free with a successful Strength or Instinct Roll. A target Restrained by this feature must spend a Hope to make an action roll.
", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json b/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json index d14eff69..5f97abe1 100644 --- a/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json +++ b/src/packs/adversaries/adversary_Deeproot_Defender_9x2xY9zwc3xzbXo5.json @@ -439,7 +439,7 @@ "description": "You are Restrained until the Defender takes Severe damage.
", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json b/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json index 5c09cd95..05bf04de 100644 --- a/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json +++ b/src/packs/adversaries/adversary_Giant_Beastmaster_8VZIgU12cB3cvlyH.json @@ -385,7 +385,7 @@ "description": "You are Restrained until you break free with a successful Finesse or Strength Roll.
", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json b/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json index f400054b..0a027340 100644 --- a/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json +++ b/src/packs/adversaries/adversary_Giant_Eagle_OMQ0v6PE8s1mSU0K.json @@ -624,7 +624,7 @@ "description": "", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json b/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json index 32282950..1085e0eb 100644 --- a/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json +++ b/src/packs/adversaries/adversary_Kraken_4nqv3ZwJGjnmic8j.json @@ -426,7 +426,7 @@ "description": "You are Restrained and Vulnerable until you break free with a successful Strength Roll or the Kraken takes Major or greater damage. While Restrained and Vulnerable in this way, you must mark a Stress when you make an action roll.
", "tint": "#ffffff", "statuses": [ - "restrain", + "restrained", "vulnerable" ], "sort": 0, diff --git a/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json b/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json index 7d16e606..df42ee06 100644 --- a/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json +++ b/src/packs/adversaries/adversary_Masked_Thief_niBpVU7yeo5ccskE.json @@ -432,7 +432,7 @@ "description": "You are Restrained and Vulnerable until you break free, ending both conditions, with a successful Finesse or Strength Roll (13).
[[/dr trait=finesse difficulty=13]]
[[/dr trait=strength difficulty=13]]
You are Restrained within the Gaoler until freed with a successful Strength Roll (18). While Restrained, you can only attack the Gaoler.
", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json b/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json index 5449bf90..12894c19 100644 --- a/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json +++ b/src/packs/adversaries/adversary_Young_Dryad_8yUj2Mzvnifhxegm.json @@ -399,7 +399,7 @@ "description": "You are Restrained until you're freed with a successful Strength Roll. When a creature makes an action roll against the cage, they must mark a Stress.
", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json b/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json index 4db3e78f..da819526 100644 --- a/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json +++ b/src/packs/domains/domainCard_Book_of_Norai_WtwSWXTRZa7QVvmo.json @@ -211,7 +211,7 @@ "description": "", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/domains/domainCard_Death_Grip_x0FVGE1YbfXalJiw.json b/src/packs/domains/domainCard_Death_Grip_x0FVGE1YbfXalJiw.json index 437100da..26b0a2f2 100644 --- a/src/packs/domains/domainCard_Death_Grip_x0FVGE1YbfXalJiw.json +++ b/src/packs/domains/domainCard_Death_Grip_x0FVGE1YbfXalJiw.json @@ -267,7 +267,7 @@ "description": "", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, @@ -313,7 +313,7 @@ "description": "", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, @@ -359,7 +359,7 @@ "description": "", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json b/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json index 25277259..acba166e 100644 --- a/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json +++ b/src/packs/domains/domainCard_Shadowbind_kguhWlidhxe2GbT0.json @@ -112,7 +112,7 @@ "description": "", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json b/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json index c78bebca..a6107487 100644 --- a/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json +++ b/src/packs/domains/domainCard_Vicious_Entangle_qvpvTnkAoRn9vYO4.json @@ -173,7 +173,7 @@ "description": "", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, @@ -219,7 +219,7 @@ "description": "", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json b/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json index 9e855978..d67e4062 100644 --- a/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json +++ b/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json @@ -302,7 +302,7 @@ "description": "Restrained lasts until you’re freed with a successful Finesse or Strength roll or by dealing at least 6 damage to the vines.
", "tint": "#ffffff", "statuses": [ - "restrain" + "restrained" ], "sort": 0, "flags": {}, diff --git a/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json b/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json index 0abe95cf..a8ed164a 100644 --- a/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json +++ b/src/packs/environments/environment_Burning_Heart_of_the_Woods_oY69NN4rYxoRE4hl.json @@ -313,7 +313,7 @@ "description": "Restrained and Vulnerable until you break free, clearing both conditions, with a successful Finesse or Strength Roll or by dealing 10 damage to the vines. When the target makes a roll to escape, they take 1d8+4 physical damage and lose a Hope.
What painful memories do the vines bring to the surface as they pierce flesh?