Added countdown automation

This commit is contained in:
WBHarry 2025-06-21 14:13:26 +02:00
parent b6f78c5102
commit 2299141442
18 changed files with 500 additions and 53 deletions

View file

@ -1,15 +1,22 @@
import { countdownTypes } from '../config/generalConfig.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(basePath) {
super();
super({});
this.basePath = basePath;
}
get title() {
return game.i18n.format('DAGGERHEART.Countdown.Title', {
type: game.i18n.localize(`DAGGERHEART.Countdown.Types.${this.basePath}`)
});
}
static DEFAULT_OPTIONS = {
id: 'countdown',
classes: [],
classes: ['daggerheart', 'dh-style', 'countdown'],
tag: 'form',
window: {
frame: true,
@ -19,7 +26,8 @@ export default class Countdowns extends HandlebarsApplicationMixin(ApplicationV2
},
actions: {
addCountdown: this.addCountdown,
removeCountdown: this.removeCountdown
removeCountdown: this.removeCountdown,
editImage: this.onEditImage
},
form: { handler: this.updateData, submitOnChange: true }
};
@ -30,6 +38,22 @@ export default class Countdowns extends HandlebarsApplicationMixin(ApplicationV2
}
};
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelectorAll('.mini-countdown-container').forEach(element => {
element.addEventListener('click', event => this.updateCountdownValue.bind(this)(event, true));
element.addEventListener('contextmenu', event => this.updateCountdownValue.bind(this)(event, false));
});
}
async _onFirstRender(context, options) {
super._onFirstRender(context, options);
this.element.querySelector('.expanded-view').classList.toggle('hidden');
this.element.querySelector('.minimized-view').classList.toggle('hidden');
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
const countdownData = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath];
@ -38,6 +62,7 @@ export default class Countdowns extends HandlebarsApplicationMixin(ApplicationV2
context.source = countdownData.toObject();
context.systemFields = countdownData.schema.fields;
context.countdownFields = context.systemFields.countdowns.element.fields;
context.minimized = this.minimized || _options.isFirstRender;
return context;
}
@ -52,6 +77,63 @@ export default class Countdowns extends HandlebarsApplicationMixin(ApplicationV2
this.render();
}
async minimize() {
await super.minimize();
this.element.querySelector('.expanded-view').classList.toggle('hidden');
this.element.querySelector('.minimized-view').classList.toggle('hidden');
}
async maximize() {
if (this.minimized) {
this.element.querySelector('.expanded-view').classList.toggle('hidden');
this.element.querySelector('.minimized-view').classList.toggle('hidden');
}
await super.maximize();
}
static onEditImage(_, target) {
const setting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath];
const current = setting.countdowns[target.dataset.countdown].img;
const fp = new FilePicker({
current,
type: 'image',
callback: async path => this.updateImage.bind(this)(path, target.dataset.countdown),
top: this.position.top + 40,
left: this.position.left + 10
});
return fp.browse();
}
async updateImage(path, countdown) {
const setting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
await setting.updateSource({
[`${this.basePath}.countdowns.${countdown}.img`]: path
});
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, setting);
this.render();
}
async updateCountdownValue(event, increase) {
const countdownSetting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
const countdown = countdownSetting[this.basePath].countdowns[event.currentTarget.dataset.countdown];
const currentValue = countdown.progress.current;
if (increase && currentValue === countdown.progress.max) return;
if (!increase && currentValue === 0) return;
await countdownSetting.updateSource({
[`${this.basePath}.countdowns.${event.currentTarget.dataset.countdown}.progress.current`]: increase
? currentValue + 1
: currentValue - 1
});
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, countdownSetting.toObject());
this.render();
}
static async addCountdown() {
const countdownSetting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
await countdownSetting.updateSource({
@ -71,4 +153,75 @@ export default class Countdowns extends HandlebarsApplicationMixin(ApplicationV2
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, countdownSetting.toObject());
this.render();
}
async open() {
await this.render(true);
if (
Object.keys(game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath].countdowns)
.length > 0
) {
this.minimize();
}
}
}
export class NarrativeCountdowns extends Countdowns {
constructor() {
super('narrative');
}
static DEFAULT_OPTIONS = {
id: 'narrative-countdowns'
};
}
export class EncounterCountdowns extends Countdowns {
constructor() {
super('encounter');
}
static DEFAULT_OPTIONS = {
id: 'encounter-countdowns'
};
}
export const registerCountdownHooks = () => {
const updateCountdowns = async shouldIncrease => {
if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation).countdowns) {
const countdownSetting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
for (let countdownCategoryKey in countdownSetting) {
const countdownCategory = countdownSetting[countdownCategoryKey];
for (let countdownKey in countdownCategory.countdowns) {
const countdown = countdownCategory.countdowns[countdownKey];
if (shouldIncrease(countdown)) {
await countdownSetting.updateSource({
[`${countdownCategoryKey}.countdowns.${countdownKey}.progress.current`]:
countdown.progress.current + 1
});
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, countdownSetting);
foundry.applications.instances.get(`${countdownCategoryKey}-countdowns`)?.render();
}
}
}
}
};
Hooks.on(SYSTEM.HOOKS.characterAttack, async () => {
updateCountdowns(countdown => {
return (
countdown.progress.type.value === countdownTypes.characterAttack.id &&
countdown.progress.current < countdown.progress.max
);
});
});
Hooks.on(SYSTEM.HOOKS.spotlight, async () => {
updateCountdowns(countdown => {
return (
countdown.progress.type.value === countdownTypes.spotlight.id &&
countdown.progress.current < countdown.progress.max
);
});
});
};

View file

@ -371,7 +371,11 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
static async attackRoll(event, button) {
const weapon = await fromUuid(button.dataset.weapon);
if (!weapon) return;
weapon.use(event);
const wasUsed = await weapon.use(event);
if (wasUsed) {
Hooks.callAll(SYSTEM.HOOKS.characterAttack, {});
}
}
static openLevelUp() {

View file

@ -369,6 +369,10 @@ export const countdownTypes = {
id: 'spotlight',
label: 'DAGGERHEART.Countdown.Type.Spotlight'
},
characterAttack: {
id: 'characterAttack',
label: 'DAGGERHEART.Countdown.Type.CharacterAttack'
},
custom: {
id: 'custom',
label: 'DAGGERHEART.Countdown.Type.Custom'

View file

@ -0,0 +1,4 @@
export const hooks = {
characterAttack: 'characterAttackHook',
spotlight: 'spotlightHook'
};

View file

@ -3,6 +3,7 @@ import * as DOMAIN from './domainConfig.mjs';
import * as ACTOR from './actorConfig.mjs';
import * as ITEM from './itemConfig.mjs';
import * as SETTINGS from './settingsConfig.mjs';
import { hooks as HOOKS } from './hooksConfig.mjs';
import * as EFFECTS from './effectConfig.mjs';
import * as ACTIONS from './actionConfig.mjs';
@ -15,6 +16,7 @@ export const SYSTEM = {
ACTOR,
ITEM,
SETTINGS,
HOOKS,
EFFECTS,
ACTIONS
};

View file

@ -6,7 +6,7 @@ export default class DhCountdowns extends foundry.abstract.DataModel {
return {
narrative: new fields.EmbeddedDataField(DhCountdownData),
combat: new fields.EmbeddedDataField(DhCountdownData)
encounter: new fields.EmbeddedDataField(DhCountdownData)
};
}
@ -32,6 +32,11 @@ class DhCountdown extends foundry.abstract.DataModel {
required: true,
label: 'DAGGERHEART.Countdown.FIELDS.countdowns.element.name.label'
}),
img: new fields.FilePathField({
categories: ['IMAGE'],
base64: false,
initial: 'icons/magic/time/hourglass-yellow-green.webp'
}),
progress: new fields.SchemaField({
current: new fields.NumberField({
required: true,
@ -42,7 +47,7 @@ class DhCountdown extends foundry.abstract.DataModel {
max: new fields.NumberField({
required: true,
integer: true,
initial: 0,
initial: 1,
label: 'DAGGERHEART.Countdown.FIELDS.countdowns.element.progress.max.label'
}),
type: new fields.SchemaField({

View file

@ -5,7 +5,8 @@ export default class DhAutomation extends foundry.abstract.DataModel {
const fields = foundry.data.fields;
return {
hope: new fields.BooleanField({ required: true, initial: false }),
actionPoints: new fields.BooleanField({ required: true, initial: false })
actionPoints: new fields.BooleanField({ required: true, initial: false }),
countdowns: new fields.BooleanField({ requireD: true, initial: false })
};
}
}

View file

@ -301,7 +301,7 @@ export default class DhpActor extends Actor {
);
if (this.type === 'character') {
const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope);
const automateHope = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation).hope;
if (automateHope && result.hopeUsed) {
await this.update({
@ -330,7 +330,7 @@ export default class DhpActor extends Actor {
hope = roll.dice[0].results[0].result;
fear = roll.dice[1].results[0].result;
if (
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope) &&
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation).hope &&
config.roll.type === 'action'
) {
if (hope > fear) {

View file

@ -1,9 +1,12 @@
import { EncounterCountdowns } from '../applications/countdowns.mjs';
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
static DEFAULT_OPTIONS = {
actions: {
requestSpotlight: this.requestSpotlight,
toggleSpotlight: this.toggleSpotlight,
setActionTokens: this.setActionTokens
setActionTokens: this.setActionTokens,
openCountdowns: this.openCountdowns
}
};
@ -83,6 +86,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
.map(x => x.id)
.indexOf(combatantId);
if (this.viewed.turn !== toggleTurn) Hooks.callAll(SYSTEM.HOOKS.spotlight, {});
await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn });
await combatant.update({ 'system.spotlight.requesting': false });
}
@ -97,4 +102,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
await combatant.update({ 'system.actionTokens': newIndex });
this.render();
}
static openCountdowns() {
new EncounterCountdowns().open();
}
}