Merge from main, with conflict fixes

This commit is contained in:
Chris Ryan 2025-12-08 21:07:44 +10:00
commit a11a2d4e30
1252 changed files with 2537 additions and 22178 deletions

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

@ -877,4 +877,32 @@ export default class DhpActor extends Actor {
return acc;
}, []);
}
/* Temporarily copying the foundry method to add a fix to a bug with scenes
https://discord.com/channels/170995199584108546/1296292044011995136/1446693077443149856
*/
getDependentTokens({ scenes, linked = false } = {}) {
if (this.isToken && !scenes) return [this.token];
if (scenes) scenes = Array.isArray(scenes) ? scenes : [scenes];
else scenes = Array.from(this._dependentTokens.keys());
/* Code to filter out nonexistant scenes */
scenes = scenes.filter(scene => game.scenes.some(x => x.id === scene.id));
if (this.isToken) {
const parent = this.token.parent;
return scenes.includes(parent) ? [this.token] : [];
}
const allTokens = [];
for (const scene of scenes) {
if (!scene) continue;
const tokens = this._dependentTokens.get(scene);
for (const token of tokens ?? []) {
if (!linked || token.actorLink) allTokens.push(token);
}
}
return allTokens;
}
}

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

@ -28,6 +28,13 @@ export default class DHItem extends foundry.documents.Item {
return doc;
}
static async createDocuments(sources, operation) {
// Ensure that items being created are valid to the actor its being added to
const actor = operation.parent;
sources = actor?.system?.isItemValid ? sources.filter((s) => actor.system.isItemValid(s)) : sources;
return super.createDocuments(sources, operation);
}
/* -------------------------------------------- */
/** @inheritDoc */

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