[Feature] Countdown Actions (#1302)

* Added countdown actions

* Added a CountdownAutomation setting to enable/disable countdown automation

* Added Looping

* Added characterSpotlight automation

* Countdown max as formula to enable random countdowns

* Updated compendiums with countdowns

* .

* Fixed lightmode colouration

* Raised system version

* Added automation for ActionRolls on countdowns

* Added automation on fear to countdowns

* Corrected attackAction countdown automation

* Added initial countdown upon creating a CountdownAction

* Improved ActionCountdown initial name to be 'Start Countdown'
This commit is contained in:
WBHarry 2025-11-20 11:46:00 +01:00 committed by GitHub
parent 0233979a9f
commit 207220ff7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 1742 additions and 635 deletions

View file

@ -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 * as countdowns from './countdowns.mjs';
export * as actions from './action/_module.mjs';
export * as activeEffects from './activeEffect/_module.mjs';
export * as actors from './actor/_module.mjs';

View file

@ -1,6 +1,7 @@
import AttackAction from './attackAction.mjs';
import BaseAction from './baseAction.mjs';
import BeastformAction from './beastformAction.mjs';
import CountdownAction from './countdownAction.mjs';
import DamageAction from './damageAction.mjs';
import EffectAction from './effectAction.mjs';
import HealingAction from './healingAction.mjs';
@ -10,6 +11,7 @@ import SummonAction from './summonAction.mjs';
export const actionsTypes = {
base: BaseAction,
attack: AttackAction,
countdown: CountdownAction,
damage: DamageAction,
healing: HealingAction,
summon: SummonAction,

View file

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

View file

@ -43,6 +43,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return schemaFields;
}
/**
* The default values to supply to schema fields when they are created in the actionConfig. Defined by implementing classes.
*/
get defaultValues() {
return {};
}
/**
* Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property.
*

View file

@ -0,0 +1,30 @@
import DHBaseAction from './baseAction.mjs';
export default class DhCountdownAction extends DHBaseAction {
static extraSchemas = [...super.extraSchemas, 'countdown'];
get defaultValues() {
return {
...super.defaultValues,
countdown: {
name: this.parent.parent.name,
img: this.img
}
};
}
/** @inheritdoc */
static getSourceConfig(parent) {
const updateSource = game.system.api.data.actions.actionsTypes.base.getSourceConfig(parent);
updateSource.name = game.i18n.localize('DAGGERHEART.ACTIONS.Config.countdown.startCountdown');
updateSource['countdown'] = [
{
...game.system.api.data.countdowns.DhCountdown.defaultCountdown(),
name: parent.parent.name,
img: parent.parent.img
}
];
return updateSource;
}
}

View file

@ -1,3 +1,5 @@
import FormulaField from './fields/formulaField.mjs';
export default class DhCountdowns extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
@ -105,8 +107,8 @@ class DhOldCountdown extends foundry.abstract.DataModel {
type: new fields.SchemaField({
value: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.countdownTypes,
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
choices: CONFIG.DH.GENERAL.countdownProgressionTypes,
initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id,
label: 'DAGGERHEART.GENERAL.type'
}),
label: new fields.StringField({
@ -165,16 +167,22 @@ export class DhCountdown extends foundry.abstract.DataModel {
initial: 1,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.current.label'
}),
max: new fields.NumberField({
max: new FormulaField({
required: true,
integer: true,
initial: 1,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.max.label'
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.max.label',
deterministic: false
}),
looping: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.countdownLoopingTypes,
initial: CONFIG.DH.GENERAL.countdownLoopingTypes.noLooping.id,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.looping.label'
}),
type: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.countdownTypes,
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
choices: CONFIG.DH.GENERAL.countdownProgressionTypes,
initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.type.label'
})
})

View file

@ -1,4 +1,5 @@
export { default as CostField } from './costField.mjs';
export { default as CountdownField } from './countdownField.mjs';
export { default as UsesField } from './usesField.mjs';
export { default as RangeField } from './rangeField.mjs';
export { default as TargetField } from './targetField.mjs';

View file

@ -0,0 +1,88 @@
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../../systemRegistration/socket.mjs';
const fields = foundry.data.fields;
export default class CountdownField extends fields.ArrayField {
constructor(options = {}, context = {}) {
const element = new fields.SchemaField({
...game.system.api.data.countdowns.DhCountdown.defineSchema(),
type: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.countdownBaseTypes,
initial: CONFIG.DH.GENERAL.countdownBaseTypes.encounter.id,
label: 'DAGGERHEART.GENERAL.type'
}),
name: new fields.StringField({
required: true,
initial: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.newCountdown'),
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.name.label'
}),
defaultOwnership: new fields.NumberField({
required: true,
choices: CONFIG.DH.GENERAL.simpleOwnershiplevels,
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT,
label: 'DAGGERHEART.ACTIONS.Config.countdown.defaultOwnership'
})
});
super(element, options, context);
}
/**
* Countdown Action Workflow part.
* Must be called within Action context or similar. Requires a GM online to edit the game setting for countdowns.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
static async execute(config) {
const noGM = !game.users.find(x => x.isGM && x.active);
if (noGM) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
return;
}
const data = { countdowns: {} };
for (let countdown of config.countdowns) {
const { total: max } = await new Roll(countdown.progress.max).evaluate();
data.countdowns[foundry.utils.randomID()] = {
...countdown,
progress: {
...countdown.progress,
current: max,
max: max
}
};
}
await emitAsGM(
GMUpdateEvent.UpdateCountdowns,
async () => {
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
await countdownSetting.updateSource(data);
await game.settings.set(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Countdowns,
countdownSetting.toObject()
),
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }
});
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
},
data,
null,
{
refreshType: RefreshType.Countdown
}
);
}
/**
* Update Action Workflow config object.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
prepareConfig(config) {
config.countdowns = this.countdown;
return config;
}
}

View file

@ -260,7 +260,7 @@ export function ActionMixin(Base) {
origin: origin,
action: { name: this.name, img: this.img, tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'] },
itemOrigin: this.item,
description: this.description || (this.item instanceof Item ? this.item.system.description : "")
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
};
const msg = {
type: 'abilityUse',

View file

@ -18,6 +18,11 @@ export default class DhAutomation extends foundry.abstract.DataModel {
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
})
}),
countdownAutomation: new fields.BooleanField({
required: true,
initial: true,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.countdownAutomation.label'
}),
levelupAuto: new fields.BooleanField({
required: true,
initial: true,