[Feature] Encounter Battlepoints (#1346)

* Added BP calculation and tooltip breakdown of BP sources

* Added Modifiers

* Fixed automatic battleToggles

* Corrected 'NoToughies' conditional

* Fixed GM-only visibility

* Fixed combatant isNPC
This commit is contained in:
WBHarry 2025-12-06 21:11:34 +01:00 committed by GitHub
parent c3cb9121af
commit 451bef4c92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 712 additions and 141 deletions

View file

@ -43,6 +43,7 @@ CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
CONFIG.Combat.documentClass = documents.DhpCombat;
CONFIG.Combat.dataModels = { base: models.DhCombat };
CONFIG.Combatant.documentClass = documents.DHCombatant;
CONFIG.Combatant.dataModels = { base: models.DhCombatant };
CONFIG.ChatMessage.dataModels = models.chatMessages.config;

View file

@ -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,30 @@
"evolved": "Evolved",
"hybrid": "Hybrid"
},
"BPModifiers": {
"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",
"twoHanded": "Two-Handed"

View file

@ -1,3 +1,5 @@
import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs';
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
static DEFAULT_OPTIONS = {
actions: {
@ -20,11 +22,33 @@ 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 modifierBP =
this.combats
.find(x => x.active)
?.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);
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, hasModifierBP: Boolean(modifierBP) }
});
}
@ -99,6 +123,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)
};

View file

@ -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 hooksConfig from './hooksConfig.mjs';

View file

@ -108,52 +108,64 @@ 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,
bpDescription: 'DAGGERHEART.CONFIG.AdversaryType.leader.'
},
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
}
};

View file

@ -0,0 +1,145 @@
export const BaseBPPerEncounter = nrCharacters => 3 * nrCharacters + 2;
export const AdversaryBPPerEncounter = (adversaries, characters) => {
const adversaryTypes = CONFIG.DH.ACTOR.allAdversaryTypes();
return 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 += characters.length === 0 ? 0 : Math.ceil(entry.nr / characters.length);
} else {
acc += bpCost;
}
return acc;
}, 0);
};
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.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',
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'
}
},
1: {
lowerTier: {
sort: 1,
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',
automatic: true,
conditional: (_combat, adversaries) => {
const toughyTypes = ['bruiser', 'horde', 'leader', 'solo'];
return (
adversaries.length > 0 &&
!adversaries.some(adversary => toughyTypes.includes(adversary.system.type))
);
}
}
},
2: {
moreDangerous: {
sort: 2,
description: 'DAGGERHEART.CONFIG.BPModifiers.moreDangerous.description'
}
}
};

View file

@ -26,3 +26,5 @@ export const userFlags = {
welcomeMessage: 'welcome-message',
countdownMode: 'countdown-mode'
};
export const combatToggle = 'combat-toggle-origin';

View file

@ -1,5 +1,6 @@
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';
@ -13,6 +14,7 @@ export const SYSTEM_ID = 'daggerheart';
export const SYSTEM = {
id: SYSTEM_ID,
ENCOUNTER,
GENERAL,
DOMAIN,
ACTOR,

View file

@ -1,6 +1,39 @@
export default class DhCombat extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {};
return {
battleToggles: new fields.ArrayField(
new fields.SchemaField({
category: new fields.NumberField({ required: true, integer: true }),
grouping: new fields.StringField({ required: true })
})
)
};
}
/** 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];
}
}

View file

@ -1,6 +1,7 @@
export { default as DhpActor } from './actor.mjs';
export { default as DHItem } from './item.mjs';
export { default as DhpCombat } from './combat.mjs';
export { default as DHCombatant } from './combatant.mjs';
export { default as DhActiveEffect } from './activeEffect.mjs';
export { default as DhChatMessage } from './chatMessage.mjs';
export { default as DhToken } from './token.mjs';

View file

@ -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)
);
}
}
}
}

View file

@ -0,0 +1,6 @@
export default class DhCombatant extends Combatant {
/**@inheritdoc */
get isNPC() {
return this.actor?.isNPC ?? (!this.actor || !this.hasPlayerOwner);
}
}

View file

@ -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 ?? {});
}
}

View file

@ -1,10 +1,29 @@
import { AdversaryBPPerEncounter, BaseBPPerEncounter } from '../config/encounterConfig.mjs';
export default class DhTooltipManager extends foundry.helpers.interaction.TooltipManager {
#wide = false;
#bordered = false;
async activate(element, options = {}) {
const { TextEditor } = foundry.applications.ux;
let html = options.html;
if (element.dataset.tooltip?.startsWith('#battlepoints#')) {
this.#wide = true;
html = await this.getBattlepointHTML(element.dataset.combatId);
options.direction = this._determineItemTooltipDirection(element);
super.activate(element, { ...options, html: html });
const lockedTooltip = this.lockTooltip();
lockedTooltip.querySelectorAll('.battlepoint-toggle-container input').forEach(element => {
element.addEventListener('input', this.toggleModifier.bind(this));
});
return;
} else {
this.#wide = false;
}
if (element.dataset.tooltip === '#effect-display#') {
this.#bordered = true;
let effect = {};
@ -220,4 +239,128 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
}
}
}
/**@inheritdoc */
_setStyle(position = {}) {
super._setStyle(position);
if (this.#wide) {
this.tooltip.classList.add('wide');
}
}
/**@inheritdoc */
lockTooltip() {
const clone = super.lockTooltip();
clone.classList.add('wide');
return clone;
}
/** Get HTML for Battlepoints tooltip */
async getBattlepointHTML(combatId) {
const combat = game.combats.get(combatId);
const adversaries =
combat.turns?.filter(x => x.actor?.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? [];
const characters = combat.turns?.filter(x => !x.isNPC) ?? [];
const nrCharacters = characters.length;
const currentBP = AdversaryBPPerEncounter(adversaries, characters);
const maxBP = combat.system.extendedBattleToggles.reduce(
(acc, toggle) => acc + toggle.category,
BaseBPPerEncounter(nrCharacters)
);
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));
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({
...grouping,
categoryKey: Number(categoryKey),
toggleKey,
checked: extendedBattleToggles.find(
x => x.category == categoryKey && x.grouping === toggleKey
),
disabled: grouping.automatic
});
return acc;
}, [])
);
return acc;
}, [])
.sort((a, b) => {
if (a.categoryKey < b.categoryKey) return -1;
if (a.categoryKey > b.categoryKey) return 1;
else return a.toggleKey.localeCompare(b.toggleKey);
});
return await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/battlepoints.hbs`,
{
combatId: combat.id,
nrCharacters,
currentBP,
maxBP,
categories,
toggles
}
);
}
/** Enable/disable a BP modifier */
async toggleModifier(event) {
const { combatId, category, grouping } = event.target.dataset;
const combat = game.combats.get(combatId);
await combat.update({
system: {
battleToggles: combat.system.battleToggles.some(x => x.category == category && x.grouping === grouping)
? combat.system.battleToggles.filter(x => x.category != category && x.grouping !== grouping)
: [...combat.system.battleToggles, { category: Number(category), grouping }]
}
});
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 => {
element.addEventListener('input', this.toggleModifier.bind(this));
});
}
}

View file

@ -4,6 +4,12 @@
.encounter-title {
text-align: left;
flex: none;
}
.encounter-battlepoints {
display: flex;
cursor: help;
}
.inner-controls {

View file

@ -31,4 +31,4 @@
font-size: var(--font-size-12);
}
}
}
}

View file

@ -1,3 +1,4 @@
@import './tooltip/tooltip.less';
@import './tooltip/battlepoints.less';
@import './tooltip/bordered-tooltip.less';
@import './autocomplete/autocomplete.less';

View file

@ -0,0 +1,42 @@
.daggerheart.dh-style.tooltip {
.battlepoint-categories-container {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
.battlepoint-grouping-container {
display: flex;
gap: 4px;
.unselected-grouping {
opacity: 0.4;
}
}
}
.battlepoint-toggles-container {
display: flex;
flex-direction: column;
gap: 8px;
.battlepoint-toggle-container {
display: flex;
gap: 4px;
&.inactive {
opacity: 0.4;
}
.grouping-lock {
font-size: 17.5px;
margin: 0 5px;
color: @golden;
}
input {
margin-top: 0;
}
}
}
}

View file

@ -1,140 +1,147 @@
.daggerheart.dh-style.tooltip {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
border-width: 0;
.tooltip-title-container {
width: 100%;
display: flex;
align-items: center;
gap: 16px;
.tooltip-image {
height: 40px;
width: 40px;
border-radius: 6px;
border: 1px solid @golden;
}
#tooltip,
.locked-tooltip {
&.wide {
max-width: 480px;
}
.tooltip-title {
margin: 0;
text-align: center;
}
.tooltip-image {
height: 180px;
width: 180px;
}
.tooltip-description {
font-style: italic;
text-align: start;
}
.simple-info {
font-style: italic;
}
.tooltip-sub-title {
margin: 0;
color: light-dark(@dark-blue, @beige);
}
.tooltip-information-section {
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
&.spaced {
margin-top: 8px;
}
&.triple {
grid-template-columns: 1fr 1fr 1fr;
}
.tooltip-information {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
&.full-width {
grid-column: span 2;
}
label {
font-weight: bold;
}
label,
div {
white-space: nowrap;
}
}
}
.tooltip-chips {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: 8px;
.tooltip-chip {
font-size: var(--font-size-18);
padding: 2px 4px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
color: light-dark(@dark, @beige);
background-image: url(../assets/parchments/dh-parchment-dark.png);
}
}
.tooltip-tags {
width: 100%;
.daggerheart.dh-style.tooltip {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
border-width: 0;
.tooltip-tag {
.tooltip-title-container {
width: 100%;
display: grid;
grid-template-columns: 80px 1fr;
align-items: start;
gap: 8px;
padding: 4px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
display: flex;
align-items: center;
gap: 16px;
.tooltip-tag-label-container {
display: flex;
align-items: center;
flex-direction: column;
gap: 2px;
.tooltip-tag-image {
width: 40px;
height: 40px;
}
}
.tooltip-tag-label {
font-weight: bold;
text-align: center;
}
.tooltip-tag-description {
display: flex;
flex-wrap: wrap;
.tooltip-image {
height: 40px;
width: 40px;
border-radius: 6px;
border: 1px solid @golden;
}
}
}
.spaced {
margin-bottom: 4px;
.tooltip-title {
margin: 0;
text-align: center;
}
.tooltip-image {
height: 180px;
width: 180px;
}
.tooltip-description {
font-style: italic;
text-align: start;
}
.simple-info {
font-style: italic;
}
.tooltip-sub-title {
margin: 0;
color: light-dark(@dark-blue, @beige);
}
.tooltip-information-section {
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
&.spaced {
margin-top: 8px;
}
&.triple {
grid-template-columns: 1fr 1fr 1fr;
}
.tooltip-information {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
&.full-width {
grid-column: span 2;
}
label {
font-weight: bold;
}
label,
div {
white-space: nowrap;
}
}
}
.tooltip-chips {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: 8px;
.tooltip-chip {
font-size: var(--font-size-18);
padding: 2px 4px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
color: light-dark(@dark, @beige);
background-image: url(../assets/parchments/dh-parchment-dark.png);
}
}
.tooltip-tags {
width: 100%;
display: flex;
flex-direction: column;
gap: 4px;
.tooltip-tag {
width: 100%;
display: grid;
grid-template-columns: 80px 1fr;
align-items: start;
gap: 8px;
padding: 4px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
.tooltip-tag-label-container {
display: flex;
align-items: center;
flex-direction: column;
gap: 2px;
.tooltip-tag-image {
width: 40px;
height: 40px;
}
}
.tooltip-tag-label {
font-weight: bold;
text-align: center;
}
.tooltip-tag-description {
display: flex;
flex-wrap: wrap;
}
}
}
.spaced {
margin-bottom: 4px;
}
}
}

View file

@ -64,6 +64,12 @@
{{/if}}
</strong>
{{#if (and combats.length user.isGM)}}
<div class="encounter-battlepoints" data-tooltip="#battlepoints#" data-combat-id="{{combat.id}}">
{{battlepoints.current}}/{{battlepoints.max}} BP{{#if battlepoints.hasModifierBP}}*{{/if}}
</div>
{{/if}}
{{!-- Combat Controls --}}
<div class="inner-controls">
{{#if hasCombat}}

View file

@ -0,0 +1,29 @@
<div class="daggerheart dh-style tooltip">
<div class="battlepoint-categories-container">
<h3>{{localize "Adversaries"}} ({{currentBP}}/{{maxBP}})</h3>
{{#each categories as |category key|}}
{{#each category as |grouping index|}}
<div class="battlepoint-grouping-container">
{{#if grouping.nr}}
<label>{{key}} BP: {{concat (localize grouping.description) ' ' '('grouping.nr 'x)'}}</label>
{{else}}
<label class="unselected-grouping">{{key}} BP - {{localize grouping.description}}</label>
{{/if}}
</div>
{{/each}}
{{/each}}
</div>
<div class="battlepoint-toggles-container">
<h3>{{localize "Modifiers"}}</h3>
{{#each toggles as |toggle|}}
<div class="battlepoint-toggle-container {{#if (and toggle.disabled (not toggle.checked))}}inactive{{/if}}">
{{#if toggle.disabled}}
<i class="grouping-lock fa-solid {{#if toggle.checked}}fa-lock{{else}}fa-unlock{{/if}}"></i>
{{else}}
<input type="checkbox" data-combat-id="{{@root.combatId}}" data-category="{{toggle.categoryKey}}" data-grouping="{{toggle.toggleKey}}" {{checked toggle.checked}} />
{{/if}}
<label class="unselected-grouping">{{toggle.categoryKey}} BP: {{localize toggle.description}}</label>
</div>
{{/each}}
</div>
</div>