diff --git a/system.json b/system.json index 41d4068..6de04c8 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "zero-engine", "title": "Zero Engine (SLA Industries)", "description": "A basic game system for SLA Industries using the Zero Engine rules.", - "version": "1.4.8", + "version": "1.4.9", "compatibility": { "minimum": 13, "verified": "14.359" diff --git a/zero-engine.mjs b/zero-engine.mjs index f3dcefe..b639f55 100644 --- a/zero-engine.mjs +++ b/zero-engine.mjs @@ -5484,44 +5484,157 @@ class SLANPCCreator extends Application { // ══════════════════════════════════════════════════════════════════════════════ // SLA GM FINANCE LEDGER // GM-only tool: lists all Player Character actors, shows current balance, -// weekly income/expenses, net weekly, and allows one-click week calculation. +// weekly income/expenses, net weekly. +// Calculate button: newCredits = credits + income - expenses, then zeros +// all income and expense fields on the actor. // ══════════════════════════════════════════════════════════════════════════════ class SLAGMFinanceTool extends Application { + constructor(...args) { + super(...args); + this._actorHookId = null; + } + static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { id: 'sla-gm-finance-tool', title: 'GM Finance Ledger', - width: 660, + width: 680, height: 'auto', resizable: true, classes: ['zero-engine', 'gm-finance-tool'] }); } - /** Build inline HTML — no separate .hbs file needed */ - async _renderInner(data) { + // ── Auto-refresh when any actor is updated ──────────────────────────────── + async _render(force, options) { + await super._render(force, options); + if (!this._actorHookId) { + this._actorHookId = Hooks.on('updateActor', () => { + if (this.rendered) this.render(false); + }); + } + } + + async close(options) { + if (this._actorHookId) { + Hooks.off('updateActor', this._actorHookId); + this._actorHookId = null; + } + return super.close(options); + } + + // ── Read credits from the actor's raw source data ───────────────────────── + // _source bypasses any DataModel computed values and gives the raw DB value. + _readCredits(actor) { + let raw = actor._source?.system?.details?.credits; + if (raw === undefined || raw === null) + raw = actor.system?.details?.credits; + if (raw === undefined || raw === null) + raw = foundry.utils.getProperty(actor, 'system.details.credits'); + // Fix "500,500" string from duplicate-named form inputs + if (typeof raw === 'string' && raw.includes(',')) + raw = raw.split(',')[0]; + const n = Number(raw); + return isNaN(n) ? 0 : n; + } + + // ── Read a finances sub-value safely ───────────────────────────────────── + _readFinance(actor, path) { + let raw = foundry.utils.getProperty(actor._source ?? {}, `system.${path}`) + ?? foundry.utils.getProperty(actor, `system.${path}`); + const n = Number(raw); + return isNaN(n) ? 0 : n; + } + + // ── Build per-actor data row ────────────────────────────────────────────── + _getPCData() { + return (game.actors ?? []) + .filter(a => a.type === 'character' && a.system?.details?.isPlayerCharacter) + .map(a => { + const salary = this._readFinance(a, 'finances.income.salary'); + const bpn = this._readFinance(a, 'finances.income.bpnReward'); + const incOther = this._readFinance(a, 'finances.income.other'); + const accom = this._readFinance(a, 'finances.expenses.accommodation'); + const drugs = this._readFinance(a, 'finances.expenses.drugs'); + const subs = this._readFinance(a, 'finances.expenses.subscriptions'); + const expOther = this._readFinance(a, 'finances.expenses.other'); + const btax = this._readFinance(a, 'finances.expenses.bulletTax'); + const weeklyIncome = salary + bpn + incOther; + const weeklyExpenses = accom + drugs + subs + expOther + btax; + const netWeekly = weeklyIncome - weeklyExpenses; + const credits = this._readCredits(a); + return { + id: a.id, name: a.name, + credits, weeklyIncome, weeklyExpenses, netWeekly + }; + }); + } + + // ── Apply week: credits += income - expenses, then zero all fields ───────── + async _applyWeek(actorId) { + if (!game.user?.isGM) return; + const actor = game.actors.get(actorId); + if (!actor) return; + + const salary = this._readFinance(actor, 'finances.income.salary'); + const bpn = this._readFinance(actor, 'finances.income.bpnReward'); + const incOther = this._readFinance(actor, 'finances.income.other'); + const accom = this._readFinance(actor, 'finances.expenses.accommodation'); + const drugs = this._readFinance(actor, 'finances.expenses.drugs'); + const subs = this._readFinance(actor, 'finances.expenses.subscriptions'); + const expOther = this._readFinance(actor, 'finances.expenses.other'); + const btax = this._readFinance(actor, 'finances.expenses.bulletTax'); + + const income = salary + bpn + incOther; + const expenses = accom + drugs + subs + expOther + btax; + const current = this._readCredits(actor); + const newBal = current + income - expenses; + + await actor.update({ + 'system.details.credits': newBal, + 'system.finances.income.salary': 0, + 'system.finances.income.bpnReward': 0, + 'system.finances.income.other': 0, + 'system.finances.expenses.accommodation': 0, + 'system.finances.expenses.drugs': 0, + 'system.finances.expenses.subscriptions': 0, + 'system.finances.expenses.other': 0, + 'system.finances.expenses.bulletTax': 0, + 'system.finances.ammoSpentSession': 0, + 'system.finances.debt': 0 + }); + + ui.notifications.info( + `${actor.name}: ${current}c + ${income}c income − ${expenses}c expenses = ${newBal}c` + ); + } + + // ── Render the table ────────────────────────────────────────────────────── + async _renderInner(_data) { const pcs = this._getPCData(); let rows = ''; + if (pcs.length === 0) { rows = `
| Character | Balance | -Wk Income | -Wk Expenses | +Income | +Expenses | Net |
|---|