Files
DeltaGreen/module/utils/stimulant-effect.js
T
Aaron Lutomski bf4ca9bf88 Implemented Active Effects.
Used AE to implement exhaustion, stimulants, and rest.
2026-05-29 21:07:33 -07:00

167 lines
4.3 KiB
JavaScript

import DG from "../config.js";
import { syncExhaustionEffect } from "./exhaustion-effect.js";
/**
* @param {Actor} actor
* @returns {ActiveEffect[]}
*/
function getStimulantEffects(actor) {
return (
actor.effects?.filter((effect) => effect.getFlag(DG.ID, "stimulant")) ?? []
);
}
/**
* @param {Actor} actor
* @returns {ActiveEffect|undefined}
*/
function getStimulantEffect(actor) {
return getStimulantEffects(actor).find((effect) => !effect.isSuppressed);
}
/**
* @param {Actor} actor
* @returns {boolean}
*/
export function hasActiveStimulantEffect(actor) {
return getStimulantEffects(actor).some((effect) => !effect.isSuppressed);
}
/**
* @param {ActiveEffect} effect
* @returns {number}
*/
function getStimulantRemainingHours(effect) {
effect.updateDuration();
const { remaining, secondsRemaining, units } = effect.duration ?? {};
if (Number.isFinite(remaining) && remaining > 0 && units === "hours") {
return Math.ceil(remaining);
}
if (Number.isFinite(secondsRemaining) && secondsRemaining > 0) {
const { calendar } = game.time;
const components = calendar.difference(
game.time.worldTime + secondsRemaining,
game.time.worldTime,
);
const hours = calendar.componentsToUnit(components, "hour", {
roundFn: "ceil",
});
return Math.max(0, Math.ceil(Math.abs(hours)));
}
return 0;
}
/**
* Persisted manual suppress or an active stimulant AE.
* @param {Actor} actor
* @returns {boolean}
*/
export function getEffectiveSuppressExhaustion(actor) {
const persisted =
actor._source?.system?.physical?.suppressExhaustion ?? false;
return Boolean(persisted) || hasActiveStimulantEffect(actor);
}
/**
* @param {number} hours
* @returns {object}
*/
function buildStimulantEffectData(hours) {
return {
name: game.i18n.localize("DG.Physical.StimulantsEffectName"),
img: "systems/deltagreen/assets/icons/tablet.svg",
transfer: false,
disabled: false,
start: { time: game.time.worldTime },
duration: {
value: hours,
units: "hours",
},
changes: [],
flags: { [DG.ID]: { stimulant: true } },
};
}
/**
* Create or refresh the stimulant AE; duration is max(remaining, newHours).
* @param {Actor} actor
* @param {number} newHours
* @returns {Promise<number>} Hours applied on the stimulant AE.
*/
export async function applyStimulantEffect(actor, newHours) {
if (actor.type !== "agent") return 0;
const rolledHours = Math.max(1, Math.ceil(Number(newHours) || 0));
const existing = getStimulantEffect(actor);
const remainingHours = existing ? getStimulantRemainingHours(existing) : 0;
const hours = Math.max(remainingHours, rolledHours);
const data = buildStimulantEffectData(hours);
if (existing) {
await existing.update({
start: data.start,
duration: data.duration,
changes: data.changes,
disabled: false,
});
return hours;
}
const documentClass = getDocumentClass("ActiveEffect");
await documentClass.create(data, { parent: actor });
return hours;
}
/**
* @param {Actor} actor
* @returns {ActiveEffect[]}
*/
function getExpiredStimulantEffects(actor) {
return getStimulantEffects(actor).filter(
(effect) => effect.duration?.expired,
);
}
/**
* Remove stimulant AEs that have expired.
* @param {Actor} actor
* @returns {Promise<void>}
*/
export async function pruneExpiredStimulantEffects(actor) {
if (actor.type !== "agent") return;
const expired = getExpiredStimulantEffects(actor);
if (!expired.length) return;
await actor.deleteEmbeddedDocuments(
"ActiveEffect",
expired.map((effect) => effect.id),
);
}
/**
* Delete all stimulant AEs on the actor (e.g. after a full rest).
* @param {Actor} actor
* @returns {Promise<void>}
*/
export async function clearStimulantEffects(actor) {
if (actor.type !== "agent") return;
const effects = getStimulantEffects(actor);
if (!effects.length) return;
await actor.deleteEmbeddedDocuments(
"ActiveEffect",
effects.map((effect) => effect.id),
);
}
/**
* @returns {Promise<void>}
*/
export async function pruneAllAgentsExpiredStimulants() {
if (!game.user.isActiveGM) return;
for (const actor of game.actors) {
if (actor.type !== "agent") continue;
await pruneExpiredStimulantEffects(actor);
await syncExhaustionEffect(actor);
}
}