mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-11 19:25:21 +01:00
* Added BP calculation and tooltip breakdown of BP sources * Added Modifiers * Fixed automatic battleToggles * Corrected 'NoToughies' conditional * Fixed GM-only visibility * Fixed combatant isNPC
366 lines
15 KiB
JavaScript
366 lines
15 KiB
JavaScript
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 = {};
|
|
if (element.dataset.uuid) {
|
|
const effectData = (await foundry.utils.fromUuid(element.dataset.uuid)).toObject();
|
|
effect = {
|
|
...effectData,
|
|
name: game.i18n.localize(effectData.name),
|
|
description: game.i18n.localize(effectData.description ?? effectData.parent.system.description)
|
|
};
|
|
} else {
|
|
const conditions = CONFIG.DH.GENERAL.conditions();
|
|
const condition = conditions[element.dataset.condition];
|
|
effect = {
|
|
...condition,
|
|
name: game.i18n.localize(condition.name),
|
|
description: game.i18n.localize(condition.description),
|
|
appliedBy: element.dataset.appliedBy,
|
|
isLockedCondition: true
|
|
};
|
|
}
|
|
|
|
html = await foundry.applications.handlebars.renderTemplate(
|
|
`systems/daggerheart/templates/ui/tooltip/effect-display.hbs`,
|
|
{
|
|
effect
|
|
}
|
|
);
|
|
|
|
this.tooltip.innerHTML = html;
|
|
options.direction = this._determineItemTooltipDirection(element);
|
|
} else {
|
|
this.#bordered = false;
|
|
}
|
|
|
|
if (element.dataset.tooltip?.startsWith('#item#')) {
|
|
const itemUuid = element.dataset.tooltip.slice(6);
|
|
const item = await foundry.utils.fromUuid(itemUuid);
|
|
if (item) {
|
|
const isAction = item instanceof game.system.api.models.actions.actionsTypes.base;
|
|
const isEffect = item instanceof ActiveEffect;
|
|
await this.enrichText(item, isAction || isEffect);
|
|
|
|
const type = isAction ? 'action' : isEffect ? 'effect' : item.type;
|
|
html = await foundry.applications.handlebars.renderTemplate(
|
|
`systems/daggerheart/templates/ui/tooltip/${type}.hbs`,
|
|
{
|
|
item: item,
|
|
description: item.system?.enrichedDescription ?? item.enrichedDescription,
|
|
config: CONFIG.DH
|
|
}
|
|
);
|
|
|
|
this.tooltip.innerHTML = html;
|
|
options.direction = this._determineItemTooltipDirection(element);
|
|
}
|
|
} else {
|
|
const attack = element.dataset.tooltip?.startsWith('#attack#');
|
|
if (attack) {
|
|
const actorUuid = element.dataset.tooltip.slice(8);
|
|
const actor = await foundry.utils.fromUuid(actorUuid);
|
|
const attack = actor.system.attack;
|
|
|
|
const description = await TextEditor.enrichHTML(attack.description);
|
|
html = await foundry.applications.handlebars.renderTemplate(
|
|
`systems/daggerheart/templates/ui/tooltip/attack.hbs`,
|
|
{
|
|
attack: attack,
|
|
description: description,
|
|
parent: actor,
|
|
config: CONFIG.DH
|
|
}
|
|
);
|
|
|
|
this.tooltip.innerHTML = html;
|
|
}
|
|
|
|
const shortRest = element.dataset.tooltip?.startsWith('#shortRest#');
|
|
const longRest = element.dataset.tooltip?.startsWith('#longRest#');
|
|
if (shortRest || longRest) {
|
|
const key = element.dataset.tooltip.slice(shortRest ? 11 : 10);
|
|
|
|
const moves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves[
|
|
element.dataset.restType
|
|
].moves;
|
|
const move = moves[key];
|
|
const description = await TextEditor.enrichHTML(move.description);
|
|
html = await foundry.applications.handlebars.renderTemplate(
|
|
`systems/daggerheart/templates/ui/tooltip/downtime.hbs`,
|
|
{
|
|
move: move,
|
|
description: description
|
|
}
|
|
);
|
|
|
|
this.tooltip.innerHTML = html;
|
|
options.direction = this._determineItemTooltipDirection(
|
|
element,
|
|
this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
|
);
|
|
}
|
|
|
|
const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#');
|
|
const isDisadvantage = element.dataset.tooltip?.startsWith('#disadvantage#');
|
|
if (isAdvantage || isDisadvantage) {
|
|
const actorUuid = element.dataset.tooltip.slice(isAdvantage ? 11 : 14);
|
|
const actor = await foundry.utils.fromUuid(actorUuid);
|
|
|
|
if (actor) {
|
|
html = await foundry.applications.handlebars.renderTemplate(
|
|
`systems/daggerheart/templates/ui/tooltip/advantage.hbs`,
|
|
{
|
|
sources: isAdvantage ? actor.system.advantageSources : actor.system.disadvantageSources
|
|
}
|
|
);
|
|
|
|
this.tooltip.innerHTML = html;
|
|
}
|
|
}
|
|
|
|
const deathMove = element.dataset.tooltip?.startsWith('#deathMove#');
|
|
if (deathMove) {
|
|
const name = element.dataset.deathName;
|
|
const img = element.dataset.deathImg;
|
|
const description = element.dataset.deathDescription;
|
|
|
|
html = await foundry.applications.handlebars.renderTemplate(
|
|
`systems/daggerheart/templates/ui/tooltip/death-move.hbs`,
|
|
{
|
|
move: { name: name, img: img, description: description }
|
|
}
|
|
);
|
|
|
|
this.tooltip.innerHTML = html;
|
|
options.direction = this._determineItemTooltipDirection(
|
|
element,
|
|
this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
|
);
|
|
}
|
|
}
|
|
|
|
super.activate(element, { ...options, html: html });
|
|
}
|
|
|
|
_setStyle(position = {}) {
|
|
super._setStyle(position);
|
|
|
|
if (this.#bordered) {
|
|
this.tooltip.classList.add('bordered-tooltip');
|
|
}
|
|
}
|
|
|
|
_determineItemTooltipDirection(element, prefered = this.constructor.TOOLTIP_DIRECTIONS.LEFT) {
|
|
const pos = element.getBoundingClientRect();
|
|
const dirs = this.constructor.TOOLTIP_DIRECTIONS;
|
|
switch (prefered) {
|
|
case this.constructor.TOOLTIP_DIRECTIONS.LEFT:
|
|
return dirs[
|
|
pos.x - this.tooltip.offsetWidth < 0
|
|
? this.constructor.TOOLTIP_DIRECTIONS.DOWN
|
|
: this.constructor.TOOLTIP_DIRECTIONS.LEFT
|
|
];
|
|
case this.constructor.TOOLTIP_DIRECTIONS.UP:
|
|
return dirs[
|
|
pos.y - this.tooltip.offsetHeight < 0
|
|
? this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
|
: this.constructor.TOOLTIP_DIRECTIONS.UP
|
|
];
|
|
case this.constructor.TOOLTIP_DIRECTIONS.RIGHT:
|
|
return dirs[
|
|
pos.x + this.tooltip.offsetWidth > document.body.clientWidth
|
|
? this.constructor.TOOLTIP_DIRECTIONS.DOWN
|
|
: this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
|
];
|
|
case this.constructor.TOOLTIP_DIRECTIONS.DOWN:
|
|
return dirs[
|
|
pos.y + this.tooltip.offsetHeight > document.body.clientHeight
|
|
? this.constructor.TOOLTIP_DIRECTIONS.LEFT
|
|
: this.constructor.TOOLTIP_DIRECTIONS.DOWN
|
|
];
|
|
}
|
|
}
|
|
|
|
async enrichText(item, flatStructure) {
|
|
const { TextEditor } = foundry.applications.ux;
|
|
const enrichPaths = [
|
|
{ path: flatStructure ? '' : 'system', name: 'description' },
|
|
{ path: 'system', name: 'features' },
|
|
{ path: 'system', name: 'actions' },
|
|
{ path: 'system', name: 'customActions' }
|
|
];
|
|
|
|
for (let data of enrichPaths) {
|
|
const basePath = `${data.path ? `${data.path}.` : ''}${data.name}`;
|
|
const pathValue = foundry.utils.getProperty(item, basePath);
|
|
if (!pathValue) continue;
|
|
|
|
if (Array.isArray(pathValue) || pathValue.size) {
|
|
for (const [index, itemValue] of pathValue.entries()) {
|
|
const itemIsAction = itemValue instanceof game.system.api.models.actions.actionsTypes.base;
|
|
const value = itemIsAction || !itemValue?.item ? itemValue : itemValue.item;
|
|
const enrichedValue = await TextEditor.enrichHTML(value.system?.description ?? value.description);
|
|
if (itemIsAction) value.enrichedDescription = enrichedValue;
|
|
else foundry.utils.setProperty(item, `${basePath}.${index}.enrichedDescription`, enrichedValue);
|
|
}
|
|
} else {
|
|
const enrichedValue = await TextEditor.enrichHTML(pathValue);
|
|
foundry.utils.setProperty(
|
|
item,
|
|
`${data.path ? `${data.path}.` : ''}enriched${data.name.capitalize()}`,
|
|
enrichedValue
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**@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));
|
|
});
|
|
}
|
|
}
|