mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-03-08 06:56:12 +01:00
Merged with development
This commit is contained in:
commit
4944d1ef49
161 changed files with 2733 additions and 694 deletions
|
|
@ -2,6 +2,7 @@ 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 * as countdowns from './countdowns.mjs';
|
||||
export * as actions from './action/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ 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') {
|
||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import DhpActor from '../../documents/actor.mjs';
|
|||
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
||||
import { ActionMixin } from '../fields/actionField.mjs';
|
||||
import { originItemField } from '../chat-message/actorRoll.mjs';
|
||||
import TriggerField from '../fields/triggerField.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
|
|
@ -34,7 +35,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
nullable: false,
|
||||
required: true
|
||||
}),
|
||||
targetUuid: new fields.StringField({ initial: undefined })
|
||||
targetUuid: new fields.StringField({ initial: undefined }),
|
||||
triggers: new fields.ArrayField(new TriggerField())
|
||||
};
|
||||
|
||||
this.extraSchemas.forEach(s => {
|
||||
|
|
@ -164,7 +166,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
*/
|
||||
getRollData(data = {}) {
|
||||
const actorData = this.actor ? this.actor.getRollData(false) : {};
|
||||
|
||||
actorData.result = data.roll?.total ?? 1;
|
||||
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
||||
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
||||
|
|
@ -197,6 +198,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
let config = this.prepareConfig(event);
|
||||
if (!config) return;
|
||||
|
||||
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item);
|
||||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
||||
|
||||
// Display configuration window if necessary
|
||||
|
|
@ -263,6 +266,28 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the all potentially applicable effects on the actor
|
||||
* @param {DHActor} actor The actor performing the action
|
||||
* @param {DHItem|DhActor} effectParent The parent of the effect
|
||||
* @returns {DhActiveEffect[]}
|
||||
*/
|
||||
static async getEffects(actor, effectParent) {
|
||||
if (!actor) return [];
|
||||
|
||||
return Array.from(await actor.allApplicableEffects()).filter(effect => {
|
||||
/* Effects on weapons only ever apply for the weapon itself */
|
||||
if (effect.parent.type === 'weapon') {
|
||||
/* Unless they're secondary - then they apply only to other primary weapons */
|
||||
if (effect.parent.system.secondary) {
|
||||
if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false;
|
||||
} else if (effectParent?.id !== effect.parent.id) return false;
|
||||
}
|
||||
|
||||
return !effect.isSuppressed;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to know if a configuration dialog must be shown or not when there is no roll.
|
||||
* @param {*} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
|
|
@ -343,6 +368,10 @@ export class ResourceUpdateMap extends Map {
|
|||
}
|
||||
|
||||
addResources(resources) {
|
||||
if (!resources?.length) return;
|
||||
const invalidResources = resources.some(resource => !resource.key);
|
||||
if (invalidResources) return;
|
||||
|
||||
for (const resource of resources) {
|
||||
if (!resource.key) continue;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,5 @@
|
|||
import DHBaseAction from './baseAction.mjs';
|
||||
|
||||
export default class DHSummonAction extends DHBaseAction {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
documentUUID: new fields.DocumentUUIDField({ type: 'Actor' })
|
||||
};
|
||||
}
|
||||
|
||||
async trigger(event, ...args) {
|
||||
if (!this.canSummon || !canvas.scene) return;
|
||||
}
|
||||
|
||||
get canSummon() {
|
||||
return game.user.can('TOKEN_CREATE');
|
||||
}
|
||||
static extraSchemas = [...super.extraSchemas, 'summon'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export default class BeastformEffect extends BaseEffect {
|
|||
base64: false
|
||||
}),
|
||||
tokenSize: new fields.SchemaField({
|
||||
scale: new fields.NumberField({ nullable: false, initial: 1 }),
|
||||
height: new fields.NumberField({ integer: false, nullable: true }),
|
||||
width: new fields.NumberField({ integer: false, nullable: true })
|
||||
})
|
||||
|
|
@ -55,7 +56,9 @@ export default class BeastformEffect extends BaseEffect {
|
|||
const update = {
|
||||
...baseUpdate,
|
||||
texture: {
|
||||
src: this.characterTokenData.tokenImg
|
||||
src: this.characterTokenData.tokenImg,
|
||||
scaleX: this.characterTokenData.tokenSize.scale,
|
||||
scaleY: this.characterTokenData.tokenSize.scale
|
||||
},
|
||||
ring: {
|
||||
enabled: this.characterTokenData.usesDynamicToken,
|
||||
|
|
@ -86,7 +89,9 @@ export default class BeastformEffect extends BaseEffect {
|
|||
y,
|
||||
'texture': {
|
||||
enabled: this.characterTokenData.usesDynamicToken,
|
||||
src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg
|
||||
src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg,
|
||||
scaleX: this.characterTokenData.tokenSize.scale,
|
||||
scaleY: this.characterTokenData.tokenSize.scale
|
||||
},
|
||||
'ring': {
|
||||
subject: {
|
||||
|
|
|
|||
|
|
@ -280,6 +280,24 @@ export default class DhCharacter extends BaseDataActor {
|
|||
})
|
||||
})
|
||||
}),
|
||||
dualityRoll: new fields.SchemaField({
|
||||
defaultHopeDice: new fields.NumberField({
|
||||
nullable: false,
|
||||
required: true,
|
||||
integer: true,
|
||||
choices: CONFIG.DH.GENERAL.dieFaces,
|
||||
initial: 12,
|
||||
label: 'DAGGERHEART.ACTORS.Character.defaultHopeDice'
|
||||
}),
|
||||
defaultFearDice: new fields.NumberField({
|
||||
nullable: false,
|
||||
required: true,
|
||||
integer: true,
|
||||
choices: CONFIG.DH.GENERAL.dieFaces,
|
||||
initial: 12,
|
||||
label: 'DAGGERHEART.ACTORS.Character.defaultFearDice'
|
||||
})
|
||||
}),
|
||||
runeWard: new fields.BooleanField({ initial: false }),
|
||||
burden: new fields.SchemaField({
|
||||
ignore: new fields.BooleanField()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import BaseDataActor from './base.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
import DHEnvironmentSettings from '../../applications/sheets-configs/environment-settings.mjs';
|
||||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
export default class DhEnvironment extends BaseDataActor {
|
||||
scenes = new Set();
|
||||
|
||||
/**@override */
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Environment'];
|
||||
|
||||
|
|
@ -53,6 +56,31 @@ export default class DhEnvironment extends BaseDataActor {
|
|||
}
|
||||
|
||||
isItemValid(source) {
|
||||
return source.type === "feature";
|
||||
return source.type === 'feature';
|
||||
}
|
||||
|
||||
_onUpdate(changes, options, userId) {
|
||||
super._onUpdate(changes, options, userId);
|
||||
for (const scene of this.scenes) {
|
||||
scene.render();
|
||||
}
|
||||
}
|
||||
|
||||
_onDelete(options, userId) {
|
||||
super._onDelete(options, userId);
|
||||
for (const scene of this.scenes) {
|
||||
if (game.user.isActiveGM) {
|
||||
const newSceneEnvironments = scene.flags.daggerheart.sceneEnvironments.filter(
|
||||
x => x !== this.parent.uuid
|
||||
);
|
||||
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 }
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@ export { ActionCollection } from './actionField.mjs';
|
|||
export { default as FormulaField } from './formulaField.mjs';
|
||||
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
|
||||
export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs';
|
||||
export { default as TriggerField } from './triggerField.mjs';
|
||||
export { default as MappingField } from './mappingField.mjs';
|
||||
export * as ActionFields from './action/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@ export { default as BeastformField } from './beastformField.mjs';
|
|||
export { default as DamageField } from './damageField.mjs';
|
||||
export { default as RollField } from './rollField.mjs';
|
||||
export { default as MacroField } from './macroField.mjs';
|
||||
export { default as SummonField } from './summonField.mjs';
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ export class DHActionRollData extends foundry.abstract.DataModel {
|
|||
if (this.type === CONFIG.DH.GENERAL.rollTypes.attack.id)
|
||||
modifiers.push({
|
||||
label: 'Bonus to Hit',
|
||||
value: this.bonus ?? this.parent.actor.system.attack.roll.bonus
|
||||
value: this.bonus ?? this.parent.actor.system.attack.roll.bonus ?? 0
|
||||
});
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
89
module/data/fields/action/summonField.mjs
Normal file
89
module/data/fields/action/summonField.mjs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import FormulaField from '../formulaField.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
export default class DHSummonField extends fields.ArrayField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 120;
|
||||
|
||||
constructor(options = {}, context = {}) {
|
||||
const summonFields = new fields.SchemaField({
|
||||
actorUUID: new fields.DocumentUUIDField({
|
||||
type: 'Actor',
|
||||
required: true
|
||||
}),
|
||||
count: new FormulaField({
|
||||
required: true,
|
||||
default: '1'
|
||||
})
|
||||
});
|
||||
super(summonFields, options, context);
|
||||
}
|
||||
|
||||
static async execute() {
|
||||
if (!canvas.scene) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.summon.error'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.summon.length === 0) {
|
||||
ui.notifications.warn('No actors configured for this Summon action.');
|
||||
return;
|
||||
}
|
||||
|
||||
const rolls = [];
|
||||
const summonData = [];
|
||||
for (const summon of this.summon) {
|
||||
let count = summon.count;
|
||||
const roll = new Roll(summon.count);
|
||||
if (!roll.isDeterministic) {
|
||||
await roll.evaluate();
|
||||
if (game.modules.get('dice-so-nice')?.active) rolls.push(roll);
|
||||
count = roll.total;
|
||||
}
|
||||
|
||||
const actor = DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
|
||||
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
|
||||
summon.rolledCount = count;
|
||||
summon.actor = actor.toObject();
|
||||
|
||||
summonData.push({ actor, count: count });
|
||||
}
|
||||
|
||||
if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true)));
|
||||
|
||||
this.actor.sheet?.minimize();
|
||||
DHSummonField.handleSummon(summonData, this.actor);
|
||||
}
|
||||
|
||||
/* Check for any available instances of the actor present in the world if we're missing artwork in the compendium */
|
||||
static getWorldActor(baseActor) {
|
||||
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
|
||||
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
|
||||
const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
|
||||
return worldActorCopy ?? baseActor;
|
||||
}
|
||||
|
||||
return baseActor;
|
||||
}
|
||||
|
||||
static async handleSummon(summonData, actionActor, summonIndex = 0) {
|
||||
const summon = summonData[summonIndex];
|
||||
const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, {
|
||||
name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}`
|
||||
});
|
||||
|
||||
if (!result) return actionActor.sheet?.maximize();
|
||||
summon.actor = result.actor;
|
||||
|
||||
summon.count--;
|
||||
if (summon.count <= 0) {
|
||||
summonIndex++;
|
||||
if (summonIndex === summonData.length) return actionActor.sheet?.maximize();
|
||||
}
|
||||
|
||||
DHSummonField.handleSummon(summonData, actionActor, summonIndex);
|
||||
}
|
||||
}
|
||||
|
|
@ -267,7 +267,8 @@ export function ActionMixin(Base) {
|
|||
action: {
|
||||
name: this.name,
|
||||
img: this.baseAction ? this.parent.parent.img : this.img,
|
||||
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10']
|
||||
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
||||
summon: this.summon
|
||||
},
|
||||
itemOrigin: this.item,
|
||||
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
||||
|
|
|
|||
24
module/data/fields/triggerField.mjs
Normal file
24
module/data/fields/triggerField.mjs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
export default class TriggerField extends foundry.data.fields.SchemaField {
|
||||
constructor(context) {
|
||||
super(
|
||||
{
|
||||
trigger: new foundry.data.fields.StringField({
|
||||
nullable: false,
|
||||
blank: false,
|
||||
initial: CONFIG.DH.TRIGGER.triggers.dualityRoll.id,
|
||||
choices: CONFIG.DH.TRIGGER.triggers,
|
||||
label: 'DAGGERHEART.CONFIG.Triggers.triggerType'
|
||||
}),
|
||||
triggeringActorType: new foundry.data.fields.StringField({
|
||||
nullable: false,
|
||||
blank: false,
|
||||
initial: CONFIG.DH.TRIGGER.triggerActorTargetType.any.id,
|
||||
choices: CONFIG.DH.TRIGGER.triggerActorTargetType,
|
||||
label: 'DAGGERHEART.CONFIG.Triggers.triggeringActorType'
|
||||
}),
|
||||
command: new foundry.data.fields.JavaScriptField({ async: true })
|
||||
},
|
||||
context
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +54,21 @@ export default class DHArmor extends AttachableItem {
|
|||
);
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
|
||||
const features = this.armorFeatures.map(x => allFeatures[x.value]);
|
||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||
|
||||
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/armor/description.hbs',
|
||||
{ features }
|
||||
);
|
||||
|
||||
return { prefix, value: baseDescription, suffix: null };
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _preUpdate(changes, options, user) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
* @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
|
||||
*/
|
||||
|
||||
import { addLinkedItemsDiff, createScrollText, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
||||
import { addLinkedItemsDiff, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
||||
import { ActionsField } from '../fields/actionField.mjs';
|
||||
import FormulaField from '../fields/formulaField.mjs';
|
||||
|
||||
|
|
@ -124,6 +124,33 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
return [source, page ? `pg ${page}.` : null].filter(x => x).join('. ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Augments the description for the item with type specific info to display. Implemented in applicable item subtypes.
|
||||
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
||||
* @returns {string}
|
||||
*/
|
||||
async getDescriptionData(_options) {
|
||||
return { prefix: null, value: this.description, suffix: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the enriched and augmented description for the item.
|
||||
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
||||
* @returns {string}
|
||||
*/
|
||||
async getEnrichedDescription() {
|
||||
if (!this.metadata.hasDescription) return '';
|
||||
|
||||
const { prefix, value, suffix } = await this.getDescriptionData();
|
||||
const fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n<hr>\n');
|
||||
|
||||
return await foundry.applications.ux.TextEditor.implementation.enrichHTML(fullDescription, {
|
||||
relativeTo: this,
|
||||
rollData: this.getRollData(),
|
||||
secrets: this.isOwner
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
|
||||
* @param {object} [options] - Options which modify the getRollData method.
|
||||
|
|
@ -135,6 +162,11 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
return data;
|
||||
}
|
||||
|
||||
prepareBaseData() {
|
||||
super.prepareBaseData();
|
||||
game.system.registeredTriggers.registerItemTriggers(this.parent);
|
||||
}
|
||||
|
||||
async _preCreate(data, options, user) {
|
||||
// Skip if no initial action is required or actions already exist
|
||||
if (this.metadata.hasInitialAction && foundry.utils.isEmpty(this.actions)) {
|
||||
|
|
@ -195,6 +227,28 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
const armorData = getScrollTextData(this.parent.parent.system.resources, changed.system.marks, 'armor');
|
||||
options.scrollingTextData = [armorData];
|
||||
}
|
||||
|
||||
if (changed.system?.actions) {
|
||||
const triggersToRemove = Object.keys(changed.system.actions).reduce((acc, key) => {
|
||||
if (!changed.system.actions[key]) {
|
||||
const strippedKey = key.replace('-=', '');
|
||||
acc.push(...this.actions.get(strippedKey).triggers.map(x => x.trigger));
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
game.system.registeredTriggers.unregisterTriggers(triggersToRemove, this.parent.uuid);
|
||||
|
||||
if (!(this.parent.parent.token instanceof game.system.api.documents.DhToken)) {
|
||||
for (const token of this.parent.parent.getActiveTokens()) {
|
||||
game.system.registeredTriggers.unregisterTriggers(
|
||||
triggersToRemove,
|
||||
`${token.document.uuid}.${this.parent.uuid}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onUpdate(changed, options, userId) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export default class DHBeastform extends BaseDataItem {
|
|||
choices: CONFIG.DH.ACTOR.tokenSize,
|
||||
initial: CONFIG.DH.ACTOR.tokenSize.custom.id
|
||||
}),
|
||||
scale: new fields.NumberField({ nullable: false, min: 0.2, max: 3, step: 0.05, initial: 1 }),
|
||||
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
|
||||
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
||||
}),
|
||||
|
|
@ -184,6 +185,7 @@ export default class DHBeastform extends BaseDataItem {
|
|||
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
||||
tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture,
|
||||
tokenSize: {
|
||||
scale: this.parent.parent.prototypeToken.texture.scaleX,
|
||||
height: this.parent.parent.prototypeToken.height,
|
||||
width: this.parent.parent.prototypeToken.width
|
||||
}
|
||||
|
|
@ -209,7 +211,9 @@ export default class DHBeastform extends BaseDataItem {
|
|||
height,
|
||||
width,
|
||||
texture: {
|
||||
src: this.tokenImg
|
||||
src: this.tokenImg,
|
||||
scaleX: this.tokenSize.scale,
|
||||
scaleY: this.tokenSize.scale
|
||||
},
|
||||
ring: {
|
||||
subject: {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,21 @@ export default class DHDomainCard extends BaseDataItem {
|
|||
required: true,
|
||||
initial: CONFIG.DH.DOMAIN.cardTypes.ability.id
|
||||
}),
|
||||
inVault: new fields.BooleanField({ initial: false })
|
||||
inVault: new fields.BooleanField({ initial: false }),
|
||||
vaultActive: new fields.BooleanField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: false
|
||||
}),
|
||||
loadoutIgnore: new fields.BooleanField({
|
||||
required: true,
|
||||
nullable: false,
|
||||
initial: false
|
||||
}),
|
||||
domainTouched: new fields.NumberField({
|
||||
nullable: true,
|
||||
initial: null
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -38,6 +52,19 @@ export default class DHDomainCard extends BaseDataItem {
|
|||
return game.i18n.localize(allDomainData[this.domain].label);
|
||||
}
|
||||
|
||||
get isVaultSupressed() {
|
||||
return this.inVault && !this.vaultActive;
|
||||
}
|
||||
|
||||
get isDomainTouchedSuppressed() {
|
||||
if (!this.parent.system.domainTouched || this.parent.parent?.type !== 'character') return false;
|
||||
|
||||
const matchingDomainCards = this.parent.parent.items.filter(
|
||||
item => !item.system.inVault && item.system.domain === this.parent.system.domain
|
||||
).length;
|
||||
return matchingDomainCards < this.parent.system.domainTouched;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**@override */
|
||||
|
|
|
|||
|
|
@ -110,6 +110,21 @@ export default class DHWeapon extends AttachableItem {
|
|||
);
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
|
||||
const features = this.weaponFeatures.map(x => allFeatures[x.value]);
|
||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||
|
||||
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/weapon/description.hbs',
|
||||
{ features }
|
||||
);
|
||||
|
||||
return { prefix, value: baseDescription, suffix: null };
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
|
||||
}
|
||||
|
|
|
|||
154
module/data/registeredTriggers.mjs
Normal file
154
module/data/registeredTriggers.mjs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
export default class RegisteredTriggers extends Map {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
registerTriggers(triggers, actor, uuid) {
|
||||
for (const triggerKey of Object.keys(CONFIG.DH.TRIGGER.triggers)) {
|
||||
const match = triggers[triggerKey];
|
||||
const existingTrigger = this.get(triggerKey);
|
||||
|
||||
if (!match) {
|
||||
if (existingTrigger?.get(uuid)) this.get(triggerKey).delete(uuid);
|
||||
} else {
|
||||
const { trigger, triggeringActorType, commands } = match;
|
||||
|
||||
if (!existingTrigger) this.set(trigger, new Map());
|
||||
this.get(trigger).set(uuid, { actor, triggeringActorType, commands });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerItemTriggers(item, registerOverride) {
|
||||
for (const action of item.system.actions ?? []) {
|
||||
if (!action.actor) continue;
|
||||
|
||||
/* Non actor-linked should only prep synthetic actors so they're not registering triggers unless they're on the canvas */
|
||||
if (
|
||||
!registerOverride &&
|
||||
!action.actor.prototypeToken.actorLink &&
|
||||
(!(action.actor.parent instanceof game.system.api.documents.DhToken) || !action.actor.parent?.uuid)
|
||||
)
|
||||
continue;
|
||||
|
||||
const triggers = {};
|
||||
for (const trigger of action.triggers) {
|
||||
const { args } = CONFIG.DH.TRIGGER.triggers[trigger.trigger];
|
||||
const fn = new foundry.utils.AsyncFunction(...args, `{${trigger.command}\n}`);
|
||||
|
||||
if (!triggers[trigger.trigger])
|
||||
triggers[trigger.trigger] = {
|
||||
trigger: trigger.trigger,
|
||||
triggeringActorType: trigger.triggeringActorType,
|
||||
commands: []
|
||||
};
|
||||
triggers[trigger.trigger].commands.push(fn.bind(action));
|
||||
}
|
||||
|
||||
this.registerTriggers(triggers, action.actor?.uuid, item.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
unregisterTriggers(triggerKeys, uuid) {
|
||||
for (const triggerKey of triggerKeys) {
|
||||
const existingTrigger = this.get(triggerKey);
|
||||
if (!existingTrigger) return;
|
||||
|
||||
existingTrigger.delete(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
unregisterItemTriggers(items) {
|
||||
for (const item of items) {
|
||||
if (!item.system.actions.size) continue;
|
||||
|
||||
const triggers = (item.system.actions ?? []).reduce((acc, action) => {
|
||||
acc.push(...action.triggers.map(x => x.trigger));
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
this.unregisterTriggers(triggers, item.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
unregisterSceneTriggers(scene) {
|
||||
for (const triggerKey of Object.keys(CONFIG.DH.TRIGGER.triggers)) {
|
||||
const existingTrigger = this.get(triggerKey);
|
||||
if (!existingTrigger) continue;
|
||||
const filtered = new Map();
|
||||
for (const [uuid, data] of existingTrigger.entries()) {
|
||||
if (!uuid.startsWith(scene.uuid)) filtered.set(uuid, data);
|
||||
}
|
||||
this.set(triggerKey, filtered);
|
||||
}
|
||||
}
|
||||
|
||||
registerSceneTriggers(scene) {
|
||||
/* TODO: Finish sceneEnvironment registration and unreg */
|
||||
// const systemData = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart);
|
||||
// for (const environment of systemData.sceneEnvironments) {
|
||||
// for (const feature of environment.system.features) {
|
||||
// if(feature) this.registerItemTriggers(feature, true);
|
||||
// }
|
||||
// }
|
||||
|
||||
for (const actor of scene.tokens.filter(x => x.actor).map(x => x.actor)) {
|
||||
if (actor.prototypeToken.actorLink) continue;
|
||||
|
||||
for (const item of actor.items) {
|
||||
this.registerItemTriggers(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async runTrigger(trigger, currentActor, ...args) {
|
||||
const updates = [];
|
||||
const triggerSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).triggers;
|
||||
if (!triggerSettings.enabled) return updates;
|
||||
|
||||
const dualityTrigger = this.get(trigger);
|
||||
if (dualityTrigger) {
|
||||
const tokenBoundActors = ['adversary', 'environment'];
|
||||
const triggerActors = ['character', ...tokenBoundActors];
|
||||
for (let [itemUuid, { actor: actorUuid, triggeringActorType, commands }] of dualityTrigger.entries()) {
|
||||
const actor = await foundry.utils.fromUuid(actorUuid);
|
||||
if (!actor || !triggerActors.includes(actor.type)) continue;
|
||||
if (tokenBoundActors.includes(actor.type) && !actor.getActiveTokens().length) continue;
|
||||
|
||||
const triggerData = CONFIG.DH.TRIGGER.triggers[trigger];
|
||||
if (triggerData.usesActor && triggeringActorType !== 'any') {
|
||||
if (triggeringActorType === 'self' && currentActor?.uuid !== actorUuid) continue;
|
||||
else if (triggeringActorType === 'other' && currentActor?.uuid === actorUuid) continue;
|
||||
}
|
||||
|
||||
for (const command of commands) {
|
||||
try {
|
||||
if (CONFIG.debug.triggers) {
|
||||
const item = await foundry.utils.fromUuid(itemUuid);
|
||||
console.log(
|
||||
game.i18n.format('DAGGERHEART.UI.ConsoleLogs.triggerRun', {
|
||||
actor: actor.name ?? '<Missing Actor>',
|
||||
item: item?.name ?? '<Missing Item>',
|
||||
trigger: game.i18n.localize(triggerData.label)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const result = await command(...args);
|
||||
if (result?.updates?.length) updates.push(...result.updates);
|
||||
} catch (_) {
|
||||
const triggerName = game.i18n.localize(triggerData.label);
|
||||
ui.notifications.error(
|
||||
game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerError', {
|
||||
trigger: triggerName,
|
||||
actor: currentActor?.name
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updates;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
|
||||
/* Foundry does not add any system data for subtyped Scenes. The data model is therefore used by instantiating a new instance of it for sceneConfigSettings.mjs.
|
||||
Needed dataprep and lifetime hooks are handled in documents/scene.
|
||||
*/
|
||||
export default class DHScene extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -13,7 +18,8 @@ export default class DHScene extends foundry.abstract.DataModel {
|
|||
veryClose: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.veryClose.name' }),
|
||||
close: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.close.name' }),
|
||||
far: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.far.name' })
|
||||
})
|
||||
}),
|
||||
sceneEnvironments: new ForeignDocumentUUIDArrayField({ type: 'Actor', prune: true })
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,11 +55,6 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
initial: true,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.resourceScrollTexts.label'
|
||||
}),
|
||||
playerCanEditSheet: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.playerCanEditSheet.label'
|
||||
}),
|
||||
defeated: new fields.SchemaField({
|
||||
enabled: new fields.BooleanField({
|
||||
required: true,
|
||||
|
|
@ -173,6 +168,13 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
label: 'DAGGERHEART.GENERAL.player.plurial'
|
||||
})
|
||||
})
|
||||
}),
|
||||
triggers: new fields.SchemaField({
|
||||
enabled: new fields.BooleanField({
|
||||
nullable: false,
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.triggers.enabled.label'
|
||||
})
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue