diff --git a/css/mosh.css b/css/mosh.css index 9818b1e..6152d6e 100644 --- a/css/mosh.css +++ b/css/mosh.css @@ -4775,11 +4775,21 @@ p { .mosh.sla-chat-shell .sla-chat-section-danger.sla-severity-minor { background: linear-gradient(180deg, rgba(92, 57, 18, 0.96), rgba(55, 34, 12, 0.96)); border-color: rgba(255, 190, 74, 0.5); + color: #ffeacc; +} + +.mosh.sla-chat-shell .sla-chat-section-danger.sla-severity-minor strong { + color: #ffffff; } .mosh.sla-chat-shell .sla-chat-section-danger.sla-severity-severe { background: linear-gradient(180deg, rgba(90, 18, 26, 0.97), rgba(52, 10, 15, 0.97)); border-color: rgba(255, 91, 91, 0.52); + color: #ffdada; +} + +.mosh.sla-chat-shell .sla-chat-section-danger.sla-severity-severe strong { + color: #ffffff; } .mosh.sla-chat-shell .sla-chat-section-danger.sla-severity-critical { @@ -4788,6 +4798,11 @@ p { linear-gradient(180deg, rgba(102, 8, 28, 0.98), rgba(45, 8, 21, 0.98)); border-color: rgba(204, 237, 255, 0.84); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.06), 0 0 14px rgba(162, 231, 255, 0.08); + color: #ffffff; +} + +.mosh.sla-chat-shell .sla-chat-section-danger.sla-severity-critical strong { + color: #ffffff; } .mosh.sla-chat-shell .sla-chat-section-target { @@ -4983,26 +4998,25 @@ p { font-weight: 800; } -.mosh .sla-severity-minor, .mosh .sla-chat-severity-pill.sla-severity-minor { - background: linear-gradient(180deg, rgba(255, 216, 140, 0.32), rgba(255, 189, 79, 0.18)); - border-color: rgba(224, 152, 24, 0.42); - color: #9b5b00; + background: linear-gradient(180deg, #ffc866 0%, #ff9d00 100%); + border-color: #ffbe4a; + color: #1a1000 !important; + text-shadow: none; } -.mosh .sla-severity-severe, .mosh .sla-chat-severity-pill.sla-severity-severe { - background: linear-gradient(180deg, rgba(255, 145, 145, 0.24), rgba(233, 63, 63, 0.14)); - border-color: rgba(179, 27, 27, 0.34); - color: #9e1717; + background: linear-gradient(180deg, #ff8080 0%, #cc0000 100%); + border-color: #ff5b5b; + color: #ffffff !important; + text-shadow: 0 1px 1px rgba(0,0,0,0.5); } -.mosh .sla-severity-critical, .mosh .sla-chat-severity-pill.sla-severity-critical { - background: linear-gradient(180deg, rgba(232, 247, 255, 0.94), rgba(196, 233, 255, 0.82)); - border-color: rgba(84, 145, 199, 0.42); - color: #611120; - box-shadow: 0 0 14px rgba(126, 196, 245, 0.16); + background: linear-gradient(180deg, #9ae6ff 0%, #0099ff 100%); + border-color: #54a5d6; + color: #001a33 !important; + text-shadow: none; } .mosh .sla-chat-section-target { diff --git a/module/actor/actor.js b/module/actor/actor.js index 29f82a2..8d3f248 100644 --- a/module/actor/actor.js +++ b/module/actor/actor.js @@ -2031,19 +2031,29 @@ export class MothershipActor extends Actor { if (rawFormula === "Str/10") { rawFormula = `floor((${this.system.stats.strength.value} + ${(Number(this.system.stats.strength.mod) || 0)}) / 10)`; } + const mult = Number(weapon?.system?.sla?.damageMultiplier ?? weapon?.system?.damageMultiplier ?? 1) || 1; + if (mult > 1) { + if (/^\d+d\d+/i.test(rawFormula)) { + rawFormula = rawFormula.replace(/^(\d+)(d\d+)/i, (match, dice, rest) => { + return `${Number(dice) * mult}${rest}`; + }); + } else { + rawFormula = `(${rawFormula}) * ${mult}`; + } + } const critDamageMode = game.settings.get('sla-mothership', 'critDamage'); if (isCritical) { if (critDamageMode === 'advantage') { - rawFormula = `{${parsedDamageString},${parsedDamageString}}kh`; + rawFormula = `{${rawFormula},${rawFormula}}kh`; } else if (critDamageMode === 'doubleDamage') { - rawFormula = `(${parsedDamageString}) * 2`; + rawFormula = `(${rawFormula}) * 2`; } else if (critDamageMode === 'doubleDice') { - rawFormula = `${parsedDamageString} + ${parsedDamageString}`; + rawFormula = `${rawFormula} + ${rawFormula}`; } else if (critDamageMode === 'maxDamage') { - rawFormula = parsedDamageString.replaceAll('d', ' * '); + rawFormula = rawFormula.replaceAll('d', ' * '); } else if (critDamageMode === 'weaponValue') { - rawFormula = `${parsedDamageString} + ${weapon?.system?.critDmg || 0}`; + rawFormula = `${rawFormula} + ${weapon?.system?.critDmg || 0}`; } } @@ -2587,7 +2597,15 @@ export class MothershipActor extends Actor { //if this is a damage roll if (specialRoll === 'damage') { //parse the roll string - let damageRollString = weapon.system.damage + let damageRollString = weapon.system.damage; + if (weapon && !overrideDamagaRollString) { + const mult = Number(weapon.system?.sla?.damageMultiplier ?? weapon.system?.damageMultiplier ?? 1) || 1; + if (mult > 1) { + damageRollString = damageRollString.replace(/^(\d+)(d\d+)/i, (match, dice, rest) => { + return `${Number(dice) * mult}${rest}`; + }); + } + } if(overrideDamagaRollString){ damageRollString = overrideDamagaRollString; } @@ -2606,8 +2624,10 @@ export class MothershipActor extends Actor { } //prepare flavortext if (weapon.system.damage === "Str/10" && this.type === 'character') { + const mult = Number(weapon.system?.sla?.damageMultiplier ?? weapon.system?.damageMultiplier ?? 1) || 1; + const multStr = mult > 1 ? `${mult} * ` : ''; //determine the damage string - flavorText = 'You strike your target for [[floor((' + this.system.stats.strength.value + ' + ' + (Number(this.system.stats.strength.mod) || 0) + ')/10)]] damage.'; + flavorText = 'You strike your target for [[' + multStr + 'floor((' + this.system.stats.strength.value + ' + ' + (Number(this.system.stats.strength.mod) || 0) + ')/10)]] damage.'; } else { flavorText = 'You inflict [[' + parsedDamageString + '[' + dsnTheme + ']' + critMod + ']] points of damage.'; } diff --git a/module/mosh.js b/module/mosh.js index 194e465..01fe932 100644 --- a/module/mosh.js +++ b/module/mosh.js @@ -152,8 +152,26 @@ Hooks.once('init', async function () { // character generator creates many items in rapid succession (each createItem // hook call would otherwise race to delete the same Ebb items concurrently). const _pendingEbbEnforcement = new Map(); - Hooks.on("createItem", (item) => { + Hooks.on("createItem", async (item, options, userId) => { + // ── Unarmed Combat Auto-Skill Drop ── const actor = item.parent; + if (actor && actor.documentName === "Actor" && item.type === "weapon" && String(item.name).trim().toLowerCase() === "unarmed combat") { + const hasSkill = actor.items.some(i => i.type === "skill" && String(i.name).trim().toLowerCase() === "unarmed combat"); + if (!hasSkill) { + await actor.createEmbeddedDocuments("Item", [{ + name: "Unarmed Combat", + type: "skill", + img: "icons/svg/fist.svg", + system: { + rank: "Trained", + bonus: 10, + sla: { category: "combat", skillFamily: "", source: "Unarmed Item Drop" } + } + }]); + ui.notifications.info(`Added 'Unarmed Combat' skill to ${actor.name}.`); + } + } + if (!actor || actor.type !== "character" || typeof actor.enforceSlaEbbEligibility !== "function") return; if (actor.isSlaEbbUser?.()) return; if (_pendingEbbEnforcement.has(actor.id)) clearTimeout(_pendingEbbEnforcement.get(actor.id)); @@ -358,6 +376,129 @@ Hooks.on("renderChatMessageHTML", (app, html) => { await actor.rollSlaWoundEffect(effectKey, modifier); }); }); + + // ── Critical Hit? Button Handler ── + root.querySelectorAll(".sla-crit-hit-btn").forEach((button) => { + button.addEventListener("click", async (event) => { + event.preventDefault(); + event.stopPropagation(); + + const actorId = button.dataset.actorId; + const itemId = button.dataset.itemId; + const actor = actorId ? game.actors?.get(actorId) : null; + if (!actor) { + ui.notifications.warn("The actor for this critical hit could not be found."); + return; + } + + const weapon = itemId ? actor.items.get(itemId) : null; + + // Determine wound setting key based on weapon and damage type + let settingKey = 'table1eWoundBluntForce'; + let tableLabel = 'Blunt Force'; + + if (weapon) { + const name = String(weapon.name ?? "").toLowerCase(); + const damageType = String(weapon.system?.sla?.damageType ?? "").toLowerCase(); + + if (damageType.includes("blunt") || damageType.includes("impact")) { + settingKey = 'table1eWoundBluntForce'; + tableLabel = 'Blunt Force'; + } else if (damageType.includes("penetrating") || damageType.includes("pierce") || damageType.includes("stab") || damageType.includes("bullet") || damageType.includes("shot")) { + settingKey = 'table1eWoundGunshot'; + tableLabel = 'Gunshot'; + } else if (damageType.includes("slash") || damageType.includes("rend") || damageType.includes("bleed") || damageType.includes("cut")) { + settingKey = 'table1eWoundBleeding'; + tableLabel = 'Bleeding'; + } else if (damageType.includes("fire") || damageType.includes("explos") || damageType.includes("acid") || damageType.includes("burn")) { + settingKey = 'table1eWoundFireExplosives'; + tableLabel = 'Fire & Explosives'; + } else if (name.includes("pistol") || name.includes("smg") || name.includes("rifle") || name.includes("shotgun") || name.includes("blunderbuss") || name.includes("gun") || name.includes("harpoon") || name.includes("bullet") || name.includes("projectile") || name.includes("crossbow")) { + settingKey = 'table1eWoundGunshot'; + tableLabel = 'Gunshot'; + } else if (name.includes("machete") || name.includes("cleaver") || name.includes("whip") || name.includes("blade") || name.includes("knife") || name.includes("sword") || name.includes("claws") || name.includes("bite") || name.includes("razor") || name.includes("teeth")) { + settingKey = 'table1eWoundBleeding'; + tableLabel = 'Bleeding'; + } else if (name.includes("club") || name.includes("pipe") || name.includes("hammer") || name.includes("prod") || name.includes("unarmed") || name.includes("fist") || name.includes("strike") || name.includes("blunt") || name.includes("brawl") || name.includes("cuff")) { + settingKey = 'table1eWoundBluntForce'; + tableLabel = 'Blunt Force'; + } else if (name.includes("bomb") || name.includes("acid") || name.includes("spit") || name.includes("fire") || name.includes("flame") || name.includes("grenade") || name.includes("explosive")) { + settingKey = 'table1eWoundFireExplosives'; + tableLabel = 'Fire & Explosives'; + } + } + + const tableId = game.settings.get('sla-mothership', settingKey); + if (!tableId) { + ui.notifications.warn(`No wound table is configured for ${tableLabel}.`); + return; + } + + // Resolve the table + let resolved = game.tables.get(tableId) || game.tables.getName(tableId); + if (!resolved) { + for (const pack of game.packs) { + if (pack.metadata.type === "RollTable") { + const index = await pack.getIndex(); + const entry = index.find(e => e.id === tableId || e.name === tableId || e.name === `${tableLabel} Wound` || e.name === tableLabel.replace(/&/g, "and")); + if (entry) { + resolved = await pack.getDocument(entry._id); + break; + } + } + } + } + + if (!resolved) { + ui.notifications.warn(`The ${tableLabel} wound table could not be found.`); + return; + } + + await actor.rollTable(resolved.uuid ?? resolved.id ?? tableId, null, null, null, null, null, null); + }); + }); + + // ── Critical Success Option Button Handler ── + root.querySelectorAll(".sla-crit-option-btn").forEach((button) => { + button.addEventListener("click", async (event) => { + event.preventDefault(); + event.stopPropagation(); + + const actorId = button.dataset.actorId; + const optionText = button.dataset.optionText; + const actor = actorId ? game.actors?.get(actorId) : null; + const actorName = actor ? actor.name : "An operative"; + + const chatContent = await foundry.applications.handlebars.renderTemplate( + 'systems/sla-mothership/templates/chat/sla-critical-selection.html', + { + actorName: actorName, + img: actor?.img ?? 'icons/svg/mystery-man.svg', + optionText: optionText + } + ); + + await ChatMessage.create({ + user: game.user.id, + content: chatContent, + speaker: ChatMessage.getSpeaker({ actor: actor }) + }); + + // Disable all options in the card + const container = button.closest(".sla-crit-success-options"); + if (container) { + container.querySelectorAll(".sla-crit-option-btn").forEach(btn => { + btn.disabled = true; + btn.style.opacity = 0.6; + btn.style.cursor = "default"; + if (btn === button) { + btn.style.background = "#0f300f"; + btn.style.border = "1px solid #1ebd1e"; + } + }); + } + }); + }); }); //add custom damage dice for MOSH diff --git a/system.json b/system.json index 38d9b2f..a6317d1 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "sla-mothership", "title": "SLA Mothership", "description": "An SLA Industries conversion built on the unofficial Mothership system for Foundry VTT, preserving Mothership's core mechanics while adding SLA flux, Ebb, BPN, and equipment support.", - "version": "0.6.5", + "version": "0.6.8", "esmodules": [ "module/mosh.js" ], diff --git a/templates/chat/rollCheck.html b/templates/chat/rollCheck.html index 00b9819..8e7c93f 100644 --- a/templates/chat/rollCheck.html +++ b/templates/chat/rollCheck.html @@ -81,6 +81,11 @@ {{#if (compare 'str' specialRoll '===' 'damage')}}