[Feature] Countdown Rols And Randomzie (#1329)

* .

* .

* .

* Raised system version
This commit is contained in:
WBHarry 2025-11-25 00:36:43 +01:00 committed by GitHub
parent 9e0bc3cff1
commit d137e33c3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 281 additions and 87 deletions

View file

@ -1,4 +1,5 @@
import { DhCountdown } from '../../data/countdowns.mjs';
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -26,6 +27,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
toggleCountdownEdit: CountdownEdit.#toggleCountdownEdit,
editCountdownImage: CountdownEdit.#editCountdownImage,
editCountdownOwnership: CountdownEdit.#editCountdownOwnership,
randomiseCountdownStart: CountdownEdit.#randomiseCountdownStart,
removeCountdown: CountdownEdit.#removeCountdown
},
form: { handler: this.updateData, submitOnChange: true }
@ -57,6 +59,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
: 'DAGGERHEART.UI.Countdowns.loop'
: null;
const randomizeValid = !new Roll(countdown.progress.startFormula ?? '').isDeterministic;
acc[key] = {
...countdown,
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].label),
@ -67,6 +70,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
)
},
editing: this.editingCountdowns.has(key),
randomizeValid,
loopTooltip
};
@ -123,18 +127,26 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
// Sync current and max if max is changing and they were equal before
for (const [id, countdown] of Object.entries(settingsData.countdowns ?? {})) {
const existing = this.data.countdowns[id];
const wasEqual = existing && existing.progress.current === existing.progress.max;
if (wasEqual && countdown.progress.max !== existing.progress.max) {
countdown.progress.current = countdown.progress.max;
} else {
countdown.progress.current = Math.min(countdown.progress.current, countdown.progress.max);
}
countdown.progress.current = this.getMatchingCurrentValue(
existing,
countdown.progress.start,
countdown.progress.current
);
}
this.hideNewCountdowns = hideNewCountdowns;
this.updateSetting(settingsData);
}
getMatchingCurrentValue(oldCount, newStart, newCurrent) {
const wasEqual = oldCount && oldCount.progress.current === oldCount.progress.start;
if (wasEqual && newStart !== oldCount.progress.start) {
return newStart;
} else {
return Math.min(newCurrent, newStart);
}
}
async gmSetSetting(data) {
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
game.socket.emit(`system.${CONFIG.DH.id}`, {
@ -190,6 +202,21 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
this.updateSetting({ [`countdowns.${button.dataset.countdownId}`]: data });
}
static async #randomiseCountdownStart(_, button) {
const countdown = this.data.countdowns[button.dataset.countdownId];
const roll = await new Roll(countdown.progress.startFormula).roll();
const message = await roll.toMessage({ title: 'Countdown' });
await waitForDiceSoNice(message);
await this.updateSetting({
[`countdowns.${button.dataset.countdownId}.progress`]: {
start: roll.total,
current: this.getMatchingCurrentValue(countdown, roll.total, countdown.progress.current)
}
});
this.render();
}
static async #removeCountdown(event, button) {
const { countdownId } = button.dataset;

View file

@ -1,3 +1,4 @@
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -123,13 +124,14 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
: 'DAGGERHEART.UI.Countdowns.loop'
: null;
const loopDisabled =
!countdownEditable || (isLooping && (countdown.progress.current > 0 || countdown.progress.max === '0'));
!countdownEditable ||
(isLooping && (countdown.progress.current > 0 || countdown.progress.start === '0'));
acc[key] = {
...countdown,
editable: countdownEditable,
noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0,
shouldLoop: isLooping && countdown.progress.current === 0 && countdown.progress.max > 0,
shouldLoop: isLooping && countdown.progress.current === 0 && countdown.progress.start > 0,
loopDisabled: isLooping ? loopDisabled : null,
loopTooltip: isLooping && game.i18n.localize(loopTooltip)
};
@ -182,16 +184,27 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
const countdown = settings.countdowns[target.id];
let progressMax = countdown.progress.start;
let message = null;
if (countdown.progress.startFormula) {
const roll = await new Roll(countdown.progress.startFormula).evaluate();
progressMax = roll.total;
message = await roll.toMessage();
}
const newMax =
countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
? Number(countdown.progress.max) + 1
? Number(progressMax) + 1
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
? Math.max(Number(countdown.progress.max) - 1, 0)
: countdown.progress.max;
? Math.max(Number(progressMax) - 1, 0)
: progressMax;
await waitForDiceSoNice(message);
await settings.updateSource({
[`countdowns.${target.id}.progress`]: {
current: newMax,
max: newMax
start: newMax
}
});
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
@ -205,7 +218,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
const countdown = settings.countdowns[target.id];
const newCurrent = increase
? Math.min(countdown.progress.current + 1, countdown.progress.max)
? Math.min(countdown.progress.current + 1, countdown.progress.start)
: Math.max(countdown.progress.current - 1, 0);
await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent });
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {

View file

@ -8,7 +8,10 @@ export default class DhCountdownAction extends DHBaseAction {
...super.defaultValues,
countdown: {
name: this.parent.parent.name,
img: this.img
img: this.img,
progress: {
startFormula: '1'
}
}
};
}
@ -21,10 +24,26 @@ export default class DhCountdownAction extends DHBaseAction {
{
...game.system.api.data.countdowns.DhCountdown.defaultCountdown(),
name: parent.parent.name,
img: parent.parent.img
img: parent.parent.img,
progress: {
startFormula: '1'
}
}
];
return updateSource;
}
/** @inheritDoc */
static migrateData(source) {
for (const countdown of source.countdown) {
if (countdown.progress.max) {
countdown.progress.startFormula = countdown.progress.max;
countdown.progress.start = 1;
countdown.progress.max = null;
}
}
return super.migrateData(source);
}
}

View file

@ -167,10 +167,15 @@ export class DhCountdown extends foundry.abstract.DataModel {
initial: 1,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.current.label'
}),
max: new FormulaField({
start: new fields.NumberField({
required: true,
integer: true,
initial: 1,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.max.label',
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.start.label',
deterministic: false
}),
startFormula: new FormulaField({
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.startFormula.label',
deterministic: false
}),
looping: new fields.StringField({
@ -206,7 +211,7 @@ export class DhCountdown extends foundry.abstract.DataModel {
ownership: ownership,
progress: {
current: 1,
max: 1
start: 1
}
};
}
@ -225,4 +230,15 @@ export class DhCountdown extends foundry.abstract.DataModel {
return acc;
}, {});
}
/** @inheritDoc */
static migrateData(source) {
if (source.progress.max) {
source.progress.start = Number(source.progress.max);
source.progress.max = null;
source.progress.startFormula = null;
}
return super.migrateData(source);
}
}

View file

@ -40,18 +40,40 @@ export default class CountdownField extends fields.ArrayField {
}
const data = { countdowns: {} };
const countdownMessages = [];
for (let countdown of config.countdowns) {
const { total: max } = await new Roll(countdown.progress.max).evaluate();
let startFormula = countdown.progress.startFormula ? countdown.progress.startFormula : null;
let countdownStart = startFormula ?? '1';
if (startFormula) {
const roll = await new Roll(startFormula).roll();
if (roll.dice.length > 0) {
countdownStart = roll.total;
const message = await roll.toMessage();
countdownMessages.push(message);
} else {
startFormula = null;
}
}
data.countdowns[foundry.utils.randomID()] = {
...countdown,
progress: {
...countdown.progress,
current: max,
max: max
current: countdownStart,
start: countdownStart,
startFormula
}
};
}
if (game.modules.get('dice-so-nice')?.active) {
await Promise.all(
countdownMessages.map(message => {
return game.dice3d.waitFor3DAnimationByMessageID(message.id);
})
);
}
await emitAsGM(
GMUpdateEvent.UpdateCountdowns,
async () => {

View file

@ -445,3 +445,9 @@ export function itemIsIdentical(a, b) {
return compendiumSource && name & description;
}
export async function waitForDiceSoNice(message) {
if (message && game.modules.get('dice-so-nice')?.active) {
await game.dice3d.waitFor3DAnimationByMessageID(message.id);
}
}