Merged with v14-Dev

This commit is contained in:
WBHarry 2026-03-21 00:53:51 +01:00
commit 4113f6c562
64 changed files with 1375 additions and 989 deletions

View file

@ -1,9 +1,9 @@
export { default as DhCombat } from './combat.mjs';
export { default as DhCombatant } from './combatant.mjs';
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
export { default as DhRollTable } from './rollTable.mjs';
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
export { default as TagTeamData } from './tagTeamData.mjs';
export * as countdowns from './countdowns.mjs';
export * as actions from './action/_module.mjs';

View file

@ -50,9 +50,8 @@ export default class DHAttackAction extends DHDamageAction {
async use(event, options) {
const result = await super.use(event, options);
if (!result.message) return;
if (result.message.system.action.roll?.type === 'attack') {
if (result.message?.system.action.roll?.type === 'attack') {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
}

View file

@ -207,10 +207,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* @param {Event} event Event from the button used to trigger the Action
* @returns {object}
*/
async use(event) {
async use(event, configOptions = {}) {
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
let config = this.prepareConfig(event);
let config = this.prepareConfig(event, configOptions);
if (!config) return;
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item);
@ -231,7 +231,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
if (this.chatDisplay && !config.actionChatMessageHandled) await this.toChat();
if (this.chatDisplay && !config.skips.createMessage && !config.actionChatMessageHandled) await this.toChat();
return config;
}
@ -241,7 +241,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* @param {Event} event Event from the button used to trigger the Action
* @returns {object}
*/
prepareBaseConfig(event) {
prepareBaseConfig(event, configOptions = {}) {
const isActor = this.item instanceof CONFIG.Actor.documentClass;
const actionTitle = game.i18n.localize(this.name);
const itemTitle = isActor || this.item.name === actionTitle ? '' : `${this.item.name} - `;
@ -268,7 +268,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
data: this.getRollData(),
evaluate: this.hasRoll,
resourceUpdates: new ResourceUpdateMap(this.actor),
targetUuid: this.targetUuid
targetUuid: this.targetUuid,
...configOptions
};
DHBaseAction.applyKeybindings(config);
@ -280,8 +281,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* @param {Event} event Event from the button used to trigger the Action
* @returns {object}
*/
prepareConfig(event) {
const config = this.prepareBaseConfig(event);
prepareConfig(event, configOptions = {}) {
const config = this.prepareBaseConfig(event, configOptions);
for (const clsField of Object.values(this.schema.fields)) {
if (clsField?.prepareConfig) if (clsField.prepareConfig.call(this, config) === false) return false;
}
@ -356,11 +357,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
}
get hasDamage() {
return !foundry.utils.isEmpty(this.damage?.parts) && this.type !== 'healing';
return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type !== 'healing';
}
get hasHealing() {
return !foundry.utils.isEmpty(this.damage?.parts) && this.type === 'healing';
return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type === 'healing';
}
get hasSave() {

View file

@ -189,19 +189,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
return true;
}
async _preDelete() {
/* Clear all partyMembers from tagTeam setting.*/
/* Revisit this when tagTeam is improved for many parties */
if (this.parent.parties.size > 0) {
const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
await tagTeam.updateSource({
initiator: this.parent.id === tagTeam.initiator ? null : tagTeam.initiator,
members: Object.keys(tagTeam.members).find(x => x === this.parent.id) ? { [this.parent.id]: _del } : {}
});
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam);
}
}
async _preUpdate(changes, options, userId) {
const allowed = await super._preUpdate(changes, options, userId);
if (allowed === false) return;

View file

@ -822,7 +822,6 @@ export default class DhCharacter extends DhCreature {
static migrateData(source) {
if (typeof source.scars === 'object') source.scars = 0;
if (source.resources?.hope?.max) source.scars = Math.max(6 - source.resources.hope.max, 0);
return super.migrateData(source);
}

View file

@ -75,10 +75,6 @@ export default class DhEnvironment extends BaseDataActor {
);
scene.update({ 'flags.daggerheart.sceneEnvironments': newSceneEnvironments }).then(() => {
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Scene });
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.TagTeamRoll }
});
});
}
}

View file

@ -1,5 +1,6 @@
import BaseDataActor from './base.mjs';
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
import TagTeamData from '../tagTeamData.mjs';
export default class DhParty extends BaseDataActor {
/**@inheritdoc */
@ -14,7 +15,8 @@ export default class DhParty extends BaseDataActor {
handfuls: new fields.NumberField({ initial: 1, integer: true }),
bags: new fields.NumberField({ initial: 0, integer: true }),
chests: new fields.NumberField({ initial: 0, integer: true })
})
}),
tagTeam: new fields.EmbeddedDataField(TagTeamData)
};
}
@ -40,23 +42,6 @@ export default class DhParty extends BaseDataActor {
}
}
async _preDelete() {
/* Clear all partyMembers from tagTeam setting.*/
/* Revisit this when tagTeam is improved for many parties */
const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
await tagTeam.updateSource({
initiator: this.partyMembers.some(x => x.id === tagTeam.initiator) ? null : tagTeam.initiator,
members: Object.keys(tagTeam.members).reduce((acc, key) => {
if (this.partyMembers.find(x => x.id === key)) {
acc[key] = _del;
}
return acc;
}, {})
});
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam);
}
_onDelete(options, userId) {
super._onDelete(options, userId);

View file

@ -50,9 +50,9 @@ export default class DamageField extends fields.SchemaField {
formulas = DamageField.formatFormulas.call(this, formulas, config);
const damageConfig = {
dialog: {},
...config,
roll: formulas,
dialog: {},
data: this.getRollData()
};
delete damageConfig.evaluate;

View file

@ -27,7 +27,7 @@ export default class EffectsField extends fields.ArrayField {
static async execute(config, targets = null, force = false) {
if (!config.hasEffect) return;
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
if (!message) {
if (!message && !config.skips.createMessage) {
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
roll._evaluated = true;
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);

View file

@ -38,7 +38,7 @@ export default class SaveField extends fields.SchemaField {
if (!config.hasSave) return;
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
if (!message) {
if (!message && !config.skips.createMessage) {
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
roll._evaluated = true;
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);

View file

@ -82,6 +82,24 @@ class ResourcesField extends fields.TypedObjectField {
}
return data;
}
/**
* Foundry bar attributes are unable to handle finding the schema field nor the label normally.
* This returns the element if its a valid resource key and overwrites the element's label for that retrieval.
*/
_getField(path) {
if (path.length === 0) return this;
const first = path.shift();
if (first === this.element.name) return this.element_getField(path);
const resources = CONFIG.DH.RESOURCE[this.actorType].all;
if (first in resources) {
this.element.label = resources[first].label;
return this.element._getField(path);
}
return undefined;
}
}
export { attributeField, ResourcesField, stressDamageReductionRule, bonusField };

View file

@ -0,0 +1,47 @@
export default class TagTeamData extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
initiator: new fields.SchemaField(
{
memberId: new fields.StringField({
required: true,
label: 'DAGGERHEART.APPLICATIONS.TagTeamSelect.FIELDS.initiator.memberId.label'
}),
cost: new fields.NumberField({
integer: true,
initial: 3,
label: 'DAGGERHEART.APPLICATIONS.TagTeamSelect.FIELDS.initiator.cost.label'
})
},
{ nullable: true, initial: null }
),
members: new fields.TypedObjectField(new fields.EmbeddedDataField(MemberData))
};
}
}
export class MemberData extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
name: new fields.StringField({ required: true }),
img: new fields.StringField({ required: true }),
rollType: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.tagTeamRollTypes,
initial: CONFIG.DH.GENERAL.tagTeamRollTypes.trait.id,
label: 'Roll Type'
}),
rollChoice: new fields.StringField({ nullable: true, initial: null }),
rollData: new fields.JSONField({ nullable: true, initial: null }),
selected: new fields.BooleanField({ initial: false })
};
}
get roll() {
return this.rollData ? CONFIG.Dice.daggerheart.DualityRoll.fromData(this.rollData) : null;
}
}

View file

@ -1,20 +0,0 @@
import { DhCharacter } from './actor/_module.mjs';
export default class DhTagTeamRoll extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
initiator: new fields.SchemaField({
id: new fields.StringField({ nullable: true, initial: null }),
cost: new fields.NumberField({ integer: true, min: 0, initial: 3 })
}),
members: new fields.TypedObjectField(
new fields.SchemaField({
messageId: new fields.StringField({ required: true, nullable: true, initial: null }),
selected: new fields.BooleanField({ required: true, initial: false })
})
)
};
}
}