diff --git a/styles/zero-engine.css b/styles/zero-engine.css index df98cd2..023e37e 100644 --- a/styles/zero-engine.css +++ b/styles/zero-engine.css @@ -4244,3 +4244,145 @@ body > .ze-pause-root .ze-sat-inner { background: rgba(0,212,255,0.2); border-color: #00d4ff; color: #00d4ff; box-shadow: 0 0 8px rgba(0,200,255,0.3); } + +/* ══════════════════════════════════════════════════════════════════════════════ + PLAYER CHARACTER FLAG — Bio Tab + ══════════════════════════════════════════════════════════════════════════════ */ +.zero-engine .pc-flag-row { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 12px; + margin-bottom: 14px; + background: rgba(255, 200, 0, 0.07); + border: 1px solid rgba(255, 200, 0, 0.25); + border-radius: 5px; +} +.zero-engine .pc-flag-label { + display: flex; + align-items: center; + gap: 7px; + font-size: 13px; + font-weight: bold; + color: #ffcc00; + cursor: pointer; + white-space: nowrap; +} +.zero-engine .pc-flag-label input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: #ffcc00; + cursor: pointer; +} +.zero-engine .pc-flag-hint { + font-size: 11px; + color: #888; + font-style: italic; +} + +/* ══════════════════════════════════════════════════════════════════════════════ + GM FINANCE LEDGER + ══════════════════════════════════════════════════════════════════════════════ */ +.gm-finance-tool .gm-finance-wrap { + padding: 12px; + display: flex; + flex-direction: column; + gap: 10px; +} +.gm-finance-tool .gm-finance-header-note { + font-size: 12px; + color: #aaa; + padding: 6px 10px; + background: rgba(255,255,255,0.04); + border-left: 3px solid #ffcc00; + border-radius: 3px; +} +.gm-finance-tool .gm-finance-header-note strong { color: #ffcc00; } + +.gm-finance-tool .gm-finance-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} +.gm-finance-tool .gm-finance-table thead tr { + background: rgba(255, 200, 0, 0.1); + border-bottom: 1px solid rgba(255,200,0,0.3); +} +.gm-finance-tool .gm-finance-table thead th { + padding: 7px 10px; + text-align: left; + color: #ffcc00; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.gm-finance-tool .gm-finance-table tbody tr { + border-bottom: 1px solid rgba(255,255,255,0.06); + transition: background 0.15s; +} +.gm-finance-tool .gm-finance-table tbody tr:hover { + background: rgba(255,255,255,0.04); +} +.gm-finance-tool .gm-finance-table td { + padding: 8px 10px; + color: #ccc; +} +.gm-finance-tool .gf-name { + font-weight: bold; + color: #eee; +} +.gm-finance-tool .gf-balance { + color: #88ccff; + font-weight: bold; + font-size: 14px; +} +.gm-finance-tool .gf-income { color: #44cc66; } +.gm-finance-tool .gf-expenses { color: #cc5555; } +.gm-finance-tool .gf-net { font-weight: bold; font-size: 14px; } +.gm-finance-tool .net-positive { color: #44cc66; } +.gm-finance-tool .net-negative { color: #cc3333; } + +.gm-finance-tool .gm-finance-calc-btn { + padding: 4px 10px; + font-size: 11px; + font-weight: bold; + background: rgba(255,200,0,0.15); + border: 1px solid #ffcc00; + color: #ffcc00; + border-radius: 3px; + cursor: pointer; + transition: all 0.2s; + white-space: nowrap; +} +.gm-finance-tool .gm-finance-calc-btn:hover { + background: rgba(255,200,0,0.3); + box-shadow: 0 0 8px rgba(255,200,0,0.25); +} + +.gm-finance-tool .gm-finance-empty { + text-align: center; + padding: 20px; + color: #777; + font-style: italic; +} + +.gm-finance-tool .gm-finance-footer { + display: flex; + justify-content: flex-end; + padding-top: 4px; +} +.gm-finance-tool .gm-finance-calc-all-btn { + padding: 6px 16px; + font-size: 12px; + font-weight: bold; + background: rgba(255,200,0,0.2); + border: 1px solid #ffcc00; + color: #ffcc00; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; +} +.gm-finance-tool .gm-finance-calc-all-btn:hover { + background: rgba(255,200,0,0.35); + box-shadow: 0 0 10px rgba(255,200,0,0.3); +} diff --git a/system.json b/system.json index 0dada84..e3e081b 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.5", + "version": "1.4.6", "compatibility": { "minimum": 13, "verified": "14.359" diff --git a/template.json b/template.json index 195cbce..2096463 100644 --- a/template.json +++ b/template.json @@ -11,7 +11,8 @@ "height": "", "weight": "", "scl": 10, - "credits": 500 + "credits": 500, + "isPlayerCharacter": false }, "race": [], "training": [], diff --git a/templates/actor/character-sheet.hbs b/templates/actor/character-sheet.hbs index 26dfb8e..9cf71ef 100644 --- a/templates/actor/character-sheet.hbs +++ b/templates/actor/character-sheet.hbs @@ -958,6 +958,14 @@

Biography

+
+ + When ticked, this actor appears in the GM Finance Ledger. +
+

Background

diff --git a/zero-engine.mjs b/zero-engine.mjs index 24b645a..4188d62 100644 --- a/zero-engine.mjs +++ b/zero-engine.mjs @@ -5481,6 +5481,140 @@ 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. +// ══════════════════════════════════════════════════════════════════════════════ + +class SLAGMFinanceTool extends Application { + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + id: 'sla-gm-finance-tool', + title: 'GM Finance Ledger', + width: 660, + height: 'auto', + resizable: true, + classes: ['zero-engine', 'gm-finance-tool'] + }); + } + + /** Build inline HTML — no separate .hbs file needed */ + async _renderInner(data) { + const pcs = this._getPCData(); + let rows = ''; + if (pcs.length === 0) { + rows = ` + No Player Characters found. Tick the Player Character checkbox + on the Biography tab of each PC actor sheet. + `; + } else { + for (const pc of pcs) { + const netClass = pc.netWeekly >= 0 ? 'net-positive' : 'net-negative'; + const netStr = `${pc.netWeekly >= 0 ? '+' : ''}${pc.netWeekly}c`; + rows += ` + + ${pc.name} + ${pc.credits}c + +${pc.weeklyIncome}c + −${pc.weeklyExpenses}c + ${netStr} + + + + `; + } + } + + const html = ` +
+
+ + Click Calculate to apply one week of net income/expenses to a character's balance. +
+ + + + + + + + + + + + ${rows} +
CharacterBalanceWk IncomeWk ExpensesNet
+ +
`; + + const $html = $(html); + return $html; + } + + _getPCData() { + return (game.actors ?? []) + .filter(a => a.type === 'character' && a.system?.details?.isPlayerCharacter) + .map(a => { + const inc = a.system.finances?.income || {}; + const exp = a.system.finances?.expenses || {}; + const weeklyIncome = (inc.salary||0) + (inc.bpnReward||0) + (inc.other||0); + const weeklyExpenses = (exp.accommodation||0) + (exp.drugs||0) + (exp.subscriptions||0) + (exp.other||0) + (exp.bulletTax||0); + const netWeekly = weeklyIncome - weeklyExpenses; + return { + id: a.id, + name: a.name, + credits: Number(a.system.details?.credits ?? 0), + weeklyIncome, + weeklyExpenses, + netWeekly + }; + }); + } + + async _applyWeek(actorId) { + if (!game.user?.isGM) return; + const actor = game.actors.get(actorId); + if (!actor) return; + const inc = actor.system.finances?.income || {}; + const exp = actor.system.finances?.expenses || {}; + const weeklyIncome = (inc.salary||0) + (inc.bpnReward||0) + (inc.other||0); + const weeklyExpenses = (exp.accommodation||0) + (exp.drugs||0) + (exp.subscriptions||0) + (exp.other||0) + (exp.bulletTax||0); + const net = weeklyIncome - weeklyExpenses; + const current = Number(actor.system?.details?.credits ?? 0); + const newBal = current + net; + await actor.update({ 'system.details.credits': newBal }); + ui.notifications.info( + `${actor.name}: ${current}c ${net >= 0 ? '+' : ''}${net}c → ${newBal}c` + ); + } + + activateListeners(html) { + super.activateListeners(html); + const root = html instanceof jQuery ? html[0] : html; + + root.querySelectorAll('.gm-finance-calc-btn').forEach(btn => { + btn.addEventListener('click', async () => { + await this._applyWeek(btn.dataset.actorId); + this.render(false); + }); + }); + + root.querySelector('.gm-finance-calc-all-btn')?.addEventListener('click', async () => { + const pcs = this._getPCData(); + for (const pc of pcs) await this._applyWeek(pc.id); + this.render(false); + }); + } +} + // ── ACTOR DIRECTORY BUTTONS ────────────────────────────────────────────────── Hooks.on('renderActorDirectory', (app, html) => { const root = html instanceof jQuery ? html[0] : html; @@ -5502,6 +5636,19 @@ Hooks.on('renderActorDirectory', (app, html) => { npcBtn.style.cssText = 'font-size:11px;padding:2px 7px;margin-left:4px;background:rgba(0,200,200,0.1);border:1px solid #00aacc;color:#00aacc;border-radius:3px;cursor:pointer;'; npcBtn.addEventListener('click', () => new SLANPCCreator().render(true)); + // GM-only Finance Ledger button + if (game.user?.isGM) { + const finBtn = document.createElement('button'); + finBtn.className = 'sla-gm-finance-btn'; + finBtn.innerHTML = ' Finance Ledger'; + finBtn.style.cssText = 'font-size:11px;padding:2px 7px;margin-left:4px;background:rgba(255,200,0,0.15);border:1px solid #ffcc00;color:#ffcc00;border-radius:3px;cursor:pointer;'; + finBtn.addEventListener('click', () => { + if (!game.slaGMFinanceTool) game.slaGMFinanceTool = new SLAGMFinanceTool(); + game.slaGMFinanceTool.render(true); + }); + actions.append(finBtn); + } + actions.append(pcBtn, npcBtn); });