diff --git a/lang/en.json b/lang/en.json index 05bde7e8..47bb62bb 100755 --- a/lang/en.json +++ b/lang/en.json @@ -914,12 +914,28 @@ "hybrid": "Hybrid" }, "BPModifiers": { - "increaseDamage": "if you add +1d4 (or a static +2) to all adversaries' damage rolls (to increase the challenge without lengthening the battle)", - "lessDifficult": "if the fight should be less difficult or shorter.", - "lowerTier": "if you choose an adversary from a lower tier.", - "manySolos": "if you're using 2 or more Solo adversaries.", - "moreDangerous": "if the fight should be more dangerous or last longer", - "noToughies": "if you don't include any Bruisers, Hordes, Leaders, or Solos" + "increaseDamage": { + "description": "if you add +1d4 (or a static +2) to all adversaries' damage rolls (to increase the challenge without lengthening the battle)", + "effect": { + "name": "Increase Damage", + "description": "Add 1d4 to damage" + } + }, + "lessDifficult": { + "description": "if the fight should be less difficult or shorter." + }, + "lowerTier": { + "description": "if you choose an adversary from a lower tier." + }, + "manySolos": { + "description": "if you're using 2 or more Solo adversaries." + }, + "moreDangerous": { + "description": "if the fight should be more dangerous or last longer" + }, + "noToughies": { + "description": "if you don't include any Bruisers, Hordes, Leaders, or Solos" + } }, "Burden": { "oneHanded": "One-Handed", diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs index f843ddb2..b1e658a5 100644 --- a/module/applications/ui/combatTracker.mjs +++ b/module/applications/ui/combatTracker.mjs @@ -42,7 +42,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C const modifierBP = this.combats .find(x => x.active) - ?.system?.battleToggles?.reduce((acc, toggle) => acc + toggle.category, 0) ?? 0; + ?.system?.extendedBattleToggles?.reduce((acc, toggle) => acc + toggle.category, 0) ?? 0; const maxBP = CONFIG.DH.ENCOUNTER.BaseBPPerEncounter(context.characters.length) + modifierBP; const currentBP = AdversaryBPPerEncounter(context.adversaries, context.characters); diff --git a/module/config/encounterConfig.mjs b/module/config/encounterConfig.mjs index 509a79b2..3a9b0af6 100644 --- a/module/config/encounterConfig.mjs +++ b/module/config/encounterConfig.mjs @@ -75,33 +75,68 @@ export const BPModifiers = { [-2]: { manySolos: { sort: 1, - description: 'DAGGERHEART.CONFIG.BPModifiers.manySolos' + description: 'DAGGERHEART.CONFIG.BPModifiers.manySolos.description', + automatic: true, + conditional: (_combat, adversaries) => { + return adversaries.filter(x => x.system.type === 'solo').length > 1; + } }, increaseDamage: { sort: 2, - description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage' + description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.description', + effects: [ + { + name: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.name', + description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.description', + img: 'icons/magic/control/buff-flight-wings-red.webp', + changes: [ + { + key: 'system.bonuses.damage.physical.dice', + mode: 2, + value: '1d4' + }, + { + key: 'system.bonuses.damage.magical.dice', + mode: 2, + value: '1d4' + } + ] + } + ] } }, [-1]: { lessDifficult: { sort: 2, - description: 'DAGGERHEART.CONFIG.BPModifiers.lessDifficult' + description: 'DAGGERHEART.CONFIG.BPModifiers.lessDifficult.description' } }, 1: { lowerTier: { sort: 1, - description: 'DAGGERHEART.CONFIG.BPModifiers.lowerTier' + description: 'DAGGERHEART.CONFIG.BPModifiers.lowerTier.description', + automatic: true, + conditional: (_combat, adversaries, characters) => { + const characterMaxTier = characters.reduce((maxTier, character) => { + return character.system.tier > maxTier ? character.system.tier : maxTier; + }, 1); + return adversaries.some(adversary => adversary.system.tier < characterMaxTier); + } }, noToughies: { sort: 2, - description: 'DAGGERHEART.CONFIG.BPModifiers.noToughies' + description: 'DAGGERHEART.CONFIG.BPModifiers.noToughies.description', + automatic: true, + conditional: (_combat, adversaries) => { + const toughyTypes = ['bruiser', 'horde', 'leader', 'solo']; + return !adversaries.some(adversary => toughyTypes.includes(adversary.system.type)); + } } }, 2: { moreDangerous: { sort: 2, - description: 'DAGGERHEART.CONFIG.BPModifiers.moreDangerous' + description: 'DAGGERHEART.CONFIG.BPModifiers.moreDangerous.description' } } }; diff --git a/module/config/flagsConfig.mjs b/module/config/flagsConfig.mjs index 12ab3ee1..817ac89d 100644 --- a/module/config/flagsConfig.mjs +++ b/module/config/flagsConfig.mjs @@ -26,3 +26,5 @@ export const userFlags = { welcomeMessage: 'welcome-message', countdownMode: 'countdown-mode' }; + +export const combatToggle = 'combat-toggle-origin'; diff --git a/module/data/combat.mjs b/module/data/combat.mjs index 76ccf1b9..565afffc 100644 --- a/module/data/combat.mjs +++ b/module/data/combat.mjs @@ -10,4 +10,30 @@ export default class DhCombat extends foundry.abstract.TypeDataModel { ) }; } + + /** Includes automatic BPModifiers */ + get extendedBattleToggles() { + const modifiers = CONFIG.DH.ENCOUNTER.BPModifiers; + const adversaries = + this.parent.turns?.filter(x => x.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? []; + const characters = this.parent.turns?.filter(x => !x.isNPC) ?? []; + + const activeAutomatic = Object.keys(modifiers).reduce((acc, categoryKey) => { + const category = modifiers[categoryKey]; + acc.push( + ...Object.keys(category).reduce((acc, groupingKey) => { + const grouping = category[groupingKey]; + if (grouping.automatic && grouping.conditional?.(this.parent, adversaries, characters)) { + acc.push({ category: Number(categoryKey), grouping: groupingKey }); + } + + return acc; + }, []) + ); + + return acc; + }, []); + + return [...this.battleToggles, ...activeAutomatic]; + } } diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs index 3ad3189e..01cbee61 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -16,4 +16,57 @@ export default class DhpCombat extends Combat { return a.name.localeCompare(b.name); } + + async toggleModifierEffects(add, actors, category, groupingKey) { + const effectData = category && groupingKey ? [{ category, grouping: groupingKey }] : this.system.battleToggles; + if (add) { + const effects = effectData.reduce((acc, toggle) => { + const grouping = CONFIG.DH.ENCOUNTER.BPModifiers[toggle.category]?.[toggle.grouping]; + if (!grouping?.effects?.length) return acc; + acc.push( + ...grouping.effects.map(effect => ({ + ...effect, + name: game.i18n.localize(effect.name), + description: game.i18n.localize(effect.description), + flags: { + [`${CONFIG.DH.id}.${CONFIG.DH.FLAGS.combatToggle}`]: { + category: toggle.category, + grouping: toggle.grouping + } + } + })) + ); + + return acc; + }, []); + + if (!effects.length) return; + + for (let actor of actors) { + await actor.createEmbeddedDocuments( + 'ActiveEffect', + effects.map(effect => ({ + ...effect, + name: game.i18n.localize(effect.name), + description: game.i18n.localize(effect.description) + })) + ); + } + } else { + for (let actor of actors) { + await actor.deleteEmbeddedDocuments( + 'ActiveEffect', + actor.effects + .filter(x => { + const flag = x.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.combatToggle); + if (!flag) return false; + return effectData.some( + data => flag.category == data.category && flag.grouping === data.grouping + ); + }) + .map(x => x.id) + ); + } + } + } } diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 6d080f31..6996708b 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -72,8 +72,32 @@ export default class DHToken extends TokenDocument { } return attributes; } - + _shouldRecordMovementHistory() { return false; } + + /**@inheritdoc */ + static async createCombatants(tokens, combat) { + combat ??= game.combats.viewed; + if (combat?.system?.battleToggles?.length) { + await combat.toggleModifierEffects( + true, + tokens.map(x => x.actor) + ); + } + super.createCombatants(tokens, combat ?? {}); + } + + /**@inheritdoc */ + static async deleteCombatants(tokens, { combat } = {}) { + combat ??= game.combats.viewed; + if (combat?.system?.battleToggles?.length) { + await combat.toggleModifierEffects( + false, + tokens.map(x => x.actor) + ); + } + super.deleteCombatants(tokens, combat ?? {}); + } } diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index 71386bf8..95b68d50 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -221,7 +221,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti const nrCharacters = characters.length; const currentBP = AdversaryBPPerEncounter(adversaries, characters); - const maxBP = combat.system.battleToggles.reduce( + const maxBP = combat.system.extendedBattleToggles.reduce( (acc, toggle) => acc + toggle.category, BaseBPPerEncounter(nrCharacters) ); @@ -252,18 +252,21 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti return acc; }, foundry.utils.deepClone(CONFIG.DH.ENCOUNTER.adversaryTypeCostBrackets)); + const extendedBattleToggles = combat.system.extendedBattleToggles; const toggles = Object.keys(CONFIG.DH.ENCOUNTER.BPModifiers) .reduce((acc, categoryKey) => { const category = CONFIG.DH.ENCOUNTER.BPModifiers[categoryKey]; acc.push( ...Object.keys(category).reduce((acc, toggleKey) => { + const grouping = category[toggleKey]; acc.push({ - ...category[toggleKey], + ...grouping, categoryKey: Number(categoryKey), toggleKey, - checked: combat.system.battleToggles.find( + checked: extendedBattleToggles.find( x => x.category == categoryKey && x.grouping === toggleKey - ) + ), + disabled: grouping.automatic }); return acc; @@ -302,6 +305,13 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti } }); + await combat.toggleModifierEffects( + event.target.checked, + combat.combatants.filter(x => x.actor.type === 'adversary').map(x => x.actor), + category, + grouping + ); + this.tooltip.innerHTML = await this.getBattlepointHTML(combatId); const lockedTooltip = this.lockTooltip(); lockedTooltip.querySelectorAll('.battlepoint-toggle-container input').forEach(element => { diff --git a/styles/less/ux/tooltip/battlepoints.less b/styles/less/ux/tooltip/battlepoints.less index 16427c37..61c7083d 100644 --- a/styles/less/ux/tooltip/battlepoints.less +++ b/styles/less/ux/tooltip/battlepoints.less @@ -24,6 +24,16 @@ display: flex; gap: 4px; + &.inactive { + opacity: 0.4; + } + + .grouping-lock { + font-size: 17.5px; + margin: 0 5px; + color: @golden; + } + input { margin-top: 0; } diff --git a/templates/ui/tooltip/battlepoints.hbs b/templates/ui/tooltip/battlepoints.hbs index 2e4ab2d6..ec8bbb1c 100644 --- a/templates/ui/tooltip/battlepoints.hbs +++ b/templates/ui/tooltip/battlepoints.hbs @@ -7,7 +7,7 @@ {{#if grouping.nr}} {{else}} - + {{/if}} {{/each}} @@ -16,8 +16,12 @@