commit 2305cdf438748dfa3d1d4c90a2cea8877ebb776d Author: Tim Evans (slaguru666) Date: Tue Mar 31 17:00:36 2026 +0100 chore: initial deploy — SLA Industries Foundry VTT Co-authored-by: slaguru666 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 @@ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + + + +
+