585 lines
22 KiB
JavaScript
585 lines
22 KiB
JavaScript
const MODULE_ID = "sla-shift-ledger";
|
|
const STATE_KEY = "campaignState";
|
|
const CONFIG_KEY = "billingConfig";
|
|
const ACTOR_FLAG = "actorState";
|
|
|
|
const SHIFTS = [
|
|
{ key: "morning", label: "Morning Shift", short: "Morning" },
|
|
{ key: "afternoon", label: "Afternoon Shift", short: "Afternoon" },
|
|
{ key: "evening", label: "Evening Shift", short: "Evening" },
|
|
{ key: "night", label: "Night Shift", short: "Night" }
|
|
];
|
|
|
|
const DEFAULT_ACTIVITIES = [
|
|
"Investigation",
|
|
"Resting",
|
|
"Normal Social Time",
|
|
"Transit",
|
|
"Fieldwork",
|
|
"Surveillance",
|
|
"Paperwork",
|
|
"Medical Recovery",
|
|
"Training",
|
|
"Downtime"
|
|
];
|
|
|
|
function defaultCampaignState() {
|
|
return {
|
|
day: 1000,
|
|
shift: 0
|
|
};
|
|
}
|
|
|
|
function defaultConfig() {
|
|
return {
|
|
activityOptions: [...DEFAULT_ACTIVITIES],
|
|
housingLabels: [
|
|
"Squalid",
|
|
"Bare Minimum",
|
|
"Cheap Cubicle",
|
|
"Basic",
|
|
"Steady",
|
|
"Comfortable",
|
|
"Professional",
|
|
"High Grade",
|
|
"Luxury",
|
|
"Executive"
|
|
],
|
|
housingCosts: Array.from({ length: 10 }, (_value, index) => (index + 1) * 50),
|
|
livingMultiplier: 0.5,
|
|
autoChargeWeekly: true
|
|
};
|
|
}
|
|
|
|
function defaultActorState() {
|
|
return {
|
|
housingTier: 1,
|
|
schedule: {},
|
|
ledger: [],
|
|
lastBilledWeek: 0
|
|
};
|
|
}
|
|
|
|
function getState() {
|
|
return foundry.utils.mergeObject(defaultCampaignState(), game.settings.get(MODULE_ID, STATE_KEY) ?? {});
|
|
}
|
|
|
|
function getConfig() {
|
|
const cfg = foundry.utils.mergeObject(defaultConfig(), game.settings.get(MODULE_ID, CONFIG_KEY) ?? {});
|
|
cfg.activityOptions = normalizeActivityOptions(cfg.activityOptions);
|
|
cfg.housingLabels = Array.from({ length: 10 }, (_value, index) => String(cfg.housingLabels?.[index] ?? defaultConfig().housingLabels[index]).trim() || defaultConfig().housingLabels[index]);
|
|
cfg.housingCosts = Array.from({ length: 10 }, (_value, index) => Math.max(0, Number(cfg.housingCosts?.[index] ?? ((index + 1) * 50)) || 0));
|
|
cfg.livingMultiplier = Math.max(0, Number(cfg.livingMultiplier ?? 0.5) || 0);
|
|
cfg.autoChargeWeekly = cfg.autoChargeWeekly !== false;
|
|
return cfg;
|
|
}
|
|
|
|
async function setState(nextState) {
|
|
await game.settings.set(MODULE_ID, STATE_KEY, foundry.utils.mergeObject(defaultCampaignState(), nextState ?? {}));
|
|
}
|
|
|
|
async function setConfig(nextConfig) {
|
|
await game.settings.set(MODULE_ID, CONFIG_KEY, foundry.utils.mergeObject(defaultConfig(), nextConfig ?? {}));
|
|
}
|
|
|
|
function normalizeActivityOptions(value) {
|
|
if (Array.isArray(value)) {
|
|
const cleaned = value.map((entry) => String(entry ?? "").trim()).filter(Boolean);
|
|
return cleaned.length ? cleaned : [...DEFAULT_ACTIVITIES];
|
|
}
|
|
const cleaned = String(value ?? "")
|
|
.split(/\n+/)
|
|
.map((entry) => entry.trim())
|
|
.filter(Boolean);
|
|
return cleaned.length ? cleaned : [...DEFAULT_ACTIVITIES];
|
|
}
|
|
|
|
function getWeekFromDay(day) {
|
|
const numericDay = Math.max(0, Number(day ?? 0) || 0);
|
|
return Math.floor(numericDay / 7) + 1;
|
|
}
|
|
|
|
function getShift(index) {
|
|
return SHIFTS[Math.max(0, Math.min(SHIFTS.length - 1, Number(index ?? 0) || 0))] ?? SHIFTS[0];
|
|
}
|
|
|
|
function buildCampaignStamp(state = getState()) {
|
|
return `Day ${state.day} / ${getShift(state.shift).label}`;
|
|
}
|
|
|
|
function getActorState(actor) {
|
|
return foundry.utils.mergeObject(defaultActorState(), actor.getFlag(MODULE_ID, ACTOR_FLAG) ?? {});
|
|
}
|
|
|
|
async function setActorState(actor, nextState) {
|
|
await actor.setFlag(MODULE_ID, ACTOR_FLAG, foundry.utils.mergeObject(defaultActorState(), nextState ?? {}));
|
|
}
|
|
|
|
function normalizeDaySchedule(scheduleMap, day) {
|
|
const next = foundry.utils.deepClone(scheduleMap ?? {});
|
|
const key = String(day);
|
|
const existing = Array.isArray(next[key]) ? next[key] : [];
|
|
next[key] = Array.from({ length: SHIFTS.length }, (_value, index) => String(existing[index] ?? DEFAULT_ACTIVITIES[0] ?? "Investigation"));
|
|
return next;
|
|
}
|
|
|
|
function calculateWeeklyCosts(housingTier, config = getConfig()) {
|
|
const index = Math.max(0, Math.min(9, Number(housingTier ?? 1) - 1));
|
|
const housing = Number(config.housingCosts?.[index] ?? 0) || 0;
|
|
const living = Math.round(housing * (Number(config.livingMultiplier ?? 0.5) || 0) * 100) / 100;
|
|
return {
|
|
label: String(config.housingLabels?.[index] ?? `Tier ${index + 1}`),
|
|
housing,
|
|
living,
|
|
total: Math.round((housing + living) * 100) / 100
|
|
};
|
|
}
|
|
|
|
function getViewedDay(app) {
|
|
const currentDay = Number(getState().day ?? 1000) || 1000;
|
|
const stored = Number(app?._slaShiftLedgerViewedDay ?? currentDay) || currentDay;
|
|
return Math.max(0, stored);
|
|
}
|
|
|
|
function getHtmlRoot(html) {
|
|
return html instanceof HTMLElement ? html : html?.[0] ?? null;
|
|
}
|
|
|
|
function getCredits(actor) {
|
|
return Number(actor.system?.credits?.value ?? 0) || 0;
|
|
}
|
|
|
|
async function applyWeeklyBilling(targetWeek = getWeekFromDay(getState().day), { force = false } = {}) {
|
|
const config = getConfig();
|
|
const state = getState();
|
|
const actors = game.actors.filter((actor) => actor.type === "character");
|
|
|
|
for (const actor of actors) {
|
|
const actorState = getActorState(actor);
|
|
if (!force && Number(actorState.lastBilledWeek ?? 0) >= Number(targetWeek)) continue;
|
|
|
|
const costs = calculateWeeklyCosts(actorState.housingTier, config);
|
|
const currentCredits = getCredits(actor);
|
|
const newCredits = Math.round((currentCredits - costs.total) * 100) / 100;
|
|
const entry = {
|
|
week: targetWeek,
|
|
day: state.day,
|
|
stamp: buildCampaignStamp(state),
|
|
housingTier: actorState.housingTier,
|
|
housingLabel: costs.label,
|
|
housing: costs.housing,
|
|
living: costs.living,
|
|
total: costs.total,
|
|
balance: newCredits
|
|
};
|
|
|
|
const ledger = [entry, ...(Array.isArray(actorState.ledger) ? actorState.ledger : [])].slice(0, 24);
|
|
await setActorState(actor, {
|
|
...actorState,
|
|
ledger,
|
|
lastBilledWeek: Number(targetWeek)
|
|
});
|
|
await actor.update({ "system.credits.value": newCredits });
|
|
}
|
|
}
|
|
|
|
async function advanceShift(delta = 1) {
|
|
const state = getState();
|
|
const previousDay = Number(state.day ?? 1000) || 1000;
|
|
const previousWeek = getWeekFromDay(previousDay);
|
|
let nextShift = Number(state.shift ?? 0) + Number(delta ?? 0);
|
|
let nextDay = previousDay;
|
|
|
|
while (nextShift < 0) {
|
|
nextShift += SHIFTS.length;
|
|
nextDay = Math.max(0, nextDay - 1);
|
|
}
|
|
while (nextShift >= SHIFTS.length) {
|
|
nextShift -= SHIFTS.length;
|
|
nextDay += 1;
|
|
}
|
|
|
|
const nextState = { day: nextDay, shift: nextShift };
|
|
await setState(nextState);
|
|
|
|
const nextWeek = getWeekFromDay(nextDay);
|
|
if (delta > 0 && nextWeek > previousWeek && getConfig().autoChargeWeekly) {
|
|
await applyWeeklyBilling(nextWeek);
|
|
}
|
|
|
|
renderCampaignBanner();
|
|
ui.notifications.info(`Campaign time advanced to ${buildCampaignStamp(nextState)}.`);
|
|
}
|
|
|
|
function renderCampaignBanner() {
|
|
let host = document.querySelector("#sla-shift-ledger-banner");
|
|
if (!host) {
|
|
host = document.createElement("section");
|
|
host.id = "sla-shift-ledger-banner";
|
|
host.className = "sla-shift-ledger-banner";
|
|
document.body.appendChild(host);
|
|
}
|
|
|
|
const state = getState();
|
|
const weekNumber = getWeekFromDay(state.day);
|
|
const currentShift = getShift(state.shift);
|
|
|
|
host.innerHTML = `
|
|
<div class="sla-shift-ledger-banner-inner">
|
|
<div class="sla-shift-ledger-banner-meta">
|
|
<div class="sla-shift-ledger-banner-kicker">SLA Campaign Clock</div>
|
|
<div class="sla-shift-ledger-banner-title">Day ${state.day} · Week ${weekNumber}</div>
|
|
<div class="sla-shift-ledger-banner-subtitle">${currentShift.label}</div>
|
|
</div>
|
|
<div class="sla-shift-ledger-banner-main">
|
|
<div class="sla-shift-ledger-shifts">
|
|
${SHIFTS.map((shift, index) => `
|
|
<div class="sla-shift-ledger-shift ${index === Number(state.shift) ? "is-active" : ""}">
|
|
${shift.short}
|
|
</div>
|
|
`).join("")}
|
|
</div>
|
|
<div class="sla-shift-ledger-banner-controls">
|
|
${game.user?.isGM ? `
|
|
<button type="button" class="sla-shift-ledger-button" data-action="prev">Prev</button>
|
|
<button type="button" class="sla-shift-ledger-button" data-action="next">Next</button>
|
|
<button type="button" class="sla-shift-ledger-button" data-action="open">Open</button>
|
|
` : ""}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
if (game.user?.isGM) {
|
|
host.querySelector("[data-action='prev']")?.addEventListener("click", () => advanceShift(-1));
|
|
host.querySelector("[data-action='next']")?.addEventListener("click", () => advanceShift(1));
|
|
host.querySelector("[data-action='open']")?.addEventListener("click", () => SlaShiftLedgerApp.open());
|
|
}
|
|
}
|
|
|
|
class SlaShiftLedgerApp extends FormApplication {
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
id: "sla-shift-ledger-app",
|
|
title: "SLA Shift Ledger",
|
|
template: "modules/sla-shift-ledger/templates/timekeeper.hbs",
|
|
classes: ["sla-shift-ledger", "mosh", "sheet"],
|
|
width: 760,
|
|
height: "auto",
|
|
submitOnChange: false,
|
|
closeOnSubmit: false,
|
|
resizable: true
|
|
});
|
|
}
|
|
|
|
async getData() {
|
|
const state = getState();
|
|
const config = getConfig();
|
|
return {
|
|
state,
|
|
config,
|
|
weekNumber: getWeekFromDay(state.day),
|
|
currentShiftLabel: getShift(state.shift).label,
|
|
activityOptionsText: config.activityOptions.join("\n"),
|
|
housingLevels: config.housingCosts.map((cost, index) => ({ tier: index + 1, cost, label: config.housingLabels[index] })),
|
|
shifts: SHIFTS.map((shift, index) => ({
|
|
...shift,
|
|
index,
|
|
selected: index === Number(state.shift),
|
|
active: index === Number(state.shift)
|
|
}))
|
|
};
|
|
}
|
|
|
|
activateListeners(html) {
|
|
super.activateListeners(html);
|
|
html.find("[data-action='prev']").on("click", () => advanceShift(-1));
|
|
html.find("[data-action='next']").on("click", () => advanceShift(1));
|
|
html.find("[data-action='bill']").on("click", async () => {
|
|
await applyWeeklyBilling(getWeekFromDay(getState().day), { force: true });
|
|
ui.notifications.info("Weekly billing applied to all character actors.");
|
|
});
|
|
}
|
|
|
|
async _updateObject(_event, formData) {
|
|
const housingCosts = Array.from({ length: 10 }, (_value, index) => Math.max(0, Number(formData[`housingCost${index + 1}`] ?? 0) || 0));
|
|
const housingLabels = Array.from({ length: 10 }, (_value, index) => String(formData[`housingLabel${index + 1}`] ?? "").trim() || defaultConfig().housingLabels[index]);
|
|
await setState({
|
|
day: Math.max(0, Number(formData.day ?? 1000) || 1000),
|
|
shift: Math.max(0, Math.min(3, Number(formData.shift ?? 0) || 0))
|
|
});
|
|
await setConfig({
|
|
activityOptions: normalizeActivityOptions(formData.activityOptions),
|
|
housingLabels,
|
|
housingCosts,
|
|
livingMultiplier: Math.max(0, Number(formData.livingMultiplier ?? 0.5) || 0.5),
|
|
autoChargeWeekly: String(formData.autoChargeWeekly) !== "false"
|
|
});
|
|
renderCampaignBanner();
|
|
ui.notifications.info("SLA Shift Ledger saved.");
|
|
}
|
|
|
|
static open() {
|
|
return new SlaShiftLedgerApp().render(true);
|
|
}
|
|
}
|
|
|
|
function installSceneButton(_app, html) {
|
|
if (!game.user?.isGM) return;
|
|
const root = getHtmlRoot(html);
|
|
const directoryHeader = root?.querySelector(".directory-header");
|
|
if (!directoryHeader) return;
|
|
let header = directoryHeader.querySelector(".header-actions, .action-buttons");
|
|
if (!header) {
|
|
header = document.createElement("div");
|
|
header.className = "header-actions sla-shift-ledger-fallback-actions";
|
|
directoryHeader.appendChild(header);
|
|
}
|
|
if (!header || header.querySelector(".sla-shift-ledger-open")) return;
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "sla-shift-ledger-open";
|
|
button.innerHTML = `<i class="fas fa-calendar-days"></i> Shift Ledger`;
|
|
button.addEventListener("click", () => SlaShiftLedgerApp.open());
|
|
header.appendChild(button);
|
|
}
|
|
|
|
function installSceneButtonFromSidebarTab(app, html) {
|
|
const root = getHtmlRoot(html);
|
|
const tabName = app?.tabName ?? root?.dataset?.tab ?? "";
|
|
if (tabName !== "scenes") return;
|
|
installSceneButton(app, html);
|
|
}
|
|
|
|
function buildLedgerHtml(actorState) {
|
|
const entries = Array.isArray(actorState.ledger) ? actorState.ledger : [];
|
|
if (!entries.length) {
|
|
return `<div class="sla-shift-ledger-ledger-entry">No weekly charges have been logged yet.</div>`;
|
|
}
|
|
return entries.map((entry) => `
|
|
<div class="sla-shift-ledger-ledger-entry">
|
|
<strong>Week ${entry.week}</strong> · ${entry.stamp}<br>
|
|
Housing Tier ${entry.housingTier}${entry.housingLabel ? ` · ${entry.housingLabel}` : ""}: -${entry.housing} cR · Living: -${entry.living} cR · <strong>Total -${entry.total} cR</strong><br>
|
|
Balance after charge: ${entry.balance} cR
|
|
</div>
|
|
`).join("");
|
|
}
|
|
|
|
function renderActorTimePanel(app, html) {
|
|
if (app.actor?.type !== "character") return;
|
|
|
|
const root = getHtmlRoot(html);
|
|
const notesTab = root?.querySelector(".tab[data-tab='notes']");
|
|
if (!notesTab || notesTab.querySelector(".sla-shift-ledger-actor-panel")) return;
|
|
|
|
const actor = app.actor;
|
|
const actorState = getActorState(actor);
|
|
const state = getState();
|
|
const config = getConfig();
|
|
const viewedDay = getViewedDay(app);
|
|
const schedule = normalizeDaySchedule(actorState.schedule, viewedDay);
|
|
const currentDaySchedule = schedule[String(viewedDay)];
|
|
const weeklyCosts = calculateWeeklyCosts(actorState.housingTier, config);
|
|
const currentShiftIndex = Number(state.shift ?? 0) || 0;
|
|
|
|
const panel = document.createElement("section");
|
|
panel.className = "sla-shift-ledger-actor-panel sla-card";
|
|
panel.innerHTML = `
|
|
<div class="sla-card-label">Shift Ledger</div>
|
|
<div class="sla-card-copy">Track what this operative is doing across the current day and let weekly housing and living costs feed a running ledger.</div>
|
|
<div class="sla-shift-ledger-actor-summary">
|
|
<span class="sla-shift-ledger-tag">Day ${viewedDay}</span>
|
|
<span class="sla-shift-ledger-tag">Week ${getWeekFromDay(viewedDay)}</span>
|
|
<span class="sla-shift-ledger-tag">${getShift(state.shift).label}</span>
|
|
<span class="sla-shift-ledger-tag">Current Duty: ${currentDaySchedule[currentShiftIndex] ?? config.activityOptions[0]}</span>
|
|
</div>
|
|
<div class="sla-shift-ledger-actor-card">
|
|
<div class="sla-shift-ledger-kicker">Daily Schedule</div>
|
|
<div class="sla-shift-ledger-day-nav">
|
|
<button type="button" class="sla-shift-ledger-button" data-sla-day-nav="-1">Earlier Day</button>
|
|
<button type="button" class="sla-shift-ledger-button" data-sla-day-nav="today">Current Day</button>
|
|
<button type="button" class="sla-shift-ledger-button" data-sla-day-nav="1">Later Day</button>
|
|
<span class="sla-shift-ledger-day-readout">Viewing Day ${viewedDay}</span>
|
|
</div>
|
|
<div class="sla-shift-ledger-actor-grid">
|
|
${SHIFTS.map((shift, index) => `
|
|
<div class="sla-shift-ledger-field">
|
|
<label>${shift.label}</label>
|
|
<select data-sla-shift-index="${index}" ${app.isEditable ? "" : "disabled"}>
|
|
${config.activityOptions.map((option) => `
|
|
<option value="${foundry.utils.escapeHTML(option)}" ${option === currentDaySchedule[index] ? "selected" : ""}>${foundry.utils.escapeHTML(option)}</option>
|
|
`).join("")}
|
|
</select>
|
|
</div>
|
|
`).join("")}
|
|
</div>
|
|
</div>
|
|
<div class="sla-shift-ledger-actor-card">
|
|
<div class="sla-shift-ledger-kicker">Housing & Cost of Living</div>
|
|
<div class="sla-shift-ledger-grid">
|
|
<div class="sla-shift-ledger-field">
|
|
<label>Housing Tier</label>
|
|
<select data-sla-housing-tier ${app.isEditable ? "" : "disabled"}>
|
|
${Array.from({ length: 10 }, (_value, index) => {
|
|
const tier = index + 1;
|
|
return `<option value="${tier}" ${tier === Number(actorState.housingTier ?? 1) ? "selected" : ""}>Tier ${tier} · ${config.housingLabels[index]} · ${config.housingCosts[index]} cR/week</option>`;
|
|
}).join("")}
|
|
</select>
|
|
</div>
|
|
<div class="sla-shift-ledger-field">
|
|
<label>Weekly Total</label>
|
|
<div class="sla-shift-ledger-tag">${weeklyCosts.label} · Housing ${weeklyCosts.housing} cR · Living ${weeklyCosts.living} cR · Total ${weeklyCosts.total} cR</div>
|
|
</div>
|
|
</div>
|
|
<div class="sla-shift-ledger-inline-actions">
|
|
${game.user?.isGM ? `<button type="button" class="sla-shift-ledger-button" data-sla-bill-now>Charge Now</button>` : ""}
|
|
</div>
|
|
</div>
|
|
<div class="sla-shift-ledger-actor-card">
|
|
<div class="sla-shift-ledger-kicker">Living Cost Ledger</div>
|
|
<div class="sla-shift-ledger-ledger">
|
|
${buildLedgerHtml(actorState)}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
notesTab.prepend(panel);
|
|
|
|
if (!app.isEditable) return;
|
|
|
|
panel.querySelectorAll("[data-sla-day-nav]").forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
const dir = button.dataset.slaDayNav;
|
|
if (dir === "today") {
|
|
app._slaShiftLedgerViewedDay = Number(state.day ?? viewedDay) || viewedDay;
|
|
} else {
|
|
app._slaShiftLedgerViewedDay = Math.max(0, viewedDay + (Number(dir) || 0));
|
|
}
|
|
app.render(false);
|
|
});
|
|
});
|
|
|
|
panel.querySelectorAll("[data-sla-shift-index]").forEach((select) => {
|
|
select.addEventListener("change", async (event) => {
|
|
const shiftIndex = Number(event.currentTarget.dataset.slaShiftIndex ?? 0) || 0;
|
|
const nextActorState = getActorState(actor);
|
|
const nextSchedule = normalizeDaySchedule(nextActorState.schedule, viewedDay);
|
|
nextSchedule[String(viewedDay)][shiftIndex] = String(event.currentTarget.value ?? config.activityOptions[0]);
|
|
await setActorState(actor, { ...nextActorState, schedule: nextSchedule });
|
|
app.render(false);
|
|
});
|
|
});
|
|
|
|
panel.querySelector("[data-sla-housing-tier]")?.addEventListener("change", async (event) => {
|
|
const nextActorState = getActorState(actor);
|
|
await setActorState(actor, {
|
|
...nextActorState,
|
|
housingTier: Math.max(1, Math.min(10, Number(event.currentTarget.value ?? 1) || 1))
|
|
});
|
|
app.render(false);
|
|
});
|
|
|
|
panel.querySelector("[data-sla-bill-now]")?.addEventListener("click", async () => {
|
|
const nextActorState = getActorState(actor);
|
|
const costs = calculateWeeklyCosts(nextActorState.housingTier, getConfig());
|
|
const stateNow = getState();
|
|
const newCredits = Math.round((getCredits(actor) - costs.total) * 100) / 100;
|
|
const entry = {
|
|
week: getWeekFromDay(stateNow.day),
|
|
day: stateNow.day,
|
|
stamp: buildCampaignStamp(stateNow),
|
|
housingTier: nextActorState.housingTier,
|
|
housingLabel: costs.label,
|
|
housing: costs.housing,
|
|
living: costs.living,
|
|
total: costs.total,
|
|
balance: newCredits
|
|
};
|
|
await setActorState(actor, {
|
|
...nextActorState,
|
|
ledger: [entry, ...(Array.isArray(nextActorState.ledger) ? nextActorState.ledger : [])].slice(0, 24),
|
|
lastBilledWeek: Math.max(Number(nextActorState.lastBilledWeek ?? 0), getWeekFromDay(stateNow.day))
|
|
});
|
|
await actor.update({ "system.credits.value": newCredits });
|
|
app.render(false);
|
|
});
|
|
}
|
|
|
|
function renderActorFrontSummary(app, html) {
|
|
if (app.actor?.type !== "character") return;
|
|
const root = getHtmlRoot(html);
|
|
const strip = root?.querySelector(".sla-front-strip");
|
|
if (!strip || strip.querySelector(".sla-shift-ledger-front-panel")) return;
|
|
|
|
const actorState = getActorState(app.actor);
|
|
const state = getState();
|
|
const config = getConfig();
|
|
const schedule = normalizeDaySchedule(actorState.schedule, state.day);
|
|
const currentSchedule = schedule[String(state.day)];
|
|
const currentDuty = currentSchedule[Number(state.shift ?? 0) || 0] ?? config.activityOptions[0];
|
|
const weeklyCosts = calculateWeeklyCosts(actorState.housingTier, config);
|
|
|
|
const panel = document.createElement("div");
|
|
panel.className = "sla-shift-ledger-front-panel";
|
|
panel.innerHTML = `
|
|
<div class="sla-shift-ledger-front-summary">
|
|
<span class="sla-shift-ledger-tag">Day ${state.day}</span>
|
|
<span class="sla-shift-ledger-tag">${getShift(state.shift).label}</span>
|
|
<span class="sla-shift-ledger-tag">Duty: ${currentDuty}</span>
|
|
<span class="sla-shift-ledger-tag">Housing: Tier ${actorState.housingTier} · ${weeklyCosts.label}</span>
|
|
<span class="sla-shift-ledger-tag">Weekly: ${weeklyCosts.total} cR</span>
|
|
</div>
|
|
`;
|
|
strip.appendChild(panel);
|
|
}
|
|
|
|
Hooks.once("init", () => {
|
|
game.settings.register(MODULE_ID, STATE_KEY, {
|
|
scope: "world",
|
|
config: false,
|
|
type: Object,
|
|
default: defaultCampaignState()
|
|
});
|
|
|
|
game.settings.register(MODULE_ID, CONFIG_KEY, {
|
|
scope: "world",
|
|
config: false,
|
|
type: Object,
|
|
default: defaultConfig()
|
|
});
|
|
|
|
game.settings.registerMenu(MODULE_ID, "shiftLedgerMenu", {
|
|
name: "SLA Shift Ledger",
|
|
label: "Open Shift Ledger",
|
|
hint: "Track campaign day, shift cadence, schedule options, and living costs.",
|
|
icon: "fas fa-calendar-days",
|
|
type: SlaShiftLedgerApp,
|
|
restricted: true
|
|
});
|
|
});
|
|
|
|
Hooks.once("ready", () => {
|
|
game.slaShiftLedger = {
|
|
open: () => SlaShiftLedgerApp.open(),
|
|
getState,
|
|
getConfig,
|
|
advanceShift,
|
|
applyWeeklyBilling
|
|
};
|
|
renderCampaignBanner();
|
|
});
|
|
|
|
Hooks.on("renderSceneDirectory", installSceneButton);
|
|
Hooks.on("renderSidebarTab", installSceneButtonFromSidebarTab);
|
|
Hooks.on("renderActorSheet", renderActorTimePanel);
|
|
Hooks.on("renderActorSheet", renderActorFrontSummary);
|
|
Hooks.on("renderMothershipActorSheet", renderActorTimePanel);
|
|
Hooks.on("renderMothershipActorSheet", renderActorFrontSummary);
|
|
Hooks.on("renderMothershipActorSheetDossier", renderActorTimePanel);
|
|
Hooks.on("renderMothershipActorSheetDossier", renderActorFrontSummary);
|
|
Hooks.on("updateSetting", (setting) => {
|
|
if (setting?.namespace === MODULE_ID && [STATE_KEY, CONFIG_KEY].includes(setting?.key)) {
|
|
renderCampaignBanner();
|
|
}
|
|
});
|