From 2305cdf438748dfa3d1d4c90a2cea8877ebb776d Mon Sep 17 00:00:00 2001 From: "Tim Evans (slaguru666)" Date: Tue, 31 Mar 2026 17:00:36 +0100 Subject: [PATCH] =?UTF-8?q?chore:=20initial=20deploy=20=E2=80=94=20SLA=20I?= =?UTF-8?q?ndustries=20Foundry=20VTT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: slaguru666 --- .gitignore | 15 +++ data/briefing-tables.json | 9 ++ lang/en.json | 3 + module.json | 32 +++++++ module/sla-briefing-desk.mjs | 173 +++++++++++++++++++++++++++++++++++ styles/sla-briefing-desk.css | 83 +++++++++++++++++ templates/briefing-desk.hbs | 51 +++++++++++ 7 files changed, 366 insertions(+) create mode 100644 .gitignore create mode 100644 data/briefing-tables.json create mode 100644 lang/en.json create mode 100644 module.json create mode 100644 module/sla-briefing-desk.mjs create mode 100644 styles/sla-briefing-desk.css create mode 100644 templates/briefing-desk.hbs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b24d620 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +node_modules/ +npm-debug.log* +.npm/ +dist/ +*.min.js +*.min.css +.DS_Store +.DS_Store? +._* +Thumbs.db +.vscode/ +.idea/ +*.swp +*.swo +worlds/ diff --git a/data/briefing-tables.json b/data/briefing-tables.json new file mode 100644 index 0000000..91bedd3 --- /dev/null +++ b/data/briefing-tables.json @@ -0,0 +1,9 @@ +{ + "missionTypes": ["Retrieval", "Termination", "Escort", "Containment", "Public Order", "Counter-Ebb Sweep", "Evidence Recovery", "Black Bag"], + "sectors": ["Mort", "Cannibal Sector", "Downtown", "Industrial Spine", "The Docks", "Lower Downtown", "Sub-City Arcology", "Transit Hub"], + "targets": ["renegade operative", "corp data courier", "infected slop gang", "missing asset", "cult logistics cell", "rogue media crew", "biotech sample", "witness under protection"], + "contacts": ["Mr. Slayer liaison", "Department-M minder", "sector overseer", "anonymous tip line", "embedded media fixer", "field scientist", "security captain", "ops auditor"], + "complications": ["the target is already moving", "media drones are on scene", "Cannibal activity spikes nearby", "an internal rival team is on the same brief", "local law has sealed the zone", "the client withheld one critical fact", "the package is alive", "there is a clock on public exposure"], + "payoffs": ["8,000 credits plus expenses", "10,000 credits and media bonuses", "6,500 credits with expedited status review", "12,000 credits on silent completion", "9,000 credits and requisition priority", "7,500 credits plus salvage rights"], + "twists": ["the employer wants witnesses removed", "the job is bait for another target", "the package is compromised by Ebb bleed", "the evidence points back to SLA payroll", "the target is an innocent lookalike", "the extraction route is burned", "the media angle is the true objective", "an enemy operative is already wearing a company badge"] +} diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..098a752 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,3 @@ +{ + "SLABriefingDesk.Title": "SLA Briefing Desk" +} diff --git a/module.json b/module.json new file mode 100644 index 0000000..3d3a078 --- /dev/null +++ b/module.json @@ -0,0 +1,32 @@ +{ + "id": "sla-briefing-desk", + "title": "SLA Briefing Desk", + "description": "Creates SLA Industries mission briefings, objectives, complications, and handouts for the table.", + "version": "0.1.0", + "compatibility": { + "minimum": "13", + "verified": "13.350", + "maximum": "13" + }, + "authors": [ + { + "name": "Tim Evans" + } + ], + "esmodules": [ + "module/sla-briefing-desk.mjs" + ], + "styles": [ + "styles/sla-briefing-desk.css" + ], + "languages": [ + { + "lang": "en", + "name": "English", + "path": "lang/en.json" + } + ], + "manifest": "https://raw.githubusercontent.com/slaguru666/sla-briefing-desk/main/module.json", + "download": "https://github.com/slaguru666/sla-briefing-desk/releases/latest/download/sla-briefing-desk.zip", + "url": "https://github.com/slaguru666/sla-briefing-desk" +} \ No newline at end of file diff --git a/module/sla-briefing-desk.mjs b/module/sla-briefing-desk.mjs new file mode 100644 index 0000000..9e4a8ab --- /dev/null +++ b/module/sla-briefing-desk.mjs @@ -0,0 +1,173 @@ +const MODULE_ID = "sla-briefing-desk"; +const DRAFT_KEY = "briefingDraft"; + +let TABLES = null; + +function getHtmlRoot(html) { + return html instanceof HTMLElement ? html : html?.[0] ?? null; +} + +async function loadTables() { + if (TABLES) return TABLES; + const response = await fetch("modules/sla-briefing-desk/data/briefing-tables.json"); + TABLES = await response.json(); + return TABLES; +} + +function pick(array) { + return Array.isArray(array) && array.length ? array[Math.floor(Math.random() * array.length)] : ""; +} + +function defaultBriefing() { + return { + jobName: "SLA Brief", + missionType: "", + sector: "", + target: "", + contact: "", + payoff: "", + complication: "", + twist: "", + notes: "" + }; +} + +function getDraft() { + return foundry.utils.mergeObject(defaultBriefing(), game.settings.get(MODULE_ID, DRAFT_KEY) ?? {}); +} + +async function setDraft(draft) { + await game.settings.set(MODULE_ID, DRAFT_KEY, foundry.utils.mergeObject(defaultBriefing(), draft ?? {})); +} + +function renderBriefingHtml(briefing) { + return ` +
+

${foundry.utils.escapeHTML(briefing.jobName || "SLA Brief")}

+

Mission Type: ${foundry.utils.escapeHTML(briefing.missionType)}

+

Sector: ${foundry.utils.escapeHTML(briefing.sector)}

+

Target: ${foundry.utils.escapeHTML(briefing.target)}

+

Contact: ${foundry.utils.escapeHTML(briefing.contact)}

+

Payoff: ${foundry.utils.escapeHTML(briefing.payoff)}

+

Complication: ${foundry.utils.escapeHTML(briefing.complication)}

+

Twist: ${foundry.utils.escapeHTML(briefing.twist)}

+ ${briefing.notes ? `

Operational Notes: ${foundry.utils.escapeHTML(briefing.notes)}

` : ""} +
+ `; +} + +async function randomBriefing() { + const tables = await loadTables(); + const missionType = pick(tables.missionTypes); + const sector = pick(tables.sectors); + const target = pick(tables.targets); + return { + jobName: `${missionType} - ${sector}`, + missionType, + sector, + target, + contact: pick(tables.contacts), + payoff: pick(tables.payoffs), + complication: pick(tables.complications), + twist: pick(tables.twists), + notes: "" + }; +} + +class SlaBriefingDeskApp extends FormApplication { + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + id: "sla-briefing-desk-app", + title: "SLA Briefing Desk", + template: "modules/sla-briefing-desk/templates/briefing-desk.hbs", + classes: ["sla-briefing-desk"], + width: 640, + height: "auto", + closeOnSubmit: false, + submitOnChange: false, + resizable: true + }); + } + + async getData() { + return { briefing: getDraft() }; + } + + activateListeners(html) { + super.activateListeners(html); + html.find("[data-action='randomize']").on("click", async () => { + await setDraft(await randomBriefing()); + this.render(false); + }); + html.find("[data-action='chat']").on("click", async () => { + const draft = await this._collectDraft(); + await setDraft(draft); + await ChatMessage.create({ + user: game.user.id, + speaker: { alias: "SLA Briefing Desk" }, + content: renderBriefingHtml(draft) + }); + }); + html.find("[data-action='journal']").on("click", async () => { + const draft = await this._collectDraft(); + await setDraft(draft); + const journal = await JournalEntry.create({ + name: draft.jobName || "SLA Brief", + pages: [ + { + name: "Briefing", + type: "text", + text: { content: renderBriefingHtml(draft) } + } + ] + }); + ui.notifications.info(`Created briefing journal: ${journal.name}`); + }); + } + + async _collectDraft() { + const fd = new foundry.applications.ux.FormDataExtended(this.form); + return foundry.utils.mergeObject(getDraft(), fd.object); + } + + async _updateObject(_event, formData) { + await setDraft(formData); + ui.notifications.info("SLA briefing draft saved."); + } + + static open() { + return new SlaBriefingDeskApp().render(true); + } +} + +function installJournalButton(app, html) { + const root = getHtmlRoot(html); + const header = root?.querySelector(".directory-header .header-actions, .directory-header .action-buttons"); + if (!header || header.querySelector(".sla-briefing-desk-open")) return; + const button = document.createElement("button"); + button.type = "button"; + button.className = "sla-briefing-desk-open"; + button.innerHTML = ` Briefing Desk`; + button.addEventListener("click", () => SlaBriefingDeskApp.open()); + header.appendChild(button); +} + +Hooks.once("init", () => { + game.settings.register(MODULE_ID, DRAFT_KEY, { + scope: "world", + config: false, + type: Object, + default: defaultBriefing() + }); + + game.settings.registerMenu(MODULE_ID, "briefingDeskMenu", { + name: "SLA Briefing Desk", + label: "Open Briefing Desk", + hint: "Draft or randomize an SLA mission briefing.", + icon: "fas fa-file-lines", + type: SlaBriefingDeskApp, + restricted: true + }); +}); + +Hooks.on("renderJournalDirectory", installJournalButton); diff --git a/styles/sla-briefing-desk.css b/styles/sla-briefing-desk.css new file mode 100644 index 0000000..9abb3b4 --- /dev/null +++ b/styles/sla-briefing-desk.css @@ -0,0 +1,83 @@ +.sla-briefing-desk .window-content { + background: linear-gradient(180deg, #16171c 0%, #1d2026 100%); + color: #f4efe5 !important; +} + +.sla-briefing-form { + display: grid; + gap: 12px; +} + +.sla-briefing-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.sla-briefing-field, +.sla-briefing-card { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(213, 180, 88, 0.3); + border-radius: 12px; + padding: 10px 12px; +} + +.sla-briefing-field label, +.sla-briefing-card h3 { + display: block; + color: #f4efe5 !important; + margin-bottom: 6px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.sla-briefing-field input, +.sla-briefing-field textarea { + width: 100%; + background: rgba(0, 0, 0, 0.35); + border: 1px solid rgba(255, 255, 255, 0.16); + border-radius: 10px; + color: #f4efe5 !important; + padding: 8px 10px; +} + +.sla-briefing-desk .window-title, +.sla-briefing-desk p, +.sla-briefing-desk span, +.sla-briefing-desk strong, +.sla-briefing-desk input, +.sla-briefing-desk textarea, +.sla-briefing-desk option { + color: #f4efe5 !important; +} + +.sla-briefing-desk input::placeholder, +.sla-briefing-desk textarea::placeholder { + color: rgba(244, 239, 229, 0.58) !important; +} + +.sla-briefing-field textarea { + min-height: 88px; +} + +.sla-briefing-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.sla-briefing-actions button { + background: linear-gradient(180deg, #d6b24e 0%, #b88a24 100%) !important; + border: 1px solid rgba(255, 232, 180, 0.55) !important; + border-radius: 999px; + color: #17140c !important; + padding: 7px 14px; + font-weight: 700; +} + +@media (max-width: 720px) { + .sla-briefing-grid { + grid-template-columns: 1fr; + } +} diff --git a/templates/briefing-desk.hbs b/templates/briefing-desk.hbs new file mode 100644 index 0000000..e2c4d42 --- /dev/null +++ b/templates/briefing-desk.hbs @@ -0,0 +1,51 @@ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + + + +
+