diff --git a/lang/en.json b/lang/en.json index 02704397..05bde7e8 100755 --- a/lang/en.json +++ b/lang/en.json @@ -682,6 +682,14 @@ "description": "Enemies that enhance their allies and/or disrupt their opponents." } }, + "AdversaryTypeCost": { + "minion": "for each group of Minions equal to the size of the party.", + "support": "for each Social or Support adversary.", + "standard": "for each Horde, Ranged, Skulk, or Standard adversary.", + "leader": "for each Leader adversary.", + "bruiser": "for each Bruiser adversary.", + "solo": "for each Solo adversary." + }, "ArmorFeature": { "burning": { "name": "Burning", @@ -905,6 +913,14 @@ "evolved": "Evolved", "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" + }, "Burden": { "oneHanded": "One-Handed", "twoHanded": "Two-Handed" diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs index 370e62e2..8ee685bb 100644 --- a/module/applications/ui/combatTracker.mjs +++ b/module/applications/ui/combatTracker.mjs @@ -20,11 +20,53 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C } }; + /** @inheritDoc */ + async _preparePartContext(_partId, context, _options) { + return context; + } + + async _prepareContext(options) { + const context = await super._prepareContext(options); + + await this._prepareTrackerContext(context, options); + await this._prepareCombatContext(context, options); + + return context; + } + async _prepareCombatContext(context, options) { await super._prepareCombatContext(context, options); + const adversaryTypes = CONFIG.DH.ACTOR.allAdversaryTypes(); + const maxBP = CONFIG.DH.ENCOUNTER.BaseBPPerEncounter(context.characters.length); + const currentBP = context.adversaries + .reduce((acc, adversary) => { + const existingEntry = acc.find( + x => x.adversary.name === adversary.name && x.adversary.type === adversary.type + ); + if (existingEntry) { + existingEntry.nr += 1; + } else { + acc.push({ adversary, nr: 1 }); + } + return acc; + }, []) + .reduce((acc, entry) => { + const adversary = entry.adversary; + const type = adversaryTypes[adversary.type]; + const bpCost = type.bpCost ?? 0; + if (type.partyAmountPerBP) { + acc += context.characters.length === 0 ? 0 : Math.ceil(entry.nr / context.characters.length); + } else { + acc += bpCost; + } + + return acc; + }, 0); + Object.assign(context, { - fear: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear) + fear: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), + battlepoints: { max: maxBP, current: currentBP } }); } @@ -99,6 +141,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C resource, active: index === combat.turn, canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'), + type: combatant.actor.system.type, img: await this._getCombatantThumbnail(combatant) }; diff --git a/module/config/_module.mjs b/module/config/_module.mjs index 63797607..288cd6aa 100644 --- a/module/config/_module.mjs +++ b/module/config/_module.mjs @@ -2,6 +2,7 @@ export * as actionConfig from './actionConfig.mjs'; export * as actorConfig from './actorConfig.mjs'; export * as domainConfig from './domainConfig.mjs'; export * as effectConfig from './effectConfig.mjs'; +export * as encounterConfig from './encounterConfig.mjs'; export * as flagsConfig from './flagsConfig.mjs'; export * as generalConfig from './generalConfig.mjs'; export * as itemConfig from './itemConfig.mjs'; diff --git a/module/config/actorConfig.mjs b/module/config/actorConfig.mjs index 55f03789..6540946d 100644 --- a/module/config/actorConfig.mjs +++ b/module/config/actorConfig.mjs @@ -108,52 +108,63 @@ export const adversaryTypes = { bruiser: { id: 'bruiser', label: 'DAGGERHEART.CONFIG.AdversaryType.bruiser.label', - description: 'DAGGERHEART.ACTORS.Adversary.bruiser.description' + description: 'DAGGERHEART.ACTORS.Adversary.bruiser.description', + bpCost: 4 }, horde: { id: 'horde', label: 'DAGGERHEART.CONFIG.AdversaryType.horde.label', - description: 'DAGGERHEART.ACTORS.Adversary.horde.description' + description: 'DAGGERHEART.ACTORS.Adversary.horde.description', + bpCost: 2 }, leader: { id: 'leader', label: 'DAGGERHEART.CONFIG.AdversaryType.leader.label', - description: 'DAGGERHEART.ACTORS.Adversary.leader.description' + description: 'DAGGERHEART.ACTORS.Adversary.leader.description', + bpCost: 3 }, minion: { id: 'minion', label: 'DAGGERHEART.CONFIG.AdversaryType.minion.label', - description: 'DAGGERHEART.ACTORS.Adversary.minion.description' + description: 'DAGGERHEART.ACTORS.Adversary.minion.description', + bpCost: 1, + partyAmountPerBP: true }, ranged: { id: 'ranged', label: 'DAGGERHEART.CONFIG.AdversaryType.ranged.label', - description: 'DAGGERHEART.ACTORS.Adversary.ranged.description' + description: 'DAGGERHEART.ACTORS.Adversary.ranged.description', + bpCost: 2 }, skulk: { id: 'skulk', label: 'DAGGERHEART.CONFIG.AdversaryType.skulk.label', - description: 'DAGGERHEART.ACTORS.Adversary.skulk.description' + description: 'DAGGERHEART.ACTORS.Adversary.skulk.description', + bpCost: 2 }, social: { id: 'social', label: 'DAGGERHEART.CONFIG.AdversaryType.social.label', - description: 'DAGGERHEART.ACTORS.Adversary.social.description' + description: 'DAGGERHEART.ACTORS.Adversary.social.description', + bpCost: 1 }, solo: { id: 'solo', label: 'DAGGERHEART.CONFIG.AdversaryType.solo.label', - description: 'DAGGERHEART.ACTORS.Adversary.solo.description' + description: 'DAGGERHEART.ACTORS.Adversary.solo.description', + bpCost: 5 }, standard: { id: 'standard', label: 'DAGGERHEART.CONFIG.AdversaryType.standard.label', - description: 'DAGGERHEART.ACTORS.Adversary.standard.description' + description: 'DAGGERHEART.ACTORS.Adversary.standard.description', + bpCost: 2 }, support: { id: 'support', label: 'DAGGERHEART.CONFIG.AdversaryType.support.label', - description: 'DAGGERHEART.ACTORS.Adversary.support.description' + description: 'DAGGERHEART.ACTORS.Adversary.support.description', + bpCost: 1 } }; diff --git a/module/config/encounterConfig.mjs b/module/config/encounterConfig.mjs new file mode 100644 index 00000000..1e6e0a44 --- /dev/null +++ b/module/config/encounterConfig.mjs @@ -0,0 +1,79 @@ +export const BaseBPPerEncounter = nrCharacters => 3 * nrCharacters + 2; + +export const adversaryTypeCostBrackets = { + 1: [ + { + sort: 1, + types: ['minion'], + description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.minion' + }, + { + sort: 2, + types: ['social', 'support'], + description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.support' + } + ], + 2: [ + { + sort: 1, + types: ['horde', 'ranged', 'skulk', 'standard'], + description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.standard' + } + ], + 3: [ + { + sort: 1, + types: ['leader'], + description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.leader' + } + ], + 4: [ + { + sort: 1, + types: ['bruiser'], + description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.bruiser' + } + ], + 5: [ + { + sort: 1, + types: ['solo'], + description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.solo' + } + ] +}; + +export const BPModifiers = { + [-2]: { + manySolos: { + sort: 1, + description: 'DAGGERHEART.CONFIG.BPModifiers.manySolos' + }, + increaseDamage: { + sort: 2, + description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage' + } + }, + [-1]: { + lessDifficult: { + sort: 2, + description: 'DAGGERHEART.CONFIG.BPModifiers.lessDifficult' + } + }, + 1: { + lowerTier: { + sort: 1, + description: 'DAGGERHEART.CONFIG.BPModifiers.lowerTier' + }, + noToughies: { + sort: 2, + description: 'DAGGERHEART.CONFIG.BPModifiers.noToughies' + } + }, + 2: { + moreDangerous: { + sort: 2, + description: 'DAGGERHEART.CONFIG.BPModifiers.moreDangerous' + } + } +}; diff --git a/module/config/system.mjs b/module/config/system.mjs index 374fd58c..a055319b 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -1,17 +1,19 @@ import * as GENERAL from './generalConfig.mjs'; import * as DOMAIN from './domainConfig.mjs'; +import * as ENCOUNTER from './encounterConfig.mjs'; import * as ACTOR from './actorConfig.mjs'; import * as ITEM from './itemConfig.mjs'; import * as SETTINGS from './settingsConfig.mjs'; import * as EFFECTS from './effectConfig.mjs'; import * as ACTIONS from './actionConfig.mjs'; import * as FLAGS from './flagsConfig.mjs'; -import * as ITEMBROWSER from './itemBrowserConfig.mjs' +import * as ITEMBROWSER from './itemBrowserConfig.mjs'; export const SYSTEM_ID = 'daggerheart'; export const SYSTEM = { id: SYSTEM_ID, + ENCOUNTER, GENERAL, DOMAIN, ACTOR, diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index 95621441..c174935f 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -24,6 +24,53 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti this.tooltip.innerHTML = html; options.direction = this._determineItemTooltipDirection(element); } + } else if (element.dataset.tooltip?.startsWith('#battlepoints#')) { + const combat = game.combats.get(element.dataset.combatId); + const nrCharacters = Number(element.dataset.nrCharacters); + const currentBP = element.dataset.bpCurrent; + const maxBP = element.dataset.bpMax; + + const categories = combat.combatants.reduce((acc, combatant) => { + if (combatant.actor.type === 'adversary') { + const keyData = Object.keys(acc).reduce((identifiers, categoryKey) => { + if (identifiers) return identifiers; + const category = acc[categoryKey]; + const groupingIndex = category.findIndex(grouping => + grouping.types.includes(combatant.actor.system.type) + ); + if (groupingIndex !== -1) identifiers = { categoryKey, groupingIndex }; + + return identifiers; + }, null); + if (keyData) { + const { categoryKey, groupingIndex } = keyData; + const grouping = acc[categoryKey][groupingIndex]; + const partyAmount = + CONFIG.DH.ACTOR.adversaryTypes[combatant.actor.system.type].partyAmountPerBP; + grouping.individuals = (grouping.individuals ?? 0) + 1; + + const currentNr = grouping.nr ?? 0; + grouping.nr = partyAmount + ? Math.ceil(grouping.individuals / (nrCharacters ?? 0)) + : currentNr + 1; + } + } + + return acc; + }, foundry.utils.deepClone(CONFIG.DH.ENCOUNTER.adversaryTypeCostBrackets)); + + html = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/ui/tooltip/battlepoints.hbs`, + { + categories, + nrCharacters, + currentBP, + maxBP + } + ); + + this.tooltip.innerHTML = html; + options.direction = this._determineItemTooltipDirection(element); } else { const attack = element.dataset.tooltip?.startsWith('#attack#'); if (attack) { diff --git a/styles/less/ui/combat-sidebar/encounter-controls.less b/styles/less/ui/combat-sidebar/encounter-controls.less index fd0c1aee..16a8e11a 100644 --- a/styles/less/ui/combat-sidebar/encounter-controls.less +++ b/styles/less/ui/combat-sidebar/encounter-controls.less @@ -4,6 +4,12 @@ .encounter-title { text-align: left; + flex: none; + } + + .encounter-battlepoints { + display: flex; + cursor: help; } .inner-controls { diff --git a/styles/less/ux/index.less b/styles/less/ux/index.less index 68cfc7e5..2108856a 100644 --- a/styles/less/ux/index.less +++ b/styles/less/ux/index.less @@ -1,2 +1,3 @@ @import './tooltip/tooltip.less'; +@import './tooltip/battlepoints.less'; @import './autocomplete/autocomplete.less'; diff --git a/styles/less/ux/tooltip/battlepoints.less b/styles/less/ux/tooltip/battlepoints.less new file mode 100644 index 00000000..d304183f --- /dev/null +++ b/styles/less/ux/tooltip/battlepoints.less @@ -0,0 +1,16 @@ +.daggerheart.dh-style.tooltip { + .battlepoint-categories-container { + display: flex; + flex-direction: column; + gap: 8px; + + .battlepoint-grouping-container { + display: flex; + gap: 4px; + + .unselected-grouping { + opacity: 0.4; + } + } + } +} diff --git a/templates/ui/combatTracker/combatTrackerHeader.hbs b/templates/ui/combatTracker/combatTrackerHeader.hbs index fe7d33a0..a3b1fd37 100644 --- a/templates/ui/combatTracker/combatTrackerHeader.hbs +++ b/templates/ui/combatTracker/combatTrackerHeader.hbs @@ -64,6 +64,8 @@ {{/if}} +
{{battlepoints.current}}/{{battlepoints.max}} BP
+ {{!-- Combat Controls --}}
{{#if hasCombat}} diff --git a/templates/ui/tooltip/battlepoints.hbs b/templates/ui/tooltip/battlepoints.hbs new file mode 100644 index 00000000..c97e877c --- /dev/null +++ b/templates/ui/tooltip/battlepoints.hbs @@ -0,0 +1,15 @@ +
+
+ {{#each categories as |category key|}} + {{#each category as |grouping index|}} +
+ {{#if grouping.nr}} + + {{else}} + + {{/if}} +
+ {{/each}} + {{/each}} +
+
\ No newline at end of file