Make attributes rollable using Foundry macros

This commit is contained in:
Oliver 2026-04-28 18:01:58 -06:00
parent 8f67bff2ec
commit 0cd8af77b2
6 changed files with 85 additions and 21 deletions

View file

@ -37,6 +37,7 @@ export class PlayerSheet extends
createEmbeddedItem: this.#createEmbeddedItem, createEmbeddedItem: this.#createEmbeddedItem,
configureSheet: this.#configureSheet, configureSheet: this.#configureSheet,
toggleExpand: this.#toggleExpand, toggleExpand: this.#toggleExpand,
executeTrigger: this.#executeTrigger,
}, },
}; };
@ -462,5 +463,16 @@ export class PlayerSheet extends
const item = await Item.create(data, { parent: this.actor }); const item = await Item.create(data, { parent: this.actor });
item?.sheet?.render({ force: true }); item?.sheet?.render({ force: true });
}; };
/**
* Executes an embedded item's triggering Macro if it has one attached to it.
*
* @this {PlayerSheet}
*/
static async #executeTrigger(event, target) {
const { itemUuid } = target.closest(`[data-item-uuid]`)?.dataset ?? {};
const item = await fromUuid(itemUuid);
await item?.system.execute?.();
};
// #endregion Actions // #endregion Actions
}; };

View file

@ -5,7 +5,7 @@ import { clamp } from "../../utils/clamp.mjs";
const { getProperty, hasProperty, setProperty } = foundry.utils; const { getProperty, hasProperty, setProperty } = foundry.utils;
export class AttributeItemData extends foundry.abstract.TypeDataModel { export class AttributeItemData extends foundry.abstract.TypeDataModel {
// #region Schema // MARK: Schema
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { return {
@ -43,7 +43,6 @@ export class AttributeItemData extends foundry.abstract.TypeDataModel {
}), }),
}; };
}; };
// #endregion Schema
// #region Lifecycle // #region Lifecycle
async _preCreate(data, options, user) { async _preCreate(data, options, user) {
@ -123,5 +122,30 @@ export class AttributeItemData extends foundry.abstract.TypeDataModel {
}; };
return null; return null;
}; };
/**
* Executes the macro associated with this item, if the macro cannot be
* found or if the user does not permission to execute it, it will not be
* executed. This also provides some extra context into the roll data for chat
* macros, so that they can refer to the min/value/max properties of this
* specific item without actually needing to know which item called the macro.
*/
async execute() {
const macro = await fromUuid(this.trigger);
if (!macro || !macro.canExecute) { return };
// Provide the chat-specific context when required
if (macro.type === `chat`) {
Hooks.once(`taf.getRollData`, (data) => {
data.active = {
min: this.min,
value: this.value,
max: this.max,
};
});
};
await macro?.execute({ item });
};
// #endregion Methods // #endregion Methods
}; };

View file

@ -1,6 +1,7 @@
import { toPrecision } from "../../utils/roundToPrecision.mjs"; import { toPrecision } from "../../utils/roundToPrecision.mjs";
export class GenericItemData extends foundry.abstract.TypeDataModel { export class GenericItemData extends foundry.abstract.TypeDataModel {
// MARK: Schema
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { return {
@ -35,6 +36,7 @@ export class GenericItemData extends foundry.abstract.TypeDataModel {
}; };
}; };
// #region Methods
/** /**
* Calculates the total weight of the item based on the quantity of it, this * Calculates the total weight of the item based on the quantity of it, this
* rounds the number to the nearest 2 decimal places. * rounds the number to the nearest 2 decimal places.
@ -43,4 +45,29 @@ export class GenericItemData extends foundry.abstract.TypeDataModel {
const value = this.weight * this.quantity; const value = this.weight * this.quantity;
return toPrecision(Math.max(value, 0), 2); return toPrecision(Math.max(value, 0), 2);
}; };
/**
* Executes the macro associated with this item, if the macro cannot be
* found or if the user does not permission to execute it, it will not be
* executed. This also provides some extra context into the roll data for chat
* macros, so that they can refer to some properties of this specific item
* without actually needing to know which item called the macro.
*/
async execute() {
const macro = await fromUuid(this.trigger);
if (!macro || !macro.canExecute) { return };
// Provide the chat-specific context when required
if (macro.type === `chat`) {
Hooks.once(`taf.getRollData`, (data) => {
data.active = {
quantity: this.quantity,
equipped: this.equipped ? 1 : 0,
};
});
};
await macro?.execute({ item });
};
// #endregion Methods
}; };

View file

@ -66,28 +66,12 @@ export class TAFActor extends Actor {
// #region Roll Data // #region Roll Data
getRollData() { getRollData() {
/*
All properties assigned during this phase of the roll data prep can potentially
be overridden by users creating attributes of the same key, if users shouldn't
be able to override, assign the property before the return of this function.
*/
const data = { const data = {
carryCapacity: this.system.carryCapacity ?? null, carryCapacity: this.system.carryCapacity ?? null,
...this.system.attr,
}; };
if (`attr` in this.system) { Hooks.call(`taf.getRollData`, data, this);
for (const attrID in this.system.attr) {
const attr = this.system.attr[attrID];
if (attr.isRange) {
data[attrID] = {
value: attr.value,
max: attr.max,
};
} else {
data[attrID] = attr.value;
};
};
};
return data; return data;
}; };

View file

@ -6,7 +6,16 @@
data-foreign-uuid="{{ attr.uuid }}" data-foreign-uuid="{{ attr.uuid }}"
> >
<legend> <legend>
{{ attr.name }} {{#if attr.system.trigger}}
<button
type="button"
data-action="executeTrigger"
>
{{ attr.name }}
</button>
{{else}}
{{ attr.name }}
{{/if}}
</legend> </legend>
<div class="attr-range"> <div class="attr-range">
{{#if attr.system.isRange }} {{#if attr.system.isRange }}

View file

@ -5,6 +5,14 @@
<div class="title grow"> <div class="title grow">
<span class="name">{{ name }}</span> <span class="name">{{ name }}</span>
</div> </div>
{{#if system.trigger}}
<button
type="button"
data-action="executeTrigger"
>
Roll
</button>
{{/if}}
<input <input
type="number" type="number"
id="{{uuid}}-value" id="{{uuid}}-value"