mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-22 07:23:37 +02:00
[Feature] Action Areas (#1815)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
* Functioning setup * . * Fixes * Completed * Apply suggestions from code review Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com> * using function.call instead of function.bind * Run lint fix on action areas PR (#1820) * . * . * Restructured getTemplateShape to be a lot more readable * . * . * Changed from 'area' to 'areas' * . * Moved the areas button to the left * Fix regression with actions list * Updated all SRD adversaries --------- Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com> Co-authored-by: Carlos Fernandez <cfern1990@gmail.com>
This commit is contained in:
parent
646e0debbd
commit
3eda3c4c05
89 changed files with 1871 additions and 686 deletions
|
|
@ -15,3 +15,4 @@ export * as chatMessages from './chat-message/_modules.mjs';
|
|||
export * as fields from './fields/_module.mjs';
|
||||
export * as items from './item/_module.mjs';
|
||||
export * as scenes from './scene/_module.mjs';
|
||||
export * as regionBehaviors from './regionBehavior/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
async use(event, options) {
|
||||
const result = await super.use(event, options);
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const fields = foundry.data.fields;
|
|||
*/
|
||||
|
||||
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
||||
static extraSchemas = ['cost', 'uses', 'range'];
|
||||
static extraSchemas = ['areas', 'cost', 'uses', 'range'];
|
||||
|
||||
/** @inheritDoc */
|
||||
static defineSchema() {
|
||||
|
|
|
|||
|
|
@ -93,6 +93,12 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
|||
max: new fields.NumberField({ integer: true, label: 'DAGGERHEART.GENERAL.max' })
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
targetDispositions: new fields.SetField(
|
||||
new fields.NumberField({
|
||||
choices: CONFIG.DH.GENERAL.simpleDispositions
|
||||
}),
|
||||
{ label: 'DAGGERHEART.ACTIVEEFFECT.Config.targetDispositions' }
|
||||
)
|
||||
};
|
||||
}
|
||||
|
|
@ -131,13 +137,14 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
|||
return armorChange.getArmorData();
|
||||
}
|
||||
|
||||
static getDefaultObject() {
|
||||
static getDefaultObject(options = { transfer: true }) {
|
||||
return {
|
||||
name: 'New Effect',
|
||||
id: foundry.utils.randomID(),
|
||||
disabled: false,
|
||||
img: 'icons/magic/life/heart-cross-blue.webp',
|
||||
description: '',
|
||||
transfer: options.transfer,
|
||||
statuses: [],
|
||||
changes: [],
|
||||
system: {
|
||||
|
|
|
|||
|
|
@ -7,26 +7,31 @@ export default class DHAbilityUse extends foundry.abstract.TypeDataModel {
|
|||
img: new fields.StringField({}),
|
||||
name: new fields.StringField({}),
|
||||
description: new fields.StringField({}),
|
||||
actions: new fields.ArrayField(
|
||||
new fields.ObjectField({
|
||||
name: new fields.StringField({}),
|
||||
damage: new fields.SchemaField({
|
||||
type: new fields.StringField({}),
|
||||
value: new fields.StringField({})
|
||||
}),
|
||||
healing: new fields.SchemaField({
|
||||
type: new fields.StringField({}),
|
||||
value: new fields.StringField({})
|
||||
}),
|
||||
cost: new fields.SchemaField({
|
||||
type: new fields.StringField({}),
|
||||
value: new fields.NumberField({})
|
||||
}),
|
||||
target: new fields.SchemaField({
|
||||
type: new fields.StringField({ nullable: true })
|
||||
})
|
||||
})
|
||||
)
|
||||
source: new fields.SchemaField({
|
||||
actor: new fields.StringField(),
|
||||
item: new fields.StringField(),
|
||||
action: new fields.StringField()
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
get actionActor() {
|
||||
if (!this.source.actor) return null;
|
||||
return fromUuidSync(this.source.actor);
|
||||
}
|
||||
|
||||
get actionItem() {
|
||||
const actionActor = this.actionActor;
|
||||
if (!actionActor || !this.source.item) return null;
|
||||
|
||||
const item = actionActor.items.get(this.source.item);
|
||||
return item ? item.system.actions?.find(a => a.id === this.source.action) : null;
|
||||
}
|
||||
|
||||
get action() {
|
||||
const { actionItem: itemAction } = this;
|
||||
if (!this.source.action) return null;
|
||||
if (itemAction) return itemAction;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export { default as AreasField } from './areasField.mjs';
|
||||
export { default as CostField } from './costField.mjs';
|
||||
export { default as CountdownField } from './countdownField.mjs';
|
||||
export { default as UsesField } from './usesField.mjs';
|
||||
|
|
|
|||
40
module/data/fields/action/areasField.mjs
Normal file
40
module/data/fields/action/areasField.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
export default class AreasField extends fields.ArrayField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 150;
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(options = {}, context = {}) {
|
||||
const element = new fields.SchemaField({
|
||||
name: new fields.StringField({
|
||||
nullable: false,
|
||||
initial: 'Area',
|
||||
label: 'DAGGERHEART.GENERAL.name'
|
||||
}),
|
||||
type: new fields.StringField({
|
||||
nullable: false,
|
||||
choices: CONFIG.DH.ACTIONS.areaTypes,
|
||||
initial: CONFIG.DH.ACTIONS.areaTypes.placed.id,
|
||||
label: 'DAGGERHEART.GENERAL.type'
|
||||
}),
|
||||
shape: new fields.StringField({
|
||||
nullable: false,
|
||||
choices: CONFIG.DH.GENERAL.templateTypes,
|
||||
initial: CONFIG.DH.GENERAL.templateTypes.circle.id,
|
||||
label: 'DAGGERHEART.ACTIONS.Config.area.shape'
|
||||
}),
|
||||
/* Could be opened up to allow numbers to be input aswell. Probably best handled via an autocomplete in that case to allow the select options but also free text */
|
||||
size: new fields.StringField({
|
||||
nullable: false,
|
||||
choices: CONFIG.DH.GENERAL.range,
|
||||
initial: CONFIG.DH.GENERAL.range.veryClose.id,
|
||||
label: 'DAGGERHEART.ACTIONS.Config.area.size'
|
||||
}),
|
||||
effects: new fields.ArrayField(new fields.DocumentIdField())
|
||||
});
|
||||
super(element, options, context);
|
||||
}
|
||||
}
|
||||
|
|
@ -281,8 +281,14 @@ export function ActionMixin(Base) {
|
|||
name: this.name,
|
||||
img: this.baseAction ? this.parent.parent.img : this.img,
|
||||
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
||||
areas: this.areas,
|
||||
summon: this.summon
|
||||
},
|
||||
source: {
|
||||
actor: this.actor.uuid,
|
||||
item: this.item.id,
|
||||
action: this.id
|
||||
},
|
||||
itemOrigin: this.item,
|
||||
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
||||
};
|
||||
|
|
|
|||
|
|
@ -109,7 +109,9 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
|
||||
get actionsList() {
|
||||
// No actions on non-characters
|
||||
if (this.actor && this.actor.type !== 'character') return [];
|
||||
if (this.metadata.isInventoryItem && this.actor && this.actor.type !== 'character') {
|
||||
return [];
|
||||
}
|
||||
return this.actions;
|
||||
}
|
||||
|
||||
|
|
|
|||
1
module/data/regionBehavior/_module.mjs
Normal file
1
module/data/regionBehavior/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as applyActiveEffect } from './applyActiveEffect.mjs';
|
||||
40
module/data/regionBehavior/applyActiveEffect.mjs
Normal file
40
module/data/regionBehavior/applyActiveEffect.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
export default class DhApplyActiveEffect extends CONFIG.RegionBehavior.dataModels.applyActiveEffect {
|
||||
static async #getApplicableEffects(token) {
|
||||
const effects = await Promise.all(this.effects.map(foundry.utils.fromUuid));
|
||||
return effects.filter(
|
||||
effect => !effect.system.targetDispositions.size || effect.system.targetDispositions.has(token.disposition)
|
||||
);
|
||||
}
|
||||
|
||||
static async #onTokenEnter(event) {
|
||||
if (!event.user.isSelf) return;
|
||||
const { token, movement } = event.data;
|
||||
const actor = token.actor;
|
||||
if (!actor) return;
|
||||
const resumeMovement = movement ? token.pauseMovement() : undefined;
|
||||
const effects = await DhApplyActiveEffect.#getApplicableEffects.bind(this)(event.data.token);
|
||||
const toCreate = [];
|
||||
for (const effect of effects) {
|
||||
const data = effect.toObject();
|
||||
delete data._id;
|
||||
if (effect.compendium) {
|
||||
data._stats.duplicateSource = null;
|
||||
data._stats.compendiumSource = effect.uuid;
|
||||
} else {
|
||||
data._stats.duplicateSource = effect.uuid;
|
||||
data._stats.compendiumSource = null;
|
||||
}
|
||||
data._stats.exportSource = null;
|
||||
data.origin = this.parent.uuid;
|
||||
toCreate.push(data);
|
||||
}
|
||||
if (toCreate.length) await actor.createEmbeddedDocuments('ActiveEffect', toCreate);
|
||||
await resumeMovement?.();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static events = {
|
||||
...CONFIG.RegionBehavior.dataModels.applyActiveEffect.events,
|
||||
[CONST.REGION_EVENTS.TOKEN_ENTER]: this.#onTokenEnter
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue