140 lines
4.2 KiB
JavaScript
140 lines
4.2 KiB
JavaScript
const MODULE_ID = "sla-ops-clock";
|
|
const SETTING_KEY = "opsState";
|
|
|
|
function defaultState() {
|
|
return {
|
|
opName: "New SLA Operation",
|
|
stage: "Insertion",
|
|
clock: 0,
|
|
heat: 0,
|
|
collateral: 0,
|
|
pressure: 0,
|
|
trigger: "Media attention spikes",
|
|
notes: ""
|
|
};
|
|
}
|
|
|
|
function getState() {
|
|
return foundry.utils.mergeObject(defaultState(), game.settings.get(MODULE_ID, SETTING_KEY) ?? {});
|
|
}
|
|
|
|
async function setState(nextState) {
|
|
await game.settings.set(MODULE_ID, SETTING_KEY, foundry.utils.mergeObject(defaultState(), nextState ?? {}));
|
|
}
|
|
|
|
function renderChatSummary(state) {
|
|
return `
|
|
<section class="sla-ops-clock-chat">
|
|
<h2>${foundry.utils.escapeHTML(state.opName || "SLA Operation")}</h2>
|
|
<p><strong>Stage:</strong> ${foundry.utils.escapeHTML(state.stage || "Unstated")}</p>
|
|
<p><strong>Clock:</strong> ${state.clock}/8 | <strong>Heat:</strong> ${state.heat} | <strong>Collateral:</strong> ${state.collateral} | <strong>Pressure:</strong> ${state.pressure}</p>
|
|
<p><strong>Escalation Trigger:</strong> ${foundry.utils.escapeHTML(state.trigger || "None")}</p>
|
|
${state.notes ? `<p><strong>GM Notes:</strong> ${foundry.utils.escapeHTML(state.notes)}</p>` : ""}
|
|
</section>
|
|
`;
|
|
}
|
|
|
|
function getHtmlRoot(html) {
|
|
return html instanceof HTMLElement ? html : html?.[0] ?? null;
|
|
}
|
|
|
|
class SlaOpsClockApp extends FormApplication {
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
id: "sla-ops-clock-app",
|
|
title: "SLA Ops Clock",
|
|
template: "modules/sla-ops-clock/templates/ops-clock.hbs",
|
|
classes: ["sla-ops-clock"],
|
|
width: 560,
|
|
height: "auto",
|
|
submitOnChange: false,
|
|
closeOnSubmit: false,
|
|
resizable: true
|
|
});
|
|
}
|
|
|
|
async getData() {
|
|
const state = getState();
|
|
return {
|
|
state,
|
|
segments: Array.from({ length: 8 }, (_value, index) => ({
|
|
index,
|
|
filled: index < Number(state.clock || 0)
|
|
}))
|
|
};
|
|
}
|
|
|
|
activateListeners(html) {
|
|
super.activateListeners(html);
|
|
html.find("[data-action='advance']").on("click", async () => {
|
|
const current = await this._collectState();
|
|
current.clock = Math.min(8, Number(current.clock || 0) + 1);
|
|
current.pressure = Math.min(9, Number(current.pressure || 0) + 1);
|
|
await setState(current);
|
|
this.render(false);
|
|
});
|
|
html.find("[data-action='post']").on("click", async () => {
|
|
const current = await this._collectState();
|
|
await setState(current);
|
|
await ChatMessage.create({
|
|
user: game.user.id,
|
|
speaker: { alias: "SLA Ops Clock" },
|
|
content: renderChatSummary(current)
|
|
});
|
|
});
|
|
html.find("[data-action='reset']").on("click", async () => {
|
|
await setState(defaultState());
|
|
this.render(false);
|
|
});
|
|
}
|
|
|
|
async _collectState() {
|
|
const form = this.form;
|
|
if (!form) return getState();
|
|
const fd = new foundry.applications.ux.FormDataExtended(form);
|
|
return foundry.utils.mergeObject(getState(), fd.object);
|
|
}
|
|
|
|
async _updateObject(_event, formData) {
|
|
await setState(formData);
|
|
ui.notifications.info("SLA Ops Clock saved.");
|
|
}
|
|
|
|
static open() {
|
|
return new SlaOpsClockApp().render(true);
|
|
}
|
|
}
|
|
|
|
function installSceneButton(app, html) {
|
|
if (!game.user?.isGM) return;
|
|
const root = getHtmlRoot(html);
|
|
const header = root?.querySelector(".directory-header .header-actions, .directory-header .action-buttons");
|
|
if (!header || header.querySelector(".sla-ops-clock-open")) return;
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "sla-ops-clock-open";
|
|
button.innerHTML = `<i class="fas fa-gauge-high"></i> Ops Clock`;
|
|
button.addEventListener("click", () => SlaOpsClockApp.open());
|
|
header.appendChild(button);
|
|
}
|
|
|
|
Hooks.once("init", () => {
|
|
game.settings.register(MODULE_ID, SETTING_KEY, {
|
|
scope: "world",
|
|
config: false,
|
|
type: Object,
|
|
default: defaultState()
|
|
});
|
|
|
|
game.settings.registerMenu(MODULE_ID, "opsClockMenu", {
|
|
name: "SLA Ops Clock",
|
|
label: "Open Ops Clock",
|
|
hint: "Track mission heat, collateral, and pressure for the current operation.",
|
|
icon: "fas fa-gauge-high",
|
|
type: SlaOpsClockApp,
|
|
restricted: true
|
|
});
|
|
});
|
|
|
|
Hooks.on("renderSceneDirectory", installSceneButton);
|