1 Commits
Author SHA1 Message Date
tevans 19b472de00 feat: player permissions enforcement, CSS overhaul, actor/chat template updates
- Enforce sidebar directory restrictions for non-GM players across actors,
  scenes, journal, tables, compendium, and combat
- Major CSS refactor in mosh.css (layout, sizing, colour passes)
- Actor sheet JS: extended item handling and sheet render logic
- Actor data model updates in actor.js
- Item sheet additions in item-sheet.js
- Updated chat templates: itemRoll, modifyActor, modifyItem, reload,
  rollCheck, rollTable for improved formatting and clarity
- Actor/creature sheet HTML restructured
- Compendium pack DB files rotated (LevelDB log/manifest housekeeping)

Co-authored-by: slaguru666 <timevans666@gmail.com>
2026-04-14 22:38:18 +01:00
58 changed files with 1738 additions and 1797 deletions
+748 -1084
View File
File diff suppressed because it is too large Load Diff
+126 -10
View File
@@ -141,6 +141,30 @@ function decorateSkillWithTraitPreview(skill, traitRows = []) {
return skill;
}
function getPurchasableItemTotalCost(itemData = {}) {
const system = itemData?.system ?? itemData?.data ?? {};
const unitCost = Number(system.cost ?? 0) || 0;
const quantity = Math.max(1, Number(system.quantity ?? 1) || 1);
return Math.round(unitCost * quantity * 100) / 100;
}
function isAmmoPurchaseItem(itemData = {}) {
const system = itemData?.system ?? itemData?.data ?? {};
const sla = system?.sla ?? {};
const name = String(itemData?.name ?? "").trim().toLowerCase();
const category = String(sla.category ?? "").trim().toLowerCase();
const calibre = String(sla.calibre ?? "").trim();
const ammoTag = String(sla.ammoTag ?? "").trim();
return category === "ammunition" || Boolean((calibre && ammoTag) || name.startsWith("ammo:"));
}
function formatSlaTimestamp() {
return new Intl.DateTimeFormat(undefined, {
dateStyle: "short",
timeStyle: "short"
}).format(new Date());
}
const SLA_SPECIES_RULES = {
Human: [
{ key: "adaptable", label: "Adaptable", summary: "Once per session, reroll one Stat check and take the better result.", optional: false, default: true }
@@ -677,19 +701,26 @@ export class MothershipActorSheet extends foundry.appv1.sheets.ActorSheet {
//Quantity adjuster
html.on('mousedown', '.item-quantity', ev => {
const li = ev.currentTarget.closest(".item");
//const item = duplicate(this.actor.getEmbeddedDocument("Item", li.dataset.itemId))
var item;
item = foundry.utils.duplicate(this.actor.getEmbeddedDocument("Item", li.dataset.itemId));
let amount = item.system.quantity;
const sourceItem = this.actor.getEmbeddedDocument("Item", li.dataset.itemId);
if (!sourceItem) return;
const item = foundry.utils.duplicate(sourceItem);
const amount = Math.max(0, Number(item.system?.quantity ?? 0) || 0);
let delta = 0;
if (event.button == 0) {
item.system.quantity = Number(amount) + 1;
} else if (event.button == 2) {
item.system.quantity = Number(amount) - 1;
if (ev.button === 0) {
delta = 1;
} else if (ev.button === 2) {
delta = -1;
}
this.actor.updateEmbeddedDocuments('Item', [item]);
if (!delta) return;
item.system.quantity = Math.max(0, amount + delta);
this.actor.updateEmbeddedDocuments('Item', [item]).then(async () => {
if (delta > 0) {
await this._applySlaQuantityIncreaseDebit(sourceItem, delta);
}
});
});
//Severity adjuster
@@ -1197,6 +1228,91 @@ export class MothershipActorSheet extends foundry.appv1.sheets.ActorSheet {
/* -------------------------------------------- */
async _onDrop(event) {
const data = TextEditor.getDragEventData(event);
if (data?.type !== "Item") return super._onDrop(event);
const droppedItem = await Item.implementation.fromDropData(data).catch(() => null);
if (!droppedItem) return super._onDrop(event);
if (droppedItem.parent?.uuid === this.actor.uuid) {
return super._onDrop(event);
}
const shouldDebitPurchase = !droppedItem.parent || droppedItem.parent.documentName !== "Actor";
const itemData = droppedItem.toObject();
delete itemData._id;
delete itemData.folder;
const created = await this.actor.createEmbeddedDocuments("Item", [itemData], {
slaPurchaseDebit: true,
slaPurchaseSource: data.uuid ?? droppedItem.uuid ?? ""
});
if (shouldDebitPurchase) {
await this._applySlaPurchaseDebit(created);
}
return created;
}
async _applySlaPurchaseDebit(createdDocs = []) {
const documents = Array.isArray(createdDocs) ? createdDocs : [createdDocs];
const total = Math.round(documents.reduce((sum, doc) => sum + getPurchasableItemTotalCost(doc?.toObject?.() ?? doc), 0) * 100) / 100;
if (!total) return;
const currentCredits = Number(this.actor.system?.credits?.value ?? 0) || 0;
const newCredits = Math.round((currentCredits - total) * 100) / 100;
const updateData = { "system.credits.value": newCredits };
const itemNames = documents.map((doc) => doc?.name).filter(Boolean).join(", ");
const ammoDocs = documents
.map((doc) => doc?.toObject?.() ?? doc)
.filter((doc) => isAmmoPurchaseItem(doc));
if (ammoDocs.length) {
const stamp = formatSlaTimestamp();
const currentLedger = String(this.actor.system?.sla?.ammoLedger?.value ?? "").trim();
const purchaseEntries = ammoDocs.map((doc) => {
const system = doc?.system ?? doc?.data ?? {};
const quantity = Math.max(1, Number(system.quantity ?? 1) || 1);
const unitCost = Number(system.cost ?? 0) || 0;
const lineCost = Math.round(unitCost * quantity * 100) / 100;
return `[${stamp}] PURCHASE | ${doc?.name ?? "Ammo"} | ${quantity} round${quantity === 1 ? "" : "s"} | -${lineCost} cR | balance ${newCredits} cR`;
});
const ledgerLines = [...purchaseEntries, ...currentLedger.split("\n").filter(Boolean)].slice(0, 60);
updateData["system.sla.ammoLedger.value"] = ledgerLines.join("\n");
}
await this.actor.update(updateData);
ui.notifications.info(`${this.actor.name}: deducted ${total} cR for ${itemNames || "purchased item"}. Credits now ${newCredits} cR.`);
await this.actor.addSlaActivityLog?.(`Purchased ${itemNames || "item"} for ${total} cR. Credits now ${newCredits} cR.`);
}
async _applySlaQuantityIncreaseDebit(itemDoc, increase = 0) {
const added = Math.max(0, Number(increase ?? 0) || 0);
if (!added || !itemDoc) return;
const unitCost = Number(itemDoc.system?.cost ?? 0) || 0;
const total = Math.round(unitCost * added * 100) / 100;
if (!total) return;
const currentCredits = Number(this.actor.system?.credits?.value ?? 0) || 0;
const newCredits = Math.round((currentCredits - total) * 100) / 100;
const updateData = { "system.credits.value": newCredits };
if (isAmmoPurchaseItem(itemDoc)) {
const stamp = formatSlaTimestamp();
const currentLedger = String(this.actor.system?.sla?.ammoLedger?.value ?? "").trim();
const entry = `[${stamp}] PURCHASE | ${itemDoc.name ?? "Ammo"} | ${added} round${added === 1 ? "" : "s"} | -${total} cR | balance ${newCredits} cR`;
const ledgerLines = [entry, ...currentLedger.split("\n").filter(Boolean)].slice(0, 60);
updateData["system.sla.ammoLedger.value"] = ledgerLines.join("\n");
}
await this.actor.update(updateData);
ui.notifications.info(`${this.actor.name}: deducted ${total} cR for ${added} more ${itemDoc.name ?? "item"}. Credits now ${newCredits} cR.`);
await this.actor.addSlaActivityLog?.(`Purchased +${added} ${itemDoc.name ?? "item"} for ${total} cR. Credits now ${newCredits} cR.`);
}
/* -------------------------------------------- */
/**
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
* @param {Event} event The originating click event
+177 -129
View File
@@ -402,14 +402,6 @@ export class MothershipActor extends Actor {
// Prepare Character type specific data
_deriveCharacter() {
const system = this.system;
//clamp stress.value to stress.min so stale stored values never show below the floor
if (system.other?.stress) {
const stressMin = Number(system.other.stress.min ?? 0);
const stressVal = Number(system.other.stress.value ?? 0);
if (Number.isFinite(stressMin) && Number.isFinite(stressVal) && stressVal < stressMin) {
system.other.stress.value = stressMin;
}
}
//armor points + damage reduction
//init vars
let armorPoints = 0;
@@ -497,44 +489,11 @@ export class MothershipActor extends Actor {
}
async useSlaDrug(itemId) {
const result = await SLADrugSystem.useDrug({ actor: this, itemId });
if (result?.ok !== false) {
const item = this.items.get(itemId);
if (item) await this.addSlaActivityLog(`Drug used: ${item.name}.`);
}
return result;
return SLADrugSystem.useDrug({ actor: this, itemId });
}
async closeSlaDrug(itemId = "", drug = "") {
const result = await SLADrugSystem.closeDrug({ actor: this, itemId, drug });
if (result?.ok !== false) {
const drugName = drug || (itemId ? (this.items.get(itemId)?.name ?? "Unknown drug") : "Unknown drug");
await this.addSlaActivityLog(`Drug effect ended: ${drugName}.`);
}
return result;
}
/**
* Append a timestamped line to the operative's activity log.
* Keeps newest entries at the top and trims to ~10 KB.
*/
async addSlaActivityLog(entry) {
try {
const now = new Date();
const ts = now.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
const line = `[${ts}] ${entry}`;
const current = String(this.system?.sla?.activityLog?.value ?? "");
const combined = current ? `${line}\n${current}` : line;
// Trim oldest lines until under 10 KB
const MAX = 10240;
if (combined.length > MAX) {
const lines = combined.split("\n");
while (lines.join("\n").length > MAX && lines.length > 1) lines.pop();
await this.update({ "system.sla.activityLog.value": lines.join("\n") });
} else {
await this.update({ "system.sla.activityLog.value": combined });
}
} catch (_) { /* log failures are non-fatal */ }
return SLADrugSystem.closeDrug({ actor: this, itemId, drug });
}
//central flavor text library for all chat messages
@@ -640,9 +599,123 @@ export class MothershipActor extends Actor {
return rollStringParsed;
}
clampSlaRollTarget(rollTarget, rollString = "") {
const numericTarget = Number(rollTarget);
if (!Number.isFinite(numericTarget)) return rollTarget;
const usesD100 = String(rollString ?? "").includes("1d100");
if (!usesD100) return numericTarget;
return Math.min(98, numericTarget);
}
isSlaAutomaticFailure(total, rollString = "") {
const numericTotal = Number(total);
if (!Number.isFinite(numericTotal)) return false;
const usesD100 = String(rollString ?? "").includes("1d100");
return usesD100 && numericTotal === 99;
}
getSlaChatVariant(context = {}) {
const actorClass = String(this.system?.class?.value ?? "").trim();
const employer = String(this.system?.sla?.employer?.value ?? "").trim();
const sector = String(this.system?.sla?.sector?.value ?? "").trim();
const skill = String(context?.skill ?? "").trim();
const skillCategory = String(context?.skillCategory ?? "").trim();
const tableName = String(context?.tableName ?? "").trim();
const header = String(context?.msgHeader ?? "").trim();
const weaponName = String(context?.weapon?.name ?? "").trim();
const itemName = String(context?.item?.name ?? "").trim();
const extra = String(context?.extra ?? "").trim();
const haystack = [
actorClass,
employer,
sector,
skill,
skillCategory,
tableName,
header,
weaponName,
itemName,
extra
].join(" ").toLowerCase();
if (context?.isSlaEbbSkill || /\bebb\b|ebon|brain\swaste|flux|telekinesis|blast|awareness/.test(haystack)) {
return { key: "ebb", label: "Ebb", emblem: "EBB" };
}
if (/\bdark\s?night\b/.test(haystack)) {
return { key: "darknight", label: "DarkNight", emblem: "DN" };
}
if (/\bshiver\b/.test(haystack)) {
return { key: "shiver", label: "Shiver", emblem: "SV" };
}
if (/\bmort\b|mortuary|undertaker|corpse|cadaver/.test(haystack)) {
return { key: "mort", label: "Mort", emblem: "MT" };
}
if (/\btek\b|technical|engineer|engineering|medic|medical|science|research/.test(haystack)) {
return { key: "tek", label: "Tek", emblem: "TK" };
}
if (/\bops\b|operations|opscon|ops\scon|operative|squad/.test(haystack)) {
return { key: "ops", label: "Ops", emblem: "OP" };
}
return { key: "sla", label: "SLA", emblem: "SLA" };
}
getSlaSeverityMeta({ severity = null, tableName = "", specialRoll = "", tableResultNumber = null } = {}) {
const rawSeverity = Number(severity);
if (Number.isFinite(rawSeverity)) {
if (rawSeverity >= 5) return { key: "critical", label: "Critical", cssClass: "sla-severity-critical" };
if (rawSeverity >= 3) return { key: "severe", label: "Severe", cssClass: "sla-severity-severe" };
return { key: "minor", label: "Minor", cssClass: "sla-severity-minor" };
}
const name = String(tableName ?? "").toLowerCase();
const special = String(specialRoll ?? "").toLowerCase();
const numericResult = Number(tableResultNumber);
if (!Number.isFinite(numericResult)) {
return { key: "minor", label: "Minor", cssClass: "sla-severity-minor" };
}
const isPanic = special === "paniccheck" || name.includes("panic");
const isWound = name.includes("wound");
if (isPanic) {
if (numericResult >= 16) return { key: "critical", label: "Catastrophic", cssClass: "sla-severity-critical" };
if (numericResult >= 9) return { key: "severe", label: "Severe", cssClass: "sla-severity-severe" };
return { key: "minor", label: "Minor", cssClass: "sla-severity-minor" };
}
if (isWound) {
if (numericResult >= 8) return { key: "critical", label: "Critical", cssClass: "sla-severity-critical" };
if (numericResult >= 4) return { key: "severe", label: "Severe", cssClass: "sla-severity-severe" };
return { key: "minor", label: "Minor", cssClass: "sla-severity-minor" };
}
return { key: "minor", label: "Minor", cssClass: "sla-severity-minor" };
}
async addSlaActivityLog(entry, { maxEntries = 40 } = {}) {
const text = String(entry ?? "").trim();
if (!text || this.type !== "character") return null;
const timestamp = new Date().toLocaleString("en-GB", {
day: "2-digit",
month: "2-digit",
year: "2-digit",
hour: "2-digit",
minute: "2-digit"
});
const currentLog = String(this.system?.sla?.activityLog?.value ?? "").trim();
const existingLines = currentLog ? currentLog.split("\n").filter(Boolean) : [];
const nextLines = [`[${timestamp}] ${text}`, ...existingLines].slice(0, Math.max(1, maxEntries));
await this.update({ "system.sla.activityLog.value": nextLines.join("\n") });
return nextLines[0];
}
//central roll parsing function | TAKES '1d100',[Foundry roll object],true,true,41,'<' | RETURNS enriched Foundry roll object
parseRollResult(rollString, rollResult, zeroBased, checkCrit, rollTarget, comparison, specialRoll) {
//init vars
rollTarget = this.clampSlaRollTarget(rollTarget, rollString);
let doubles = new Set([0, 11, 22, 33, 44, 55, 66, 77, 88, 99]);
let enrichedRollResult = rollResult;
let rollFormula = enrichedRollResult.formula;
@@ -711,29 +784,29 @@ export class MothershipActor extends Actor {
if (rollString.includes("[")) {
//set whether each die succeeded
//die 0
if (comparison === '<' && die0value < rollTarget && die0value < 90) {
if (comparison === '<' && die0value < rollTarget && !this.isSlaAutomaticFailure(die0value, rollString)) {
die0success = true;
}
if (comparison === '<=' && die0value <= rollTarget && die0value < 90) {
if (comparison === '<=' && die0value <= rollTarget && !this.isSlaAutomaticFailure(die0value, rollString)) {
die0success = true;
}
if (comparison === '>' && die0value > rollTarget && die0value < 90) {
if (comparison === '>' && die0value > rollTarget && !this.isSlaAutomaticFailure(die0value, rollString)) {
die0success = true;
}
if (comparison === '>=' && die0value >= rollTarget && die0value < 90) {
if (comparison === '>=' && die0value >= rollTarget && !this.isSlaAutomaticFailure(die0value, rollString)) {
die0success = true;
}
//die 1
if (comparison === '<' && die1value < rollTarget && die1value < 90) {
if (comparison === '<' && die1value < rollTarget && !this.isSlaAutomaticFailure(die1value, rollString)) {
die1success = true;
}
if (comparison === '<=' && die1value <= rollTarget && die1value < 90) {
if (comparison === '<=' && die1value <= rollTarget && !this.isSlaAutomaticFailure(die1value, rollString)) {
die1success = true;
}
if (comparison === '>' && die1value > rollTarget && die1value < 90) {
if (comparison === '>' && die1value > rollTarget && !this.isSlaAutomaticFailure(die1value, rollString)) {
die1success = true;
}
if (comparison === '>=' && die1value >= rollTarget && die1value < 90) {
if (comparison === '>=' && die1value >= rollTarget && !this.isSlaAutomaticFailure(die1value, rollString)) {
die1success = true;
}
//set whether each die are a crit
@@ -875,8 +948,8 @@ export class MothershipActor extends Actor {
//add data point: detect success/failure
if (rollTarget || rollTarget === 0) {
//check for auto failure
if (enrichedRollResult.total >= 90) {
//result >= 90 is a failure
if (this.isSlaAutomaticFailure(enrichedRollResult.total, rollString)) {
// result 99 on d100 is always a failure
enrichedRollResult.success = false;
} else {
//compare values based on compararison setting
@@ -951,7 +1024,7 @@ export class MothershipActor extends Actor {
compareIcon = '<i class="fas fa-greater-than-equal"></i>';
}
//prepare formula
if (rollTarget) {
if (rollTarget || rollTarget === 0) {
//show dice against target
diceFormula = rollString + ' ' + compareIcon + ' ' + rollTarget;
} else {
@@ -1376,6 +1449,12 @@ export class MothershipActor extends Actor {
}
//generate chat message
//prepare data
const severityMeta = this.getSlaSeverityMeta({
tableName,
specialRoll,
tableResultNumber
});
let messageData = {
actor: this,
tableResult: tableResult,
@@ -1390,7 +1469,14 @@ export class MothershipActor extends Actor {
woundText: woundText,
secondRoll: secondRoll,
parsedRollResult2: parsedRollResult2,
specialRoll: specialRoll
specialRoll: specialRoll,
severityMeta: severityMeta,
chatStyle: this.getSlaChatVariant({
tableName,
msgHeader: tableName,
isSlaEbbSkill: false,
extra: specialRoll
})
};
//prepare template
messageTemplate = 'systems/sla-mothership/templates/chat/rollTable.html';
@@ -1409,6 +1495,7 @@ export class MothershipActor extends Actor {
}, {
keepId: true
});
await this.addSlaActivityLog?.(`${tableName}: ${parsedRollResult.success ? "success" : "failure"} on ${rollString}${tableResultNumber !== null ? ` -> ${tableResultNumber}` : ""}.`);
if (game.modules.get("dice-so-nice") && game.modules.get("dice-so-nice").active) {
//log what was done
slaDebug(`Rolled on table ID: ${tableId}, with: rollString:${rollString}, aimFor:${aimFor}, zeroBased:${zeroBased}, checkCrit:${checkCrit}, rollAgainst:${rollAgainst}, comparison:${comparison}`);
@@ -1691,7 +1778,7 @@ export class MothershipActor extends Actor {
window: {title: dlgTitle},
classes: ["macro-popup-dialog"],
position: {width: 600},
content: skillHeader + `<div class="skill-dialog-scroll">${skillList}</div>` + buttonDesc,
content: skillHeader + skillList + buttonDesc,
buttons: []
};
//add adv/normal/dis buttons if we need a rollString
@@ -2513,7 +2600,13 @@ export class MothershipActor extends Actor {
targetingResult: targetingResult,
needsDesc: needsDesc,
woundEffect: woundEffect,
specialRoll: specialRoll
specialRoll: specialRoll,
chatStyle: this.getSlaChatVariant({
weapon,
msgHeader,
isSlaEbbSkill: false,
extra: ammoContext?.label ?? ""
})
};
let chatData = {
user: game.user.id,
@@ -2528,6 +2621,7 @@ export class MothershipActor extends Actor {
const content = await foundry.applications.handlebars.renderTemplate(template, messageData);
chatData.content = content;
await ChatMessage.create(chatData);
await this.addSlaActivityLog?.(`${weapon.name}: damage roll resolved.${targetingResult ? ` ${targetingResult.targetName} took ${targetingResult.finalDamage} damage.` : ""}`);
//log what was done
console.log(`Rolled damage on:${weapon.name}`);
//return messageData
@@ -2545,6 +2639,13 @@ export class MothershipActor extends Actor {
} else {
rollTarget = rollTargetOverride;
}
rollTarget = this.clampSlaRollTarget(rollTarget, rollString);
if (effectiveRollBreakdown && (String(rollString ?? "").includes("1d100"))) {
effectiveRollBreakdown = {
...effectiveRollBreakdown,
total: rollTarget
};
}
//roll the dice
//parse the roll string
let parsedRollString = this.parseRollString(rollString, aimFor);
@@ -2707,9 +2808,9 @@ export class MothershipActor extends Actor {
}
if (ammoUsage) {
const roundLabel = ammoUsage.shots === 1 ? 'round' : 'rounds';
const ammoPrefix = `<strong>${ammoUsage.label}</strong> fire with <strong>${ammoUsage.ammoLabel}</strong> spent <strong>${ammoUsage.shots}</strong> ${roundLabel}. Magazine <strong>${ammoUsage.loaded}/${weapon.system.shots}</strong>. Reserve <strong>${ammoUsage.reserve}</strong>. Ammo cost <strong>${ammoUsage.totalCost}</strong> cR. ${ammoUsage.ammoRule}`;
const ammoPrefix = `<strong>${ammoUsage.label}</strong> fire with <strong>${ammoUsage.ammoLabel}</strong> spent <strong>${ammoUsage.shots}</strong> ${roundLabel}. Magazine <strong>${ammoUsage.loaded}/${weapon.system.shots}</strong>. Reserve <strong>${ammoUsage.reserve}</strong>. Firing spend <strong>${ammoUsage.totalCost}</strong> cR. ${ammoUsage.ammoRule}`;
const accountingText = ammoAccounting
? ` Credits now <strong>${ammoAccounting.newCredits}</strong> cR. Running ammo spend <strong>${ammoAccounting.spendTotal}</strong> cR.`
? ` Credits now <strong>${ammoAccounting.newCredits}</strong> cR. Running firing spend <strong>${ammoAccounting.spendTotal}</strong> cR.`
: "";
flavorText = flavorText ? `${ammoPrefix}${accountingText}<br>${flavorText}` : `${ammoPrefix}${accountingText}`;
}
@@ -2999,7 +3100,14 @@ export class MothershipActor extends Actor {
targetingResult: targetingResult,
firstEdition: game.settings.get('sla-mothership', 'firstEdition'),
useCalm: game.settings.get('sla-mothership', 'useCalm'),
androidPanic: game.settings.get('sla-mothership', 'androidPanic')
androidPanic: game.settings.get('sla-mothership', 'androidPanic'),
chatStyle: this.getSlaChatVariant({
weapon,
skill,
skillCategory: String(attackContext?.skillCategory ?? "").trim(),
msgHeader,
isSlaEbbSkill
})
};
//prepare template
messageTemplate = 'systems/sla-mothership/templates/chat/rollCheck.html';
@@ -3018,6 +3126,11 @@ export class MothershipActor extends Actor {
}, {
keepId: true
});
const skillLabel = String(skill ?? "").trim();
const actionLabel = weapon
? weapon.name
: (skillLabel ? `${attributeLabel} / ${skillLabel}` : attributeLabel);
await this.addSlaActivityLog?.(`${actionLabel}: ${parsedRollResult.success ? "success" : "failure"} (${parsedRollResult.total} vs ${rollTarget}).`);
//is DSN active?
if (game.modules.get("dice-so-nice") && game.modules.get("dice-so-nice").active) {
//log what was done
@@ -3040,18 +3153,6 @@ export class MothershipActor extends Actor {
await this.rollTable("panicCheck", null, null, null, null, null, null);
}
}
// Activity log
if (this.type === "character" && specialRoll !== "damage") {
try {
const rollLabel = skill && skill !== "none" ? skill : (attribute ? String(attribute).charAt(0).toUpperCase() + String(attribute).slice(1) : "Roll");
const resultNum = Number(parsedRollResult?.total ?? 0);
const outcome = parsedRollResult?.success
? (parsedRollResult.critical ? "Critical success" : "Pass")
: (parsedRollResult?.critical ? "Critical failure" : "Fail");
const critTag = parsedRollResult?.critical ? " ★" : "";
await this.addSlaActivityLog(`${rollLabel}: rolled ${resultNum} vs ${rollTarget} — ${outcome}${critTag}`);
} catch (_) {}
}
return [messageData];
}
@@ -4812,57 +4913,12 @@ export class MothershipActor extends Actor {
getSlaSpeciesMinimumWounds() {
const species = String(this.system?.sla?.species?.value ?? "").trim();
if (["Frother", "Shaktar", "Stormer 313 Malice", "Stormer 711 Xeno", "Stormer Vevaphon"].includes(species)) {
if (["Frother", "Shaktar", "Stormer 313 Malice", "Stormer 711 Xeno"].includes(species)) {
return 3;
}
return 2;
}
isSlaVevaphon() {
const species = String(this.system?.sla?.species?.value ?? "").trim();
return species === "Stormer Vevaphon";
}
getSlaVevaphonInstability() {
return Math.max(0, Math.min(12, Number(this.system?.sla?.vevaphonInstability?.value ?? 0) || 0));
}
getSlaVevaphonMorphForm() {
return String(this.system?.sla?.morphForm?.value ?? "").trim() || "Brute Form";
}
async setSlaVevaphonMorphForm(formName) {
if (!this.isSlaVevaphon()) return null;
const validForms = ["Brute Form", "Stalker Form", "Raptor Form"];
const resolved = validForms.find((entry) => entry === formName) ?? formName;
const currentInstability = this.getSlaVevaphonInstability();
const newInstability = Math.min(12, currentInstability + 1);
await this.update({
"system.sla.morphForm.value": resolved,
"system.sla.vevaphonInstability.value": newInstability
});
const instabilityWarning = newInstability >= 10
? " ⚠ Instability critical — Morph Panic risk on failed Sanity save."
: newInstability >= 6
? " Instability elevated — minor morph effects active."
: "";
ui.notifications.info(`${this.name} shifts to ${resolved}. Instability: ${newInstability}/12.${instabilityWarning}`);
return { morphForm: resolved, instability: newInstability };
}
async adjustSlaVevaphonInstability(delta = 1) {
if (!this.isSlaVevaphon()) return null;
const current = this.getSlaVevaphonInstability();
const next = Math.max(0, Math.min(12, current + delta));
await this.update({ "system.sla.vevaphonInstability.value": next });
if (next >= 12) {
ui.notifications.warn(`${this.name} has reached maximum Instability (12). The Vevaphon is lost — immediate retirement or death.`);
} else if (next >= 10) {
ui.notifications.warn(`${this.name} — Instability ${next}/12. Morph Panic risk: failed Sanity save triggers a Morph Panic event.`);
}
return next;
}
async enforceSlaSpeciesWounds() {
if (this.type !== "character") return null;
const minimumWounds = this.getSlaSpeciesMinimumWounds();
@@ -4961,14 +5017,7 @@ export class MothershipActor extends Actor {
return this.items
.filter((item) => {
const name = String(item.name ?? "").trim();
if (item.type === "ability") {
// Vevaphon morph form abilities are NOT Ebb items — exclude them
if (item.system?.sla?.morphForm === true) return false;
// Also exclude by name so hand-placed morph abilities are safe even without the flag
const morphNames = ["Brute Form", "Stalker Form", "Raptor Form"];
if (morphNames.includes(name)) return false;
return true;
}
if (item.type === "ability") return true;
if (item.type !== "skill") return false;
return name === "Formulate" || name === "Biofeedback" || name === "Ebb (Core)" || name.startsWith("Ebb ");
})
@@ -5344,8 +5393,6 @@ export class MothershipActor extends Actor {
if (shouldPanic) {
await this.rollTable("panicCheck", null, null, null, null, null, null);
}
const saveOutcome = saveResult ? (saveResult.success ? "Pass" : "Fail") : "n/a";
await this.addSlaActivityLog(`Flux ${forcePanic ? "Panic forced" : "Save"} — Fear save ${saveOutcome}. Flux ${this.system?.sla?.flux?.value ?? 0}/${this.system?.sla?.flux?.max ?? 0}.${shouldPanic ? " Panic triggered." : ""}`);
return { saveResult, state, panicTriggered: shouldPanic };
}
@@ -5531,7 +5578,8 @@ export class MothershipActor extends Actor {
insert: rollInsert,
onlyDesc: true,
swapNameDesc,
swapName
swapName,
severityMeta: this.getSlaSeverityMeta({ severity: item?.system?.severity })
};
const chatData = {
+44
View File
@@ -1,5 +1,22 @@
import { SLADrugSystem } from "../sla-drug-system.js";
function isAmmoPurchaseItem(itemData = {}) {
const system = itemData?.system ?? {};
const sla = system?.sla ?? {};
const name = String(itemData?.name ?? "").trim().toLowerCase();
const category = String(sla.category ?? "").trim().toLowerCase();
const calibre = String(sla.calibre ?? "").trim();
const ammoTag = String(sla.ammoTag ?? "").trim();
return category === "ammunition" || Boolean((calibre && ammoTag) || name.startsWith("ammo:"));
}
function formatSlaTimestamp() {
return new Intl.DateTimeFormat(undefined, {
dateStyle: "short",
timeStyle: "short"
}).format(new Date());
}
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
@@ -128,11 +145,38 @@ export class MothershipItemSheet extends foundry.appv1.sheets.ItemSheet {
async _updateObject(event, formData) {
const updateData = foundry.utils.expandObject(formData);
const currentQty = Math.max(0, Number(this.item.system?.quantity ?? 0) || 0);
if (Object.prototype.hasOwnProperty.call(updateData?.system ?? {}, "weight")) {
const parsed = Number(updateData.system.weight ?? 0) || 0;
updateData.system.weight = Math.round(parsed * 10) / 10;
}
await this.item.update(updateData, { diff: false });
const nextQtyRaw = updateData?.system?.quantity;
const nextQty = nextQtyRaw === undefined ? currentQty : Math.max(0, Number(nextQtyRaw ?? 0) || 0);
const increase = Math.max(0, nextQty - currentQty);
const actor = this.item.parent;
if (!increase || actor?.documentName !== "Actor") return;
const unitCost = Number(updateData?.system?.cost ?? this.item.system?.cost ?? 0) || 0;
const total = Math.round(unitCost * increase * 100) / 100;
if (!total) return;
const currentCredits = Number(actor.system?.credits?.value ?? 0) || 0;
const newCredits = Math.round((currentCredits - total) * 100) / 100;
const updateActorData = { "system.credits.value": newCredits };
if (isAmmoPurchaseItem(this.item.toObject())) {
const stamp = formatSlaTimestamp();
const currentLedger = String(actor.system?.sla?.ammoLedger?.value ?? "").trim();
const entry = `[${stamp}] PURCHASE | ${this.item.name ?? "Ammo"} | ${increase} round${increase === 1 ? "" : "s"} | -${total} cR | balance ${newCredits} cR`;
const ledgerLines = [entry, ...currentLedger.split("\n").filter(Boolean)].slice(0, 60);
updateActorData["system.sla.ammoLedger.value"] = ledgerLines.join("\n");
}
await actor.update(updateActorData);
ui.notifications.info(`${actor.name}: deducted ${total} cR for ${increase} more ${this.item.name ?? "item"}. Credits now ${newCredits} cR.`);
await actor.addSlaActivityLog?.(`Purchased +${increase} ${this.item.name ?? "item"} for ${total} cR. Credits now ${newCredits} cR.`);
}
}
+170 -84
View File
@@ -175,16 +175,37 @@ Hooks.once('init', async function () {
Hooks.on("renderActorDirectory", (app, html) => {
installSlaSidebarButtons(app, html);
if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("actors", html);
});
Hooks.on("renderSidebarTab", (app, html) => {
const isActorsTab = app?.tabName === "actors" || app?.options?.id === "actors";
if (!isActorsTab) return;
installSlaSidebarButtons(app, html);
if (isActorsTab) installSlaSidebarButtons(app, html);
if (!game.user?.isGM) {
hideSlaRestrictedSidebarTabs();
enforceSlaPlayerSidebarTabRestrictions(app, html);
}
});
Hooks.on("renderSceneDirectory", (_app, html) => {
if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("scenes", html);
});
Hooks.on("renderJournalDirectory", (_app, html) => {
if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("journal", html);
});
Hooks.on("renderRollTableDirectory", (_app, html) => {
if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("tables", html);
});
Hooks.on("renderCompendiumDirectory", (_app, html) => {
if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("compendium", html);
});
Hooks.on("renderCombatTracker", (app, html) => {
installSlaCombatTrackerControls(app, html);
if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("combat", html);
});
Hooks.on("updateCombat", async (combat, changed) => {
@@ -247,6 +268,7 @@ Hooks.once("ready", async function () {
}
patchSlaInitiativeSystem();
if (!game.user?.isGM) hideSlaRestrictedSidebarTabs();
});
@@ -1254,33 +1276,19 @@ export function formatCreditsNumber(num) {
// SLA Industries Pause Screen Animation
// ─────────────────────────────────────────────────────────────────────────────
let slaPauseRefreshHandle = null;
function injectSlaPauseAnimation() {
const pauseEl = document.querySelector('#pause');
if (!pauseEl) return;
// Don't inject twice
if (pauseEl.querySelector('.sla-pause-root')) return;
// Aggressively hide ALL of Foundry's default pause content
// Hide every direct child that isn't our injected element
Array.from(pauseEl.children).forEach(child => {
child.style.setProperty('display', 'none', 'important');
});
// Also kill any descendant with animation or image content
pauseEl.querySelectorAll('figure, img, svg, h3, p, .fas, .fa-cog, canvas').forEach(el => {
el.style.setProperty('display', 'none', 'important');
});
document.body?.classList.add("sla-pause-active");
if (document.querySelector('.sla-pause-root')) {
return;
}
const svgHTML = `
<div class="sla-pause-root" aria-label="SLA Industries – Game Paused">
<svg
class="sla-pause-svg"
viewBox="-120 -120 240 260"
xmlns="http://www.w3.org/2000/svg"
role="img"
>
<div class="sla-pause-stage">
<svg class="sla-pause-svg" viewBox="-120 -120 240 260" xmlns="http://www.w3.org/2000/svg" role="img">
<defs>
<!-- Red glow filter for the logo and satellites -->
<filter id="sla-glow-red" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3.5" result="blur"/>
<feMerge>
@@ -1288,7 +1296,6 @@ function injectSlaPauseAnimation() {
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<!-- Subtle glow for rings -->
<filter id="sla-glow-ring" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="1.5" result="blur"/>
<feMerge>
@@ -1296,23 +1303,17 @@ function injectSlaPauseAnimation() {
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<!-- Radial gradient for background disc -->
<radialGradient id="sla-bg-grad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#1a0000" stop-opacity="0.95"/>
<stop offset="100%" stop-color="#000000" stop-opacity="0.0"/>
</radialGradient>
<!-- Clip for the inner content -->
<clipPath id="sla-clip-outer">
<circle cx="0" cy="0" r="108"/>
</clipPath>
</defs>
<!-- Ambient background disc -->
<circle cx="0" cy="0" r="108" fill="url(#sla-bg-grad)"/>
<!-- ── Outer ring (clockwise) ── -->
<circle cx="0" cy="0" r="108" fill="url(#sla-bg-grad)" class="sla-pause-core-glow"/>
<g class="sla-ring-outer">
<!-- Dashed orbit path -->
<animateTransform attributeName="transform" type="rotate" from="0 0 0" to="360 0 0" dur="18s" repeatCount="indefinite"/>
<circle cx="0" cy="0" r="90"
fill="none"
stroke="#8b1a1a"
@@ -1321,11 +1322,9 @@ function injectSlaPauseAnimation() {
opacity="0.7"
filter="url(#sla-glow-ring)"
/>
<!-- 3 red satellite dots at 0°, 120°, 240° -->
<circle cx="0" cy="-90" r="5" fill="#cc2222" filter="url(#sla-glow-red)" class="sla-sat"/>
<circle cx="77.9" cy="45" r="5" fill="#cc2222" filter="url(#sla-glow-red)" class="sla-sat"/>
<circle cx="-77.9" cy="45" r="5" fill="#cc2222" filter="url(#sla-glow-red)" class="sla-sat"/>
<!-- Thin tick marks every 30° on outer ring -->
<line x1="0" y1="-86" x2="0" y2="-94" stroke="#8b1a1a" stroke-width="1" transform="rotate(0)"/>
<line x1="0" y1="-86" x2="0" y2="-94" stroke="#8b1a1a" stroke-width="1" transform="rotate(30)"/>
<line x1="0" y1="-86" x2="0" y2="-94" stroke="#8b1a1a" stroke-width="1" transform="rotate(60)"/>
@@ -1339,9 +1338,8 @@ function injectSlaPauseAnimation() {
<line x1="0" y1="-86" x2="0" y2="-94" stroke="#8b1a1a" stroke-width="1" transform="rotate(300)"/>
<line x1="0" y1="-86" x2="0" y2="-94" stroke="#8b1a1a" stroke-width="1" transform="rotate(330)"/>
</g>
<!-- ── Inner ring (counter-clockwise) ── -->
<g class="sla-ring-inner">
<animateTransform attributeName="transform" type="rotate" from="360 0 0" to="0 0 0" dur="13s" repeatCount="indefinite"/>
<circle cx="0" cy="0" r="62"
fill="none"
stroke="#6b1010"
@@ -1350,15 +1348,11 @@ function injectSlaPauseAnimation() {
opacity="0.6"
filter="url(#sla-glow-ring)"
/>
<!-- 3 smaller orange-red dots at 60°, 180°, 300° offset for variety -->
<circle cx="53.7" cy="-31" r="3.5" fill="#dd4422" filter="url(#sla-glow-red)" class="sla-sat-inner"/>
<circle cx="-53.7" cy="-31" r="3.5" fill="#dd4422" filter="url(#sla-glow-red)" class="sla-sat-inner"/>
<circle cx="0" cy="62" r="3.5" fill="#dd4422" filter="url(#sla-glow-red)" class="sla-sat-inner"/>
</g>
<!-- ── Central "SLA" logotype ── -->
<g filter="url(#sla-glow-red)">
<!-- Hexagonal backing plate -->
<g filter="url(#sla-glow-red)" class="sla-pause-logo-core">
<polygon
points="0,-38 33,-19 33,19 0,38 -33,19 -33,-19"
fill="#1a0000"
@@ -1366,7 +1360,6 @@ function injectSlaPauseAnimation() {
stroke-width="1.2"
opacity="0.9"
/>
<!-- SLA text -->
<text
x="0" y="14"
text-anchor="middle"
@@ -1378,35 +1371,27 @@ function injectSlaPauseAnimation() {
class="sla-pause-logo-text"
>SLA</text>
</g>
<!-- ── Thin separator lines ── -->
<line x1="-70" y1="48" x2="70" y2="48" stroke="#8b1a1a" stroke-width="0.5" opacity="0.7"/>
<!-- ── "STAND BY" label ── -->
<text
x="0" y="70"
x="0" y="68"
text-anchor="middle"
font-family="'Oswald', 'Courier New', monospace"
font-size="13"
font-weight="400"
letter-spacing="5"
fill="#cc4444"
font-size="11.5"
font-weight="500"
letter-spacing="3.2"
fill="#ff7b7b"
class="sla-pause-standby"
>STAND BY</text>
<!-- ── "SLA INDUSTRIES" footer text ── -->
>PLEASE STAND BY</text>
<text
x="0" y="95"
x="0" y="94"
text-anchor="middle"
font-family="'Oswald', 'Arial Narrow', sans-serif"
font-size="7"
font-size="8"
font-weight="300"
letter-spacing="3"
fill="#661111"
opacity="0.85"
fill="#b13b3b"
opacity="0.92"
>SLA INDUSTRIES</text>
<!-- ── Scanning line sweep ── -->
<line
x1="0" y1="-90" x2="0" y2="0"
stroke="#ff2222"
@@ -1415,37 +1400,138 @@ function injectSlaPauseAnimation() {
class="sla-pause-scan"
/>
</svg>
</div>
</div>`;
document.body?.insertAdjacentHTML('beforeend', svgHTML);
}
// Inject directly into the pause element (not into figure, which we hid)
pauseEl.insertAdjacentHTML('beforeend', svgHTML);
function removeSlaPauseAnimation() {
document.querySelector(".sla-pause-root")?.remove();
document.body?.classList.remove("sla-pause-active");
}
function queueSlaPauseRefresh(delay = 0) {
window.clearTimeout(slaPauseRefreshHandle);
slaPauseRefreshHandle = window.setTimeout(() => {
if (!game?.paused) return;
injectSlaPauseAnimation();
}, delay);
}
Hooks.once('ready', () => {
// Inject if game is already paused on load
if (game.paused) injectSlaPauseAnimation();
});
// Use the pauseGame hook exclusively — don't use a MutationObserver
// which could fight Foundry's own show/hide logic on the #pause element.
Hooks.on('pauseGame', (paused) => {
if (paused) {
// Two-frame delay — Foundry renders the pause figure asynchronously
requestAnimationFrame(() => requestAnimationFrame(() => injectSlaPauseAnimation()));
} else {
// On unpause: remove our injected content so it can be re-injected cleanly next time
const slaRoot = document.querySelector('#pause .sla-pause-root');
if (slaRoot) slaRoot.remove();
// Restore Foundry's original pause children (they were hidden inline, unhide them)
const pauseEl = document.querySelector('#pause');
if (pauseEl) {
pauseEl.querySelectorAll(':scope > *:not(.sla-pause-root)').forEach(child => {
child.style.removeProperty('display');
});
}
if (game.paused) {
queueSlaPauseRefresh(0);
queueSlaPauseRefresh(60);
queueSlaPauseRefresh(180);
}
});
Hooks.on('pauseGame', (paused) => {
if (paused) {
requestAnimationFrame(() => requestAnimationFrame(() => injectSlaPauseAnimation()));
queueSlaPauseRefresh(0);
queueSlaPauseRefresh(60);
queueSlaPauseRefresh(180);
queueSlaPauseRefresh(360);
} else {
removeSlaPauseAnimation();
}
});
function getSlaHtmlRoot(html) {
return html instanceof HTMLElement ? html : html?.[0] ?? null;
}
function hideSlaRestrictedSidebarTabs() {
[
"#sidebar-tabs [data-tab='tables']",
"#sidebar-tabs [aria-controls='tables']",
"#sidebar-tabs [data-tab='compendium']",
"#sidebar-tabs [aria-controls='compendium']"
].forEach((selector) => {
document.querySelectorAll(selector).forEach((el) => {
el.closest("li")?.remove();
if (el instanceof HTMLElement) el.remove();
});
});
}
function removeSlaElements(root, selectors = []) {
selectors.forEach((selector) => {
root.querySelectorAll(selector).forEach((el) => el.remove());
});
}
function pruneEmptySlaFolders(root) {
const folders = Array.from(root.querySelectorAll(".directory-item.folder"));
folders.reverse().forEach((folder) => {
const visibleDocuments = Array.from(folder.querySelectorAll(".directory-item.document, li.document")).filter((node) => node.offsetParent !== null);
const visibleFolders = Array.from(folder.querySelectorAll(":scope > .directory-list > .directory-item.folder, :scope > ol > li.folder")).filter((node) => node.offsetParent !== null);
if (!visibleDocuments.length && !visibleFolders.length) folder.remove();
});
}
function shouldShowSlaPlayerJournal(entry) {
if (!entry) return false;
const name = String(entry.name ?? "").trim();
if (/^operative handbook$/i.test(name)) return true;
if (entry.getFlag?.("sla-mothership", "playerVisible") === true) return true;
return entry.testUserPermission?.(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) ?? false;
}
function filterSlaJournalDirectory(root) {
const rows = Array.from(root.querySelectorAll("[data-entry-id], [data-document-id]"));
rows.forEach((row) => {
const id = row.dataset.entryId ?? row.dataset.documentId;
if (!id) return;
const entry = game.journal?.get?.(id);
if (!shouldShowSlaPlayerJournal(entry)) row.remove();
});
pruneEmptySlaFolders(root);
}
function enforceSlaPlayerDirectoryRestrictions(kind, html) {
const root = getSlaHtmlRoot(html);
if (!root || game.user?.isGM) return;
removeSlaElements(root, [
".directory-header .header-actions",
".directory-header .action-buttons",
".sla-sidebar-actions",
".qbm-scene-directory-actions",
".qbm-launch",
".sla-world-tools-launch",
".sla-create-operative",
".sla-create-random-operative",
".sla-refresh-content",
".sla-gear-cache-open",
".sla-briefing-desk-open",
".sla-bpn-dispatch-open",
".sla-npc-director-open",
".sla-npc-director-actor-open",
".sla-ops-clock-open",
".npp-generate-sidebar-btn"
]);
if (kind === "journal") {
filterSlaJournalDirectory(root);
}
if (kind === "tables" || kind === "compendium") {
root.innerHTML = "";
}
}
function enforceSlaPlayerSidebarTabRestrictions(app, html) {
const tabName = app?.tabName ?? app?.options?.id ?? "";
if (tabName === "actors") return enforceSlaPlayerDirectoryRestrictions("actors", html);
if (tabName === "scenes") return enforceSlaPlayerDirectoryRestrictions("scenes", html);
if (tabName === "journal") return enforceSlaPlayerDirectoryRestrictions("journal", html);
if (tabName === "tables") return enforceSlaPlayerDirectoryRestrictions("tables", html);
if (tabName === "compendium") return enforceSlaPlayerDirectoryRestrictions("compendium", html);
if (tabName === "combat") return enforceSlaPlayerDirectoryRestrictions("combat", html);
}
// ─────────────────────────────────────────────────────────────────────────────
// SLA Industries — UI Icon Injection
// Replaces Foundry's Font Awesome icons in the sidebar and scene controls
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000061
MANIFEST-000075
+3 -3
View File
@@ -1,3 +1,3 @@
2026/04/10-15:21:30.814878 16f8db000 Recovering log #60
2026/04/10-15:21:30.815292 16f8db000 Delete type=0 #60
2026/04/10-15:21:30.815311 16f8db000 Delete type=3 #59
2026/04/14-21:44:03.701190 170dcb000 Recovering log #73
2026/04/14-21:44:03.701635 170dcb000 Delete type=3 #71
2026/04/14-21:44:03.701777 170dcb000 Delete type=0 #73
+7 -3
View File
@@ -1,3 +1,7 @@
2026/04/10-15:15:40.939578 17164b000 Recovering log #58
2026/04/10-15:15:40.939865 17164b000 Delete type=0 #58
2026/04/10-15:15:40.939883 17164b000 Delete type=3 #57
2026/04/14-14:29:42.528096 1725ef000 Recovering log #70
2026/04/14-14:29:42.528525 1725ef000 Delete type=0 #70
2026/04/14-14:29:42.528564 1725ef000 Delete type=3 #69
2026/04/14-21:41:04.230515 172707000 Level-0 table #74: started
2026/04/14-21:41:04.230654 172707000 Level-0 table #74: 0 bytes OK
2026/04/14-21:41:04.230837 172707000 Delete type=0 #72
2026/04/14-21:41:04.231926 172707000 Manual compaction at level-0 from '!items!1r6ruvSdxn2bHHIk' @ 72057594037927935 : 1 .. '!items!zbKI2RCl5h3vowoy' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000061
MANIFEST-000075
+3 -3
View File
@@ -1,3 +1,3 @@
2026/04/10-15:21:30.816908 16e0b7000 Recovering log #60
2026/04/10-15:21:30.817298 16e0b7000 Delete type=0 #60
2026/04/10-15:21:30.817326 16e0b7000 Delete type=3 #59
2026/04/14-21:44:03.703209 171de3000 Recovering log #73
2026/04/14-21:44:03.703506 171de3000 Delete type=3 #71
2026/04/14-21:44:03.703538 171de3000 Delete type=0 #73
+7 -3
View File
@@ -1,3 +1,7 @@
2026/04/10-15:15:40.941390 170e3f000 Recovering log #58
2026/04/10-15:15:40.941631 170e3f000 Delete type=0 #58
2026/04/10-15:15:40.941649 170e3f000 Delete type=3 #57
2026/04/14-14:29:42.530946 171de3000 Recovering log #70
2026/04/14-14:29:42.531303 171de3000 Delete type=0 #70
2026/04/14-14:29:42.531327 171de3000 Delete type=3 #69
2026/04/14-21:41:04.231427 172707000 Level-0 table #74: started
2026/04/14-21:41:04.231573 172707000 Level-0 table #74: 0 bytes OK
2026/04/14-21:41:04.231822 172707000 Delete type=0 #72
2026/04/14-21:41:04.231960 172707000 Manual compaction at level-0 from '!items!0ZAwhewvLBwX3opf' @ 72057594037927935 : 1 .. '!items!z0avrplKe6M0RcXk' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000061
MANIFEST-000075
+3 -3
View File
@@ -1,3 +1,3 @@
2026/04/10-15:21:30.818908 16f0cf000 Recovering log #60
2026/04/10-15:21:30.819298 16f0cf000 Delete type=0 #60
2026/04/10-15:21:30.819316 16f0cf000 Delete type=3 #59
2026/04/14-21:44:03.705381 1725ef000 Recovering log #73
2026/04/14-21:44:03.705808 1725ef000 Delete type=3 #71
2026/04/14-21:44:03.705977 1725ef000 Delete type=0 #73
+7 -3
View File
@@ -1,3 +1,7 @@
2026/04/10-15:15:40.942933 17164b000 Recovering log #58
2026/04/10-15:15:40.943347 17164b000 Delete type=0 #58
2026/04/10-15:15:40.943366 17164b000 Delete type=3 #57
2026/04/14-14:29:42.533049 170dcb000 Recovering log #70
2026/04/14-14:29:42.533339 170dcb000 Delete type=0 #70
2026/04/14-14:29:42.533362 170dcb000 Delete type=3 #69
2026/04/14-21:41:04.229609 172707000 Level-0 table #74: started
2026/04/14-21:41:04.229909 172707000 Level-0 table #74: 0 bytes OK
2026/04/14-21:41:04.230444 172707000 Delete type=0 #72
2026/04/14-21:41:04.231906 172707000 Manual compaction at level-0 from '!items!1HNvL7XEUcMQbLps' @ 72057594037927935 : 1 .. '!items!zRzb3Laiuw4ac6p0' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000061
MANIFEST-000075
+3 -3
View File
@@ -1,3 +1,3 @@
2026/04/10-15:21:30.820489 16f0cf000 Recovering log #60
2026/04/10-15:21:30.820764 16f0cf000 Delete type=0 #60
2026/04/10-15:21:30.820786 16f0cf000 Delete type=3 #59
2026/04/14-21:44:03.707592 170dcb000 Recovering log #73
2026/04/14-21:44:03.708035 170dcb000 Delete type=3 #71
2026/04/14-21:44:03.708118 170dcb000 Delete type=0 #73
+7 -3
View File
@@ -1,3 +1,7 @@
2026/04/10-15:15:40.944425 171e57000 Recovering log #58
2026/04/10-15:15:40.944787 171e57000 Delete type=0 #58
2026/04/10-15:15:40.944804 171e57000 Delete type=3 #57
2026/04/14-14:29:42.534696 1725ef000 Recovering log #70
2026/04/14-14:29:42.534932 1725ef000 Delete type=0 #70
2026/04/14-14:29:42.534950 1725ef000 Delete type=3 #69
2026/04/14-21:41:04.230890 172707000 Level-0 table #74: started
2026/04/14-21:41:04.231026 172707000 Level-0 table #74: 0 bytes OK
2026/04/14-21:41:04.231365 172707000 Delete type=0 #72
2026/04/14-21:41:04.231944 172707000 Manual compaction at level-0 from '!macros!2xRvRAixuocjCm40' @ 72057594037927935 : 1 .. '!macros!ilBclUmPwcd7aNQK' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000061
MANIFEST-000075
+3 -3
View File
@@ -1,3 +1,3 @@
2026/04/10-15:21:30.821825 16e0b7000 Recovering log #60
2026/04/10-15:21:30.822206 16e0b7000 Delete type=0 #60
2026/04/10-15:21:30.822238 16e0b7000 Delete type=3 #59
2026/04/14-21:44:03.709360 171de3000 Recovering log #73
2026/04/14-21:44:03.709675 171de3000 Delete type=3 #71
2026/04/14-21:44:03.709706 171de3000 Delete type=0 #73
+7 -3
View File
@@ -1,3 +1,7 @@
2026/04/10-15:15:40.946179 170e3f000 Recovering log #58
2026/04/10-15:15:40.946914 170e3f000 Delete type=0 #58
2026/04/10-15:15:40.946937 170e3f000 Delete type=3 #57
2026/04/14-14:29:42.536078 171de3000 Recovering log #70
2026/04/14-14:29:42.536336 171de3000 Delete type=0 #70
2026/04/14-14:29:42.536359 171de3000 Delete type=3 #69
2026/04/14-21:41:04.232630 172707000 Level-0 table #74: started
2026/04/14-21:41:04.232740 172707000 Level-0 table #74: 0 bytes OK
2026/04/14-21:41:04.232976 172707000 Delete type=0 #72
2026/04/14-21:41:04.234457 172707000 Manual compaction at level-0 from '!macros!2u4a5gEogtLd3XQz' @ 72057594037927935 : 1 .. '!macros!trU0DtBJvJCPddsQ' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000061
MANIFEST-000075
+3 -3
View File
@@ -1,3 +1,3 @@
2026/04/10-15:21:30.988603 16f8db000 Recovering log #60
2026/04/10-15:21:30.989459 16f8db000 Delete type=0 #60
2026/04/10-15:21:30.989517 16f8db000 Delete type=3 #59
2026/04/14-21:44:03.710794 170dcb000 Recovering log #73
2026/04/14-21:44:03.711256 170dcb000 Delete type=3 #71
2026/04/14-21:44:03.711288 170dcb000 Delete type=0 #73
+7 -3
View File
@@ -1,3 +1,7 @@
2026/04/10-15:15:40.964061 17164b000 Recovering log #58
2026/04/10-15:15:40.964631 17164b000 Delete type=0 #58
2026/04/10-15:15:40.964657 17164b000 Delete type=3 #57
2026/04/14-14:29:42.537344 1715d7000 Recovering log #70
2026/04/14-14:29:42.537597 1715d7000 Delete type=0 #70
2026/04/14-14:29:42.537618 1715d7000 Delete type=3 #69
2026/04/14-21:41:04.232083 172707000 Level-0 table #74: started
2026/04/14-21:41:04.232272 172707000 Level-0 table #74: 0 bytes OK
2026/04/14-21:41:04.232554 172707000 Delete type=0 #72
2026/04/14-21:41:04.234426 172707000 Manual compaction at level-0 from '!macros!0Yw4QWSAeOuUzRqB' @ 72057594037927935 : 1 .. '!macros!z5V92LyXHjfMxnRQ' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000063
MANIFEST-000077
+3 -3
View File
@@ -1,3 +1,3 @@
2026/04/10-15:21:30.992926 16f8db000 Recovering log #62
2026/04/10-15:21:30.993656 16f8db000 Delete type=0 #62
2026/04/10-15:21:30.993730 16f8db000 Delete type=3 #61
2026/04/14-21:44:03.713901 1725ef000 Recovering log #75
2026/04/14-21:44:03.714351 1725ef000 Delete type=0 #75
2026/04/14-21:44:03.714384 1725ef000 Delete type=3 #73
+7 -3
View File
@@ -1,3 +1,7 @@
2026/04/10-15:15:40.966053 17164b000 Recovering log #60
2026/04/10-15:15:40.967356 17164b000 Delete type=0 #60
2026/04/10-15:15:40.967388 17164b000 Delete type=3 #59
2026/04/14-14:29:42.538470 1725ef000 Recovering log #72
2026/04/14-14:29:42.538719 1725ef000 Delete type=3 #71
2026/04/14-14:29:42.538747 1725ef000 Delete type=0 #72
2026/04/14-21:41:04.233774 172707000 Level-0 table #76: started
2026/04/14-21:41:04.233930 172707000 Level-0 table #76: 0 bytes OK
2026/04/14-21:41:04.234304 172707000 Delete type=0 #74
2026/04/14-21:41:04.234513 172707000 Manual compaction at level-0 from '!macros!06NSvDYTtmkeUtLq' @ 72057594037927935 : 1 .. '!macros!zLRsweb35Lw0NMev' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000061
MANIFEST-000075
+3 -3
View File
@@ -1,3 +1,3 @@
2026/04/10-15:21:30.995778 16e8c3000 Recovering log #60
2026/04/10-15:21:30.996525 16e8c3000 Delete type=0 #60
2026/04/10-15:21:30.996566 16e8c3000 Delete type=3 #59
2026/04/14-21:44:03.716230 1715d7000 Recovering log #73
2026/04/14-21:44:03.716588 1715d7000 Delete type=3 #71
2026/04/14-21:44:03.716645 1715d7000 Delete type=0 #73
+7 -3
View File
@@ -1,3 +1,7 @@
2026/04/10-15:15:40.968762 17164b000 Recovering log #58
2026/04/10-15:15:40.968983 17164b000 Delete type=0 #58
2026/04/10-15:15:40.969099 17164b000 Delete type=3 #57
2026/04/14-14:29:42.540072 170dcb000 Recovering log #70
2026/04/14-14:29:42.540350 170dcb000 Delete type=0 #70
2026/04/14-14:29:42.540376 170dcb000 Delete type=3 #69
2026/04/14-21:41:04.233064 172707000 Level-0 table #74: started
2026/04/14-21:41:04.233246 172707000 Level-0 table #74: 0 bytes OK
2026/04/14-21:41:04.233613 172707000 Delete type=0 #72
2026/04/14-21:41:04.234486 172707000 Manual compaction at level-0 from '!tables!1vCm4ElRPotQXgNB' @ 72057594037927935 : 1 .. '!tables.results!kqKpQAXyLTEEyz6Z.xdwwswzfoPuVifvw' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000061
MANIFEST-000075
+3 -3
View File
@@ -1,3 +1,3 @@
2026/04/10-15:21:30.998700 16e8c3000 Recovering log #60
2026/04/10-15:21:30.999246 16e8c3000 Delete type=0 #60
2026/04/10-15:21:30.999287 16e8c3000 Delete type=3 #59
2026/04/14-21:44:03.718522 170dcb000 Recovering log #73
2026/04/14-21:44:03.718938 170dcb000 Delete type=3 #71
2026/04/14-21:44:03.718979 170dcb000 Delete type=0 #73
+7 -3
View File
@@ -1,3 +1,7 @@
2026/04/10-15:15:40.970322 172663000 Recovering log #58
2026/04/10-15:15:40.970657 172663000 Delete type=0 #58
2026/04/10-15:15:40.970680 172663000 Delete type=3 #57
2026/04/14-14:29:42.542090 1715d7000 Recovering log #70
2026/04/14-14:29:42.542385 1715d7000 Delete type=0 #70
2026/04/14-14:29:42.542403 1715d7000 Delete type=3 #69
2026/04/14-21:41:04.234643 172707000 Level-0 table #74: started
2026/04/14-21:41:04.234976 172707000 Level-0 table #74: 0 bytes OK
2026/04/14-21:41:04.235710 172707000 Delete type=0 #72
2026/04/14-21:41:04.240761 172707000 Manual compaction at level-0 from '!tables!31YibfjueXuZdNLb' @ 72057594037927935 : 1 .. '!tables.results!ypcoikqHLhnc9tNs.zJkYrssfrdWBSeRm' @ 0 : 0; will stop at (end)
+113 -243
View File
@@ -12,7 +12,7 @@
from 1 to 12 and will create that number of columns. --}}
<div class="header">
<img class="profile sla-portrait-static" src="{{img}}" data-edit="img" title="{{name}}" height="150"
<img class="profile" src="{{img}}" data-edit="img" title="{{name}}" height="150"
width="150" />
<div class="headergrid">
@@ -32,6 +32,10 @@
<span>L.A.D.</span>
</label>
</div>
<div class="header-field header-field-rest">
<div class="headerinputtext">&nbsp;</div>
<button type="button" class="sla-sheet-action sla-rest-button sla-header-rest-pill" title="Take a short or long rest">Rest</button>
</div>
<div class="header-field header-field-credits">
<div class="headerinputtext">{{ localize 'Mosh.Credits' }}</div>
<div class="headerinputfield sla-header-input charname"><input name="system.credits.value" class="noborder" type="text"
@@ -52,27 +56,22 @@
value="{{system.rank.value}}" placeholder="" /></div>
</div>
<div class="header-field header-field-sla-rank">
<div class="headerinputtext">S.C.L</div>
<div class="header-field header-field-sla-rank header-field-compact">
<div class="headerinputtext">S.C.L.</div>
<div class="headerinputfield sla-header-input charname"><input name="system.attributes.level.value" class="noborder"
type="text" value="{{system.attributes.level.value}}" placeholder="" /></div>
</div>
<div class="header-field header-field-height">
<div class="header-field header-field-height header-field-compact">
<div class="headerinputtext">Height</div>
<div class="headerinputfield sla-header-input charname"><input name="system.sla.height.value" class="noborder" type="text"
value="{{system.sla.height.value}}" placeholder="" /></div>
</div>
<div class="header-field header-field-weight">
<div class="header-field header-field-weight header-field-compact">
<div class="headerinputtext">Weight</div>
<div class="headerinputfield sla-header-input charname"><input name="system.sla.weightText.value" class="noborder" type="text"
value="{{system.sla.weightText.value}}" placeholder="" /></div>
</div>
<div class="header-field header-field-rest">
<div class="headerinputtext">&nbsp;</div>
<button type="button" class="sla-sheet-action sla-rest-button sla-header-rest-pill" title="Take a short or long rest">Rest</button>
</div>
</div>
</div>
@@ -206,16 +205,14 @@
</div>
{{/if}}
<!-- Trauma Response -->
<div class="" style="margin-left: 6px; margin-right: 0px; flex: 0; grid-column-start: 1; grid-column-end: 3;">
<div class="sla-trauma-header-row">
<div class="resource-label sla-stat-heading sla-trauma-heading">{{ localize 'Mosh.TraumaResponse' }}</div>
<div class="sla-session-ability-row">
<label class="sla-session-ability-label" title="Check when the session trauma ability has been used">
<input type="checkbox" class="sla-session-ability-used" {{#if system.sla.sessionAbilityUsed}}checked{{/if}} />
<span>Session ability used</span>
</label>
<button type="button" class="sla-new-session" title="Start a new session — resets session ability">New Session</button>
</div>
<div class="sla-trauma-panel" style="margin-left: 6px; margin-right: 0px; flex: 0; grid-column-start: 1; grid-column-end: 3;">
<div class="resource-label sla-stat-heading sla-trauma-heading">{{ localize 'Mosh.TraumaResponse' }}</div>
<div class="sla-trauma-controls">
<label class="sla-trauma-toggle" title="Mark the trauma response as used this session">
<input type="checkbox" class="sla-session-ability-used" {{checked system.sla.sessionAbilityUsed}} />
<span>Used This Session</span>
</label>
<button type="button" class="sla-inline-mini-button sla-new-session" title="Reset trauma response use for a new session">New Session</button>
</div>
<textarea name="system.other.stressdesc.value" rows="4" class="textarea-input trauma-response-box" type="text"
data-target="system.other.stressdesc.value" placeholder="Trauma response">{{system.other.stressdesc.value}}</textarea>
@@ -390,12 +387,11 @@
<a class="tab-select" data-tab="skills">{{ localize 'Mosh.Skills' }}</a>
<a class="tab-select" data-tab="weapons">{{ localize 'Mosh.Weapons' }}</a>
<a class="tab-select" data-tab="items">{{ localize 'Mosh.Items' }}</a>
<a class="tab-select" data-tab="drugs">Drugs</a>
<a class="tab-select" data-tab="sla">SLA</a>
<a class="tab-select" data-tab="traits">Traits</a>
<a class="tab-select" data-tab="special-rules">Special Rules</a>
{{#if isEbbUser}}<a class="tab-select" data-tab="ebb">Ebb</a>{{/if}}
{{#if isVevaphon}}<a class="tab-select" data-tab="morph">Morph</a>{{/if}}
<a class="tab-select" data-tab="contacts">Contacts</a>
<a class="tab-select" data-tab="description">{{ localize 'Mosh.Bio' }}</a>
<a class="tab-select" data-tab="notes">{{ localize 'Mosh.Notes' }}</a>
</nav>
@@ -403,48 +399,6 @@
{{!-- Sheet Body --}}
<section class="sheet-body">
{{!-- Contacts Tab --}}
<div class="tab items" data-group="primary" data-tab="contacts">
<div class="sla-panel">
<div class="sla-banner">
<div>
<div class="sla-kicker">Operative Network</div>
<div class="sla-title">Contacts & Associates</div>
</div>
<div class="sla-tags">
<span class="sla-tag">{{slaContactCount}} contact{{#unless (compare 'int' slaContactCount '===' 1)}}s{{/unless}}</span>
</div>
</div>
<div class="sla-contacts-toolbar">
<button type="button" class="sla-sheet-action sla-contact-add"><i class="fas fa-plus"></i> Add Contact</button>
</div>
{{#if slaContacts.length}}
<div class="sla-contacts-list">
{{#each slaContacts as |contact|}}
<div class="sla-contact-card" data-contact-id="{{contact.id}}">
<div class="sla-contact-header">
<div class="sla-contact-disposition sla-contact-disposition-{{contact.disposition}}"></div>
<div class="sla-contact-name">{{contact.name}}</div>
<div class="sla-contact-role">{{contact.role}}</div>
<div class="sla-contact-controls">
<button type="button" class="sla-contact-edit" data-contact-id="{{contact.id}}" title="Edit contact"><i class="fas fa-edit"></i></button>
<button type="button" class="sla-contact-delete" data-contact-id="{{contact.id}}" title="Delete contact"><i class="fas fa-trash"></i></button>
</div>
</div>
{{#if contact.notes}}
<div class="sla-contact-notes">{{contact.notes}}</div>
{{/if}}
</div>
{{/each}}
</div>
{{else}}
<div class="sla-note-pill sla-note-pill-wide">No contacts yet. Add allies, fixers, squad members, and other NPCs the operative knows.</div>
{{/if}}
</div>
</div>
{{!-- Biography Tab --}}
<div class="tab biography" data-group="primary" data-tab="description">
{{editor enriched.biography target="system.biography" button=true editable=true}}
@@ -452,14 +406,14 @@
{{!-- Notes Tab --}}
<div class="tab biography" data-group="primary" data-tab="notes">
<div class="sla-card" style="margin-bottom: 12px;">
<div class="sla-card-label">Ammo Spend Ledger</div>
<div class="sla-card-copy">Total spent: {{system.sla.ammoSpendTotal.value}} cR</div>
<textarea readonly rows="6" class="textarea-input">{{system.sla.ammoLedger.value}}</textarea>
<div class="sla-card-label">Operation Log</div>
<div class="sla-card-copy">Recent rolls, outcomes, and field actions for this operative.</div>
<textarea readonly rows="10" class="textarea-input">{{system.sla.activityLog.value}}</textarea>
</div>
<div class="sla-card sla-activity-log-card" style="margin-bottom: 12px;">
<div class="sla-card-label">Operative Activity Log</div>
<div class="sla-card-copy">Rolls, rests, drug use, panic — automatically recorded. Newest first.</div>
<textarea readonly rows="8" class="textarea-input sla-activity-log-textarea">{{system.sla.activityLog.value}}</textarea>
<div class="sla-card" style="margin-bottom: 12px;">
<div class="sla-card-label">Ammo Economy Ledger</div>
<div class="sla-card-copy">Firing spend total: {{system.sla.ammoSpendTotal.value}} cR. Purchases are logged below as separate entries.</div>
<textarea readonly rows="6" class="textarea-input">{{system.sla.ammoLedger.value}}</textarea>
</div>
{{editor enriched.notes target="system.notes" button=true editable=true}}
</div>
@@ -474,7 +428,6 @@
<span class="sla-tag">{{system.sla.bpn.value}}</span>
<span class="sla-tag">SCL {{system.sla.scl.value}}</span>
{{#if isEbbUser}}<span class="sla-tag">Ebb {{system.sla.ebbRating.value}}</span>{{/if}}
{{#if isVevaphon}}<span class="sla-tag sla-instability-tag {{vevaphonInstabilityClass}}">Instability {{system.sla.vevaphonInstability.value}}/12</span>{{/if}}
</div>
</div>
@@ -539,15 +492,7 @@
</div>
<div class="resource">
<label class="resource-label">SCL</label>
<div class="sla-scl-control">
<input type="text" name="system.sla.scl.value" value="{{system.sla.scl.value}}" data-dtype="String" class="sla-scl-input" />
<div class="sla-scl-buttons">
<button type="button" class="sla-scl-adjust" data-delta="1" title="+1 SCL">+1</button>
<button type="button" class="sla-scl-adjust" data-delta="-1" title="-1 SCL">-1</button>
<button type="button" class="sla-scl-adjust" data-delta="0.5" title="+0.5 SCL">+½</button>
<button type="button" class="sla-scl-adjust" data-delta="-0.5" title="-0.5 SCL">-½</button>
</div>
</div>
<input type="text" name="system.sla.scl.value" value="{{system.sla.scl.value}}" data-dtype="String" />
</div>
<div class="resource">
<label class="resource-label">Employer / Squad</label>
@@ -885,112 +830,19 @@
</ol>
</div>
{{/if}}
{{#if isVevaphon}}
<div class="tab items" data-group="primary" data-tab="morph">
<div class="sla-panel">
<div class="sla-banner">
<div>
<div class="sla-kicker">Stormer Vevaphon</div>
<div class="sla-title">Polymorphic Form Control</div>
</div>
<div class="sla-tags">
<span class="sla-tag">Form: {{system.sla.morphForm.value}}</span>
<span class="sla-tag sla-instability-tag {{vevaphonInstabilityClass}}">Instability {{system.sla.vevaphonInstability.value}} / 12</span>
<span class="sla-tag">Sanity {{system.stats.sanity.value}}</span>
</div>
</div>
<div class="grid grid-2col sla-card-grid">
<div class="sla-card">
<div class="sla-card-label">Instability Track</div>
<div class="sla-instability-bar-wrap">
<div class="sla-instability-bar {{vevaphonInstabilityClass}}" style="width: {{vevaphonInstabilityPercent}}%;"></div>
<div class="sla-instability-bar-label {{vevaphonInstabilityClass}}">{{system.sla.vevaphonInstability.value}} / 12</div>
</div>
<div class="sla-card-copy">
{{#if vevaphonInstabilityCritical}}⚠ CRITICAL — Morph Panic risk on every failed Sanity save.{{/if}}
{{#if vevaphonInstabilityElevated}}Instability elevated — minor morph effects trigger each scene.{{/if}}
{{#unless vevaphonInstabilityElevated}}Instability nominal. Morphing is available without additional risk.{{/unless}}
</div>
<div class="sla-action-row" style="gap: 6px; flex-wrap: wrap;">
<button type="button" class="sla-instability-increase">+1 Instability</button>
<button type="button" class="sla-instability-decrease">-1 Instability</button>
</div>
<div class="sla-card-copy" style="margin-top: 8px;">At 12, the Vevaphon is lost — immediate retirement or death.</div>
</div>
<div class="sla-card">
<div class="sla-card-label">Active Morph Form</div>
<div class="sla-card-title">{{system.sla.morphForm.value}}</div>
<div class="sla-card-copy">Each morph shift costs 1 Instability. Changing form mid-scene requires a Sanity save at Instability 6+.</div>
<div class="sla-morph-form-buttons">
<button type="button" class="sla-morph-shift" data-form="Brute Form" {{#if (compare 'str' system.sla.morphForm.value '===' 'Brute Form')}}disabled{{/if}}>
<span class="sla-morph-btn-name">Brute Form</span>
<span class="sla-morph-btn-meta">STR+10, BODY+10 | Knockdown Immune</span>
</button>
<button type="button" class="sla-morph-shift" data-form="Stalker Form" {{#if (compare 'str' system.sla.morphForm.value '===' 'Stalker Form')}}disabled{{/if}}>
<span class="sla-morph-btn-name">Stalker Form</span>
<span class="sla-morph-btn-meta">SPD+10, Stealth+20 | Armour Incompatible</span>
</button>
<button type="button" class="sla-morph-shift" data-form="Raptor Form" {{#if (compare 'str' system.sla.morphForm.value '===' 'Raptor Form')}}disabled{{/if}}>
<span class="sla-morph-btn-name">Raptor Form</span>
<span class="sla-morph-btn-meta">COM+10, SPD+5 | Critical Wound Bonus</span>
</button>
</div>
</div>
</div>
<div class="grid grid-2col sla-card-grid">
<div class="sla-card">
<div class="sla-card-label">Morph Panic Reference</div>
<div class="sla-card-copy">Triggered at Instability 10+ on a failed Sanity save. Roll 1d6:</div>
<div class="sla-note-list">
<div class="sla-note-pill sla-note-pill-wide">1. Cascade Shift — random morph form, +1 Instability.</div>
<div class="sla-note-pill sla-note-pill-wide">2. Identity Bleed — forgets name and callsign until end of scene.</div>
<div class="sla-note-pill sla-note-pill-wide">3. Partial Lock — one limb freezes. -10 physical rolls using that limb.</div>
<div class="sla-note-pill sla-note-pill-wide">4. Tissue Rejection — 1d5 damage, armour does not apply.</div>
<div class="sla-note-pill sla-note-pill-wide">5. Threat Imprint — locked on nearest visible target.</div>
<div class="sla-note-pill sla-note-pill-wide">6. Full Reversion — base form, lose morph bonuses, -1 Instability.</div>
</div>
</div>
<div class="sla-card">
<div class="sla-card-label">Morph Notes</div>
<textarea name="system.sla.morphNotes.value" rows="10" class="textarea-input" data-target="system.sla.morphNotes.value">{{system.sla.morphNotes.value}}</textarea>
</div>
</div>
</div>
</div>
{{/if}}
{{!-- Owned Items Tab --}}
<div class="tab items" data-group="primary" data-tab="items">
{{#if drugAlerts.length}}
<div class="sla-drug-alerts">
{{#each drugAlerts as |alert id|}}
<div class="sla-drug-alert {{alert.state}}">
<div class="sla-drug-alert-main">
<div class="sla-drug-alert-name">{{alert.name}}</div>
<div class="sla-drug-alert-meta">
<span class="sla-drug-alert-state">{{alert.stateLabel}}</span>
{{#if alert.duration}}<span class="sla-drug-alert-duration">{{alert.duration}}</span>{{/if}}
</div>
</div>
<div class="sla-drug-alert-summary">{{alert.summary}}</div>
<button type="button" class="drug-close-btn drug-close" data-item-id="{{alert.itemId}}" data-drug-id="{{alert.drugId}}">Close</button>
</div>
{{/each}}
</div>
{{/if}}
{{#if system.settings.firstEdition}}
<!-- ARMOR -->
<ol class="items-list" role="table">
<li class="item flexrow item-header" role="rowgroup">
<div class="item-image" role="cell"></div>
<div class="skill-stat" style="flex-grow: 2.5;" role="cell">{{ localize 'Mosh.ArmorName' }}</div>
<div class="skill-stat" role="cell" title="Armour Protection — reduces incoming damage before it reaches your wounds. Higher is better.">{{ localize 'Mosh.AP' }}</div>
<div class="skill-stat" role="cell" title="Damage Reduction — subtracts directly from wound damage after AP is applied. Each point absorbed is one less point of damage taken.">{{ localize 'Mosh.DR' }}</div>
<div class="skill-stat" role="cell" title="Speed penalty — how much this armour slows movement. Expressed as a % penalty to physical speed checks.">{{ localize 'Mosh.Speed' }}</div>
<div class="skill-stat" role="cell" title="Oxygen supply — current / maximum charges for sealed suits and vacuum armour. N/A means the suit is not airtight.">{{ localize 'Mosh.Oxygen' }}</div>
<div class="skill-stat" role="cell" title="Carry state — Eqp means wearing it now (weight counts against your equipped load). Stow means packed away (counts as stowed weight).">Carry</div>
<div class="skill-stat" role="cell">{{ localize 'Mosh.AP' }}</div>
<div class="skill-stat" role="cell">{{ localize 'Mosh.DR' }}</div>
<div class="skill-stat" role="cell">{{ localize 'Mosh.Speed' }}</div>
<div class="skill-stat" role="cell">{{ localize 'Mosh.Oxygen' }}</div>
<div class="skill-stat" role="cell">Carry</div>
<div class="item-controls" role="cell">
<a class="item-control item-create" title="{{ localize 'Mosh.CreateArmor' }}" data-type="armor"><i class="fas fa-plus"></i> {{ localize 'Mosh.Add' }}</a>
</div>
@@ -1027,10 +879,10 @@
<li class="item flexrow item-header" role="rowgroup">
<div class="item-image" role="cell"></div>
<div class="skill-stat" style="flex-grow: 2.5;" role="cell">{{ localize 'Mosh.ArmorName' }}</div>
<div class="skill-stat" role="cell" title="Save Bonus — this value is added to your Body save when checking to resist physical damage while wearing this armour.">{{ localize 'Mosh.SaveBonus' }}</div>
<div class="skill-stat" role="cell" title="Speed penalty — how much this armour slows movement. Expressed as a % penalty to physical speed checks.">{{ localize 'Mosh.Speed' }}</div>
<div class="skill-stat" role="cell" title="Oxygen supply — current / maximum charges for sealed suits and vacuum armour. N/A means the suit is not airtight.">{{ localize 'Mosh.Oxygen' }}</div>
<div class="skill-stat" role="cell" title="Carry state — Eqp means wearing it now (weight counts against your equipped load). Stow means packed away (counts as stowed weight).">Carry</div>
<div class="skill-stat" role="cell">{{ localize 'Mosh.SaveBonus' }}</div>
<div class="skill-stat" role="cell">{{ localize 'Mosh.Speed' }}</div>
<div class="skill-stat" role="cell">{{ localize 'Mosh.Oxygen' }}</div>
<div class="skill-stat" role="cell">Carry</div>
<div class="item-controls" role="cell">
<a class="item-control item-create" title="{{ localize 'Mosh.CreateArmor' }}" data-type="armor"><i class="fas fa-plus"></i> {{ localize 'Mosh.Add' }}</a>
</div>
@@ -1062,57 +914,15 @@
</ol>
{{/if}}
<!-- DRUGS -->
<ol class="items-list sla-drug-list">
<li class="item flexrow item-header">
<div class="item-image"></div>
<div class="skill-stat" style="flex-grow: 1.6;">Drug</div>
<div class="skill-stat" title="Quantity — number of doses currently carried.">Qty</div>
<div class="skill-stat" title="Rating — the potency tier of this drug. Higher ratings mean stronger effects and harder addiction saves.">Rating</div>
<div class="skill-stat" title="Value — street price per dose in credits (cR).">Value</div>
<div class="skill-stat" title="Weight — total carry weight for all doses of this drug.">Weight</div>
<div class="skill-stat" title="Carry state — Eqp means on your person and immediately accessible. Stow means packed away in your kit.">Carry</div>
<div class="skill-stat" title="State — current drug status. Ready means unused. Active means the drug is currently in effect. Crashed means the effect has ended and the character may be experiencing comedown penalties.">State</div>
<div class="skill-stat" title="Duration — how many rounds or minutes of effect remain. Blank or 0 means the drug is not currently active.">Duration</div>
<div class="item-controls">
<a class="item-control item-create" title="Create Drug" data-type="drug"><i class="fas fa-plus"></i> Add</a>
</div>
</li>
{{#each drugRows as |drug id|}}
<li class="item flexrow dropitem" data-item-id="{{drug.itemId}}" data-drug-id="{{drug.drugId}}">
<div class="item-image"><img src="{{drug.img}}" title="{{drug.name}}" width="24" height="24" /></div>
<div class="skill-name list-roll description-roll drug-item-summary" style="flex-grow: 1.6;">{{drug.name}}<span>{{drug.summary}}</span></div>
<div class="skill-stat item-quantity">{{drug.quantity}}</div>
<div class="skill-stat">{{drug.rating}}</div>
<div class="skill-stat">{{drug.cost}}</div>
<div class="skill-stat">{{drug.slaCarry.totalWeight}} kg</div>
<div class="skill-stat">
<div class="sla-carry-toggle-row">
<button type="button" class="sla-carry-toggle {{#if (compare 'str' drug.slaCarry.state '===' 'equipped')}}active{{/if}}" data-state="equipped">Eqp</button>
<button type="button" class="sla-carry-toggle {{#if (compare 'str' drug.slaCarry.state '===' 'stowed')}}active{{/if}}" data-state="stowed">Stow</button>
</div>
</div>
<div class="skill-stat">{{drug.statusLabel}}</div>
<div class="skill-stat">{{drug.duration}}</div>
<div class="item-controls">
<button type="button" class="drug-use-btn drug-use" {{#unless drug.canUse}}disabled{{/unless}}>Use</button>
<button type="button" class="drug-close-btn drug-close" {{#unless drug.canClose}}disabled{{/unless}}>Close</button>
<a class="item-control item-edit" title="Edit Drug"><i class="fas fa-edit"></i></a>
<a class="item-control item-delete" title="Delete Drug"><i class="fas fa-trash"></i></a>
</div>
</li>
{{/each}}
</ol>
<!-- ITEMS -->
<ol class="items-list">
<li class="item flexrow item-header">
<div class="item-image"></div>
<div class="skill-stat" style="flex-grow: 1.5;" title="Hover over any item name to see its description.">{{ localize 'Mosh.ItemName' }}</div>
<div class="skill-stat" title="Quantity — how many of this item you are carrying.">{{ localize 'Mosh.Quantity' }}</div>
<div class="skill-stat" title="Weight — total carry weight for all units of this item (kg).">{{ localize 'Mosh.Weight' }}</div>
<div class="skill-stat" title="Carry state — Eqp means on your person and ready to use. Stow means packed away. Equipped items count against your active carry limit; stowed items count against your total carry limit.">Carry</div>
<div class="skill-stat" title="Value — purchase price in credits (cR). Sell price is typically half.">{{ localize 'Mosh.Value' }}</div>
<div class="skill-stat" style="flex-grow: 1.5;">{{ localize 'Mosh.ItemName' }}</div>
<div class="skill-stat">{{ localize 'Mosh.Quantity' }}</div>
<div class="skill-stat">{{ localize 'Mosh.Weight' }}</div>
<div class="skill-stat">Carry</div>
<div class="skill-stat">{{ localize 'Mosh.Value' }}</div>
<div class="item-controls">
<a class="item-control item-create" title="{{ localize 'Mosh.CreateItem' }}" data-type="item"><i class="fas fa-plus"></i> Add</a>
</div>
@@ -1144,6 +954,66 @@
</div>
</div>
<div class="tab items" data-group="primary" data-tab="drugs">
{{#if drugAlerts.length}}
<div class="sla-drug-alerts">
{{#each drugAlerts as |alert id|}}
<div class="sla-drug-alert {{alert.state}}">
<div class="sla-drug-alert-main">
<div class="sla-drug-alert-name">{{alert.name}}</div>
<div class="sla-drug-alert-meta">
<span class="sla-drug-alert-state">{{alert.stateLabel}}</span>
{{#if alert.duration}}<span class="sla-drug-alert-duration">{{alert.duration}}</span>{{/if}}
</div>
</div>
<div class="sla-drug-alert-summary">{{alert.summary}}</div>
<button type="button" class="drug-close-btn drug-close" data-item-id="{{alert.itemId}}" data-drug-id="{{alert.drugId}}">Close</button>
</div>
{{/each}}
</div>
{{/if}}
<ol class="items-list sla-drug-list">
<li class="item flexrow item-header">
<div class="item-image"></div>
<div class="skill-stat" style="flex-grow: 1.6;">Drug</div>
<div class="skill-stat">Qty</div>
<div class="skill-stat">Rating</div>
<div class="skill-stat">Value</div>
<div class="skill-stat">Weight</div>
<div class="skill-stat">Carry</div>
<div class="skill-stat">State</div>
<div class="skill-stat">Duration</div>
<div class="item-controls">
<a class="item-control item-create" title="Create Drug" data-type="drug"><i class="fas fa-plus"></i> Add</a>
</div>
</li>
{{#each drugRows as |drug id|}}
<li class="item flexrow dropitem" data-item-id="{{drug.itemId}}" data-drug-id="{{drug.drugId}}">
<div class="item-image"><img src="{{drug.img}}" title="{{drug.name}}" width="24" height="24" /></div>
<div class="skill-name list-roll description-roll drug-item-summary" style="flex-grow: 1.6;">{{drug.name}}<span>{{drug.summary}}</span></div>
<div class="skill-stat item-quantity">{{drug.quantity}}</div>
<div class="skill-stat">{{drug.rating}}</div>
<div class="skill-stat">{{drug.cost}}</div>
<div class="skill-stat">{{drug.slaCarry.totalWeight}} kg</div>
<div class="skill-stat">
<div class="sla-carry-toggle-row">
<button type="button" class="sla-carry-toggle {{#if (compare 'str' drug.slaCarry.state '===' 'equipped')}}active{{/if}}" data-state="equipped">Eqp</button>
<button type="button" class="sla-carry-toggle {{#if (compare 'str' drug.slaCarry.state '===' 'stowed')}}active{{/if}}" data-state="stowed">Stow</button>
</div>
</div>
<div class="skill-stat">{{drug.statusLabel}}</div>
<div class="skill-stat">{{drug.duration}}</div>
<div class="item-controls">
<button type="button" class="drug-use-btn drug-use" {{#unless drug.canUse}}disabled{{/unless}}>Use</button>
<button type="button" class="drug-close-btn drug-close" {{#unless drug.canClose}}disabled{{/unless}}>Close</button>
<a class="item-control item-edit" title="Edit Drug"><i class="fas fa-edit"></i></a>
<a class="item-control item-delete" title="Delete Drug"><i class="fas fa-trash"></i></a>
</div>
</li>
{{/each}}
</ol>
</div>
{{!-- Owned Skills Tab --}}
<div class="tab items" data-group="primary" data-tab="skills">
<div class="sla-card" style="margin-bottom: 12px;">
@@ -1154,10 +1024,10 @@
<ol class="items-list" style="margin-bottom : 10px;">
<li class="item flexrow item-header">
<div class="item-image"></div>
<div class="skill-stat" style="flex-grow: 1.4;" title="Click a skill name to roll it against the relevant stat. Your bonus is added to a d100 roll — you succeed by rolling under the total.">{{ localize 'Mosh.SkillName' }}</div>
<div class="skill-stat" title="Category — broad skill grouping (Combat, Technical, Social, etc.) used for package sorting and Ebb discipline filtering.">Category</div>
<div class="skill-stat" title="Rank — your proficiency level in this skill. Untrained (no bonus), Basic (+1–9), Trained (+10–14), Expert (+15–19), Master (+20+).">{{ localize 'Mosh.SkillRank' }}</div>
<div class="skill-stat" title="Bonus — total modifier added to d100 rolls with this skill. Breakdown shown in small text below the value. Higher is better — you need to roll under (stat + bonus).">{{ localize 'Mosh.SkillBonus' }}</div>
<div class="skill-stat" style="flex-grow: 1.4;">{{ localize 'Mosh.SkillName' }}</div>
<div class="skill-stat">Category</div>
<div class="skill-stat">{{ localize 'Mosh.SkillRank' }}</div>
<div class="skill-stat">{{ localize 'Mosh.SkillBonus' }}</div>
<div class="item-controls">
<a class="item-control skill-create" title="{{ localize 'Mosh.CreateSkill' }}" data-type="skill"><i class="fas fa-plus"></i> {{ localize 'Mosh.Add' }}</a>
</div>
@@ -1216,7 +1086,7 @@
<li class="item flexrow dropitem" data-item-id="{{condition._id}}">
<div class="item-image"><img src="{{condition.img}}" title="{{condition.name}}" width="24" height="24"/></div>
<div class="skill-name description-roll list-roll">{{condition.name}}</div>
<div class="skill-stat list-roll severity">{{condition.system.severity}}</div>
<div class="skill-stat list-roll severity" data-severity="{{condition.system.severity}}">{{condition.system.severity}}</div>
<div class="skill-stat">
{{#if condition.system.treatment.html}}
<div class="treatment-button list-roll flex" style="margin: 0; position: relative; top: 50%; -ms-transform: translateY(-50%); transform: translateY(-50%);" data-key="condition" data-label="condition" data-max="3" data-min="0">
@@ -1244,7 +1114,7 @@
<div class="tab items" data-group="primary" data-tab="weapons">
<div class="sla-card" style="margin-bottom: 12px;">
<div class="sla-card-label">Weapons Readiness</div>
<div class="sla-card-copy">Ammo stacks: {{slaAmmoInventory.stackCount}} | Total loose rounds: {{slaAmmoInventory.roundCount}} | Running ammo spend: {{system.sla.ammoSpendTotal.value}} cR</div>
<div class="sla-card-copy">Ammo stacks: {{slaAmmoInventory.stackCount}} | Total loose rounds: {{slaAmmoInventory.roundCount}} | Running firing spend: {{system.sla.ammoSpendTotal.value}} cR</div>
<div class="sla-card-copy">Carry: {{slaCarry.current}} / {{slaCarry.capacity}} kg | {{slaCarry.state}}</div>
<div class="sla-action-row" style="margin-top: 8px;">
<button type="button" class="sla-sheet-action weapon-reload-all">Reload All</button>
@@ -1254,13 +1124,13 @@
<ol class="items-list">
<li class="item flexrow item-header">
<div class="item-image"></div>
<div class="skill-stat" style="flex-grow: 2;" title="Click a weapon name to make an attack roll using your Combat stat. Calibre and current ammo state shown below the name.">{{ localize 'Mosh.WeaponName' }}</div>
<div class="skill-stat" title="Damage — rolled on a successful hit. Melee weapons may add your Strength modifier. Anti-Armour (AA) weapons halve target armour before applying damage.">{{ localize 'Mosh.Damage' }}</div>
<div class="skill-stat" title="Ammo — currently loaded type (STD / AP / HE / HEAP) and reserve count. STD is standard. AP is armour-piercing (halves target armour, -1 damage). HE is high explosive (+1d5 damage, explosive wounds). HEAP combines AP and HE effects.">{{ localize 'Mosh.Ammo' }}</div>
<div class="skill-stat" title="Shots — current magazine / maximum magazine capacity. Click the reload icon to reload or swap ammo type.">{{ localize 'Mosh.Shots' }}</div>
<div class="skill-stat" title="Fire Mode — click to cycle between available modes. Single fires one shot. Burst fires several rounds for bonus damage but uses more ammo. Auto lays down suppressing fire.">Mode</div>
<div class="skill-stat" title="Carry state — Eqp means the weapon is drawn and ready. Stow means holstered or packed. Only equipped weapons can be used in combat without an action to draw.">Carry</div>
<div class="skill-stat" title="Range — maximum effective distance. At short range you roll normally. At medium range you take a penalty. At long range the penalty increases significantly. Beyond max range the weapon cannot reach.">{{ localize 'Mosh.Range' }}</div>
<div class="skill-stat" style="flex-grow: 2;">{{ localize 'Mosh.WeaponName' }}</div>
<div class="skill-stat">{{ localize 'Mosh.Damage' }}</div>
<div class="skill-stat">{{ localize 'Mosh.Ammo' }}</div>
<div class="skill-stat">{{ localize 'Mosh.Shots' }}</div>
<div class="skill-stat">Mode</div>
<div class="skill-stat">Carry</div>
<div class="skill-stat">{{ localize 'Mosh.Range' }}</div>
<div class="item-controls">
<a class="item-control weapon-create" title="{{ localize 'Mosh.CreateWeapon' }}" data-type="weapon"><i class="fas fa-plus"></i> {{ localize 'Mosh.Add' }}</a>
</div>
+1 -1
View File
@@ -391,7 +391,7 @@
<li class="item flexrow dropitem" data-item-id="{{condition._id}}">
<div class="item-image"><img src="{{condition.img}}" title="{{condition.name}}" width="24" height="24"/></div>
<div class="skill-name description-roll list-roll">{{condition.name}}</div>
<div class="skill-stat list-roll severity">{{condition.system.severity}}</div>
<div class="skill-stat list-roll severity" data-severity="{{condition.system.severity}}">{{condition.system.severity}}</div>
<div class="skill-stat">
{{#if condition.system.treatment.html}}
<div class="treatment-button list-roll flex" style="margin: 0; position: relative; top: 50%; -ms-transform: translateY(-50%); transform: translateY(-50%);" data-key="condition" data-label="condition" data-max="3" data-min="0">
+59 -50
View File
@@ -1,88 +1,97 @@
<div class="mosh" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
<div class="mosh sla-chat-shell sla-chat-shell-item" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
{{#if item._id}}data-item-id="{{item._id}}" {{/if}} {{#if tokenId}}data-token-id="{{tokenId}}" {{/if}}>
<div class="showinfo"></div>
<div class="showlessinfo"></div>
<div class="rollcontainer">
<div class="flexrow">
{{#if swapNameDesc}}
<div class="rollweaponh1" >{{swapName}}</div>
{{else}}
{{#if (compare 'int' item.name.length '>=' 22)}}
<div class="rollweaponh1" style="font-size: 0.75rem; padding-top: 7px;">{{item.name}}</div>
{{else}}
<div class="rollweaponh1" >{{item.name}}</div>
{{/if}}
{{/if}}
{{#if (compare 'str' item.type '===' 'ability')}}
{{#if (compare 'str' actor.img '===' 'icons/svg/mystery-man.svg')}}
<div style="text-align: right"><img class="roll-image" src="systems/sla-mothership/images/icons/ui/attributes/instinct.png" /></div>
{{else}}
<div style="text-align: right"><img class="roll-image" src="{{actor.img}}" /></div>
{{/if}}
{{else}}
<div style="text-align: right"><img class="roll-image" src="{{item.img}}" /></div>
{{/if}}
</div>
<div class="sla-chat-hero">
<div class="sla-chat-hero-main">
<div class="sla-chat-kicker-row">
<div class="sla-chat-kicker">Item Detail</div>
<div class="sla-chat-meta-row">
{{#if (compare 'str' item.system.sla.impact '===' 'heal')}}
<span class="sla-chat-type-badge sla-chat-type-heal">HLG</span>
{{else if item.system.roll}}
<span class="sla-chat-type-badge sla-chat-type-attack">ATK</span>
{{else if (compare 'str' item.type '===' 'ability')}}
<span class="sla-chat-type-badge sla-chat-type-ebb">ABL</span>
{{else}}
<span class="sla-chat-type-badge sla-chat-type-item">ITM</span>
{{/if}}
</div>
</div>
{{#if swapNameDesc}}
<div class="rollweaponh1 sla-chat-hero-title">{{swapName}}</div>
{{else}}
{{#if (compare 'int' item.name.length '>=' 22)}}
<div class="rollweaponh1 sla-chat-hero-title sla-chat-hero-title-tight">{{item.name}}</div>
{{else}}
<div class="rollweaponh1 sla-chat-hero-title">{{item.name}}</div>
{{/if}}
{{/if}}
</div>
<div class="sla-chat-hero-media">
{{#if (compare 'str' item.type '===' 'ability')}}
{{#if (compare 'str' actor.img '===' 'icons/svg/mystery-man.svg')}}
<img class="roll-image" src="systems/sla-mothership/images/icons/ui/attributes/instinct.png" />
{{else}}
<img class="roll-image" src="{{actor.img}}" />
{{/if}}
{{else}}
<img class="roll-image" src="{{item.img}}" />
{{/if}}
</div>
</div>
{{#if item.system.roll}}
<div class="description">
<div class="description sla-chat-section {{#if (compare 'str' item.system.sla.impact '===' 'heal')}}sla-chat-section-heal{{else}}sla-chat-section-damage{{/if}}">
{{#if (compare 'str' item.system.sla.impact '===' 'heal')}}
<div class="body">You restore <strong>[[{{item.system.roll}}[damage]]]</strong> points of healing.</div>
{{else}}
<div class="body">You inflict <strong>[[{{item.system.roll}}[damage]]]</strong> points of damage.</div>
{{/if}}
</div>
<div style="margin-bottom : 10px;"></div>
<div style="border-bottom: 1px solid #999; height: 0; width: 100%;"></div>
<div style="margin-bottom : 5px;"></div>
{{/if}}
{{#if item.system.severity}}
<div class="description">
<div class="body"><strong>{{ localize 'Mosh.Severity' }}: </strong>{{item.system.severity}}</div>
<div class="description sla-chat-section {{severityMeta.cssClass}}" data-severity="{{item.system.severity}}">
<div class="sla-chat-section-heading">
<span class="sla-chat-severity-pill {{severityMeta.cssClass}}">{{severityMeta.label}}</span>
</div>
<div class="body"><strong>{{ localize 'Mosh.Severity' }}:</strong> {{item.system.severity}}</div>
</div>
<div style="margin-bottom : 5px;"></div>
<div style="border-bottom: 1px solid #999; height: 0; width: 100%;"></div>
<div style="margin-bottom : 5px;"></div>
{{/if}}
{{#if item.system.armorPoints}}
<div class="description">
<div class="body"><strong>{{ localize 'Mosh.ArmorPoints' }}: </strong>{{item.system.armorPoints}}</div>
<div class="description sla-chat-section">
<div class="body"><strong>{{ localize 'Mosh.ArmorPoints' }}:</strong> {{item.system.armorPoints}}</div>
</div>
<div style="margin-bottom : 5px;"></div>
<div style="border-bottom: 1px solid #999; height: 0; width: 100%;"></div>
<div style="margin-bottom : 5px;"></div>
{{/if}}
{{#if item.system.damageReduction}}
<div class="description">
<div class="body"><strong>{{ localize 'Mosh.DMGReduction' }}: </strong>{{item.system.damageReduction}}</div>
<div class="description sla-chat-section">
<div class="body"><strong>{{ localize 'Mosh.DMGReduction' }}:</strong> {{item.system.damageReduction}}</div>
</div>
<div style="margin-bottom : 5px;"></div>
<div style="border-bottom: 1px solid #999; height: 0; width: 100%;"></div>
<div style="margin-bottom : 5px;"></div>
{{/if}}
{{#if swapNameDesc}}
<div class="description">
<div class="description sla-chat-section">
<div class="body">{{{item.name}}}</div>
</div>
<div style="margin-bottom : 16px;"></div>
{{else if (compare 'str' item.system.type '===' 'repair')}}
<div class="description">
<div class="description sla-chat-section">
{{#if item.system.major}}
<div class="body"><strong>{{ localize 'Mosh.MajorRepair' }}</strong></div>
{{else}}
<div class="body"><strong>{{ localize 'Mosh.MinorRepair' }}</strong></div>
{{/if}}
</div>
<div style="margin-bottom : 5px;"></div>
<div style="border-bottom: 1px solid #999; height: 0; width: 100%;"></div>
<div style="margin-bottom : 5px;"></div>
<div class="description">
<div class="description sla-chat-section">
<div class="body">{{{item.name}}}</div>
</div>
<div style="margin-bottom : 16px;"></div>
{{else}}
<div class="description">
<div class="description sla-chat-section">
<div class="body">{{{item.system.description}}}</div>
</div>
<div style="margin-bottom : 16px;"></div>
{{/if}}
</div>
</div>
+19 -15
View File
@@ -1,28 +1,32 @@
<div class="mosh" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
<div class="mosh sla-chat-shell sla-chat-shell-actor-update" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
{{#if item._id}}data-item-id="{{item._id}}" {{/if}} {{#if tokenId}}data-token-id="{{tokenId}}" {{/if}}>
<div class="rollcontainer">
<div class="flexrow" style="margin-bottom: 5px;">
{{#if (compare 'int' msgHeader.length '>=' 22)}}
<div class="rollweaponh1" style="font-size: 0.75rem; padding-top: 7px;">{{{msgHeader}}}</div>
{{else}}
<div class="rollweaponh1" >{{{msgHeader}}}</div>
{{/if}}
<div style="text-align: right"><img class="roll-image" src="{{{msgImgPath}}}" /></div>
<div class="sla-chat-hero">
<div class="sla-chat-hero-main">
<div class="sla-chat-kicker">Actor Update</div>
{{#if (compare 'int' msgHeader.length '>=' 22)}}
<div class="rollweaponh1 sla-chat-hero-title sla-chat-hero-title-tight">{{{msgHeader}}}</div>
{{else}}
<div class="rollweaponh1 sla-chat-hero-title">{{{msgHeader}}}</div>
{{/if}}
</div>
<div class="sla-chat-hero-media">
<img class="roll-image" src="{{{msgImgPath}}}" />
</div>
</div>
<div class="description"" style="margin-bottom: 20px;">
<div class="description sla-chat-section">
<div class="body">
{{{msgFlavor}}}
{{#if modRollString}}
<div class="description" style="margin-bottom: 10px;"></div>
{{{parsedRollResult.rollHtml}}}
{{else}}
<br><br>
<div class="sla-chat-rollframe">{{{parsedRollResult.rollHtml}}}</div>
{{/if}}
{{{msgOutcome}}}
{{#if halfDamage}}
<br><br>@UUID[Compendium.mosh.macros_hotbar_0e.eyGvbBi1BhWSgfUP]{Make a Panic Check}
<div class="sla-chat-section sla-chat-section-danger">
<div class="body">@UUID[Compendium.mosh.macros_hotbar_0e.eyGvbBi1BhWSgfUP]{Make a Panic Check}</div>
</div>
{{/if}}
</div>
</div>
</div>
</div>
</div>
+20 -10
View File
@@ -1,16 +1,26 @@
<div class="mosh" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
<div class="mosh sla-chat-shell sla-chat-shell-item-update" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
{{#if item._id}}data-item-id="{{item._id}}" {{/if}} {{#if tokenId}}data-token-id="{{tokenId}}" {{/if}}>
<div class="rollcontainer">
<div class="flexrow" style="margin-bottom : 5px;">
{{#if (compare 'int' msgHeader.length '>=' 22)}}
<div class="rollweaponh1" style="font-size: 0.75rem; padding-top: 7px;">{{{msgHeader}}}</div>
{{else}}
<div class="rollweaponh1" >{{{msgHeader}}}</div>
{{/if}}
<div style="text-align: right"><img class="roll-image" src="{{{msgImgPath}}}" /></div>
<div class="sla-chat-hero">
<div class="sla-chat-hero-main">
<div class="sla-chat-kicker-row">
<div class="sla-chat-kicker">Item Update</div>
<div class="sla-chat-meta-row">
<span class="sla-chat-type-badge sla-chat-type-item">UPD</span>
</div>
</div>
{{#if (compare 'int' msgHeader.length '>=' 22)}}
<div class="rollweaponh1 sla-chat-hero-title sla-chat-hero-title-tight">{{{msgHeader}}}</div>
{{else}}
<div class="rollweaponh1 sla-chat-hero-title">{{{msgHeader}}}</div>
{{/if}}
</div>
<div class="sla-chat-hero-media">
<img class="roll-image" src="{{{msgImgPath}}}" />
</div>
</div>
<div class="description" style="margin-bottom: 10px;">
<div class="description sla-chat-section sla-chat-section-target">
<div class="body">{{{flavorText}}}</div>
</div>
</div>
</div>
</div>
+20 -11
View File
@@ -1,17 +1,26 @@
<div class="mosh" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
<div class="mosh sla-chat-shell sla-chat-shell-reload" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
{{#if item._id}}data-item-id="{{item._id}}" {{/if}} {{#if tokenId}}data-token-id="{{tokenId}}" {{/if}}>
<div class="rollcontainer">
<div class="flexrow" style="margin-bottom : 5px;">
{{#if (compare 'int' item.name.length '>=' 22)}}
<div class="rollweaponh1" style="font-size: 0.75rem; padding-top: 7px;">{{{item.name}}}</div>
{{else}}
<div class="rollweaponh1" >{{{item.name}}}</div>
{{/if}}
<div style="text-align: right"><img class="roll-image" src="systems/sla-mothership/images/icons/ui/macros/reload.png" /></div>
<div class="sla-chat-hero">
<div class="sla-chat-hero-main">
<div class="sla-chat-kicker-row">
<div class="sla-chat-kicker">Reload Action</div>
<div class="sla-chat-meta-row">
<span class="sla-chat-type-badge sla-chat-type-reload">RLD</span>
</div>
</div>
{{#if (compare 'int' item.name.length '>=' 22)}}
<div class="rollweaponh1 sla-chat-hero-title sla-chat-hero-title-tight">{{{item.name}}}</div>
{{else}}
<div class="rollweaponh1 sla-chat-hero-title">{{{item.name}}}</div>
{{/if}}
</div>
<div class="sla-chat-hero-media">
<img class="roll-image" src="systems/sla-mothership/images/icons/ui/macros/reload.png" />
</div>
</div>
<div class="description">
<div class="description sla-chat-section sla-chat-section-target">
<div class="body">{{{msgBody}}}</div>
</div>
<div style="margin-bottom : 16px;"></div>
</div>
</div>
</div>
+69 -75
View File
@@ -1,38 +1,58 @@
<div class="mosh{{#if isSlaEbbSkill}} sla-ebb-chat{{/if}}" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
<div class="mosh sla-chat-shell sla-chat-shell-check{{#if isSlaEbbSkill}} sla-chat-shell-ebb{{/if}} sla-chat-variant-{{chatStyle.key}}{{#if parsedRollResult.success}} sla-chat-shell-success{{else}} sla-chat-shell-failure{{/if}}{{#if parsedRollResult.critical}} sla-chat-shell-critical{{/if}}" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
{{#if weapon._id}}data-item-id="{{weapon._id}}" {{/if}} {{#if tokenId}}data-token-id="{{tokenId}}" {{/if}}>
<div class="rollcontainer">
<div class="flexrow" style="margin-bottom : 5px;">
{{#if (compare 'int' msgHeader.length '>=' 22)}}
<div class="rollweaponh1" style="font-size: 0.75rem; padding-top: 7px;">{{{msgHeader}}}</div>
{{else}}
<div class="rollweaponh1" >{{{msgHeader}}}</div>
{{/if}}
<div style="text-align: right"><img class="roll-image" src="{{{msgImgPath}}}" /></div>
<div class="sla-chat-hero">
<div class="sla-chat-hero-main">
<div class="sla-chat-kicker-row">
<div class="sla-chat-kicker">{{#if (compare 'str' specialRoll '===' 'damage')}}Attack Roll{{else if isSlaEbbSkill}}Ebb Check{{else}}Normal Roll{{/if}}</div>
<div class="sla-chat-meta-row">
{{#if (compare 'str' specialRoll '===' 'damage')}}
<span class="sla-chat-type-badge sla-chat-type-attack">ATK</span>
{{else if isSlaEbbSkill}}
<span class="sla-chat-type-badge sla-chat-type-ebb">EBB</span>
{{else}}
<span class="sla-chat-type-badge sla-chat-type-normal">CHK</span>
{{/if}}
<div class="sla-chat-deptline"><span class="sla-chat-emblem">{{chatStyle.emblem}}</span><span class="sla-chat-dept">{{chatStyle.label}}</span></div>
</div>
</div>
{{#if (compare 'int' msgHeader.length '>=' 22)}}
<div class="rollweaponh1 sla-chat-hero-title sla-chat-hero-title-tight">{{{msgHeader}}}</div>
{{else}}
<div class="rollweaponh1 sla-chat-hero-title">{{{msgHeader}}}</div>
{{/if}}
</div>
<div class="sla-chat-hero-media">
<img class="roll-image" src="{{{msgImgPath}}}" />
</div>
</div>
{{#if (compare 'str' specialRoll '!==' 'damage')}}
{{{parsedRollResult.outcomeHtml}}}
<div class="description" style="margin-bottom: 10px;">
<div class="body">
{{#if (compare 'int' parsedRollResult.total '>=' 90)}}
<span class="high-failure">{{ localize 'Mosh.YouRolled90OrHigher' }}</span>
{{else}}
{{#if isSlaEbbSkill}}
<div class="sla-chat-summary">
{{#if (compare 'int' parsedRollResult.total '>=' 90)}}
<span class="high-failure">{{ localize 'Mosh.YouRolled90OrHigher' }}</span>
{{else}}
{{#if isSlaEbbSkill}}
{{#if (compare 'int' skillValue '>' 0)}}
You {{{outcomeVerb}}} {{{comparisonText}}} your <strong>{{{attribute}}}</strong> plus the <strong>{{{skill}}}</strong> discipline.
{{else}}
You {{{outcomeVerb}}} {{{comparisonText}}} your <strong>{{{attribute}}}</strong> while channeling <strong>{{{skill}}}</strong>.
{{/if}}
{{else}}
{{else}}
{{#if (compare 'int' skillValue '>' 0)}}
You {{{outcomeVerb}}} {{{comparisonText}}} your <strong>{{{attribute}}}</strong> plus <strong>{{{skill}}}</strong> skill bonus.
{{else}}
You {{{outcomeVerb}}} {{{comparisonText}}} your <strong>{{{attribute}}}</strong>.
{{/if}}
{{/if}}
{{/if}}
</div>
{{/if}}
</div>
{{{parsedRollResult.rollHtml}}}
<div class="sla-chat-rollframe">
{{{parsedRollResult.outcomeHtml}}}
{{{parsedRollResult.rollHtml}}}
</div>
{{#if isSlaEbbSkill}}
<div class="sla-chat-section sla-chat-section-ebb-meta">
<div class="sla-chat-section-title">Ebb Discipline</div>
@@ -51,78 +71,50 @@
</div>
{{/if}}
{{/if}}
<div class="description" style="margin-bottom : 20px;">
{{#if (compare 'str' specialRoll '===' 'damage')}}
<div class="description sla-chat-section">{{{flavorText}}}</div>
<div class="sla-chat-section sla-chat-section-damage">{{{flavorText}}}</div>
{{#if needsDesc}}
<div style="margin-bottom : 10px;"></div>
<div class="grid grid-2col" style="grid-template-columns: 10px 75px auto">
<div style="border-bottom:1px solid #999; height: 50%;"></div>
<div style= "font-size:0.65rem; text-align:center;">DESCRIPTION</div>
<div style="border-bottom:1px solid #999; height: 50%;"></div>
</div>
<div style="margin-bottom : 10px;"></div>
<div class="description">
<div class="sla-chat-divider">Description</div>
<div class="description sla-chat-section">
<div class="body">{{{weapon.system.description}}}</div>
{{#if woundEffect}}
<div class="body">
<br><strong>{{ localize 'Mosh.WoundEffect' }}</strong>
<div style="margin-bottom : 3px;"></div>
{{{woundEffect}}}
<div class="body sla-chat-wound-block">
<strong>{{ localize 'Mosh.WoundEffect' }}</strong>
<div class="sla-chat-wound-link">{{{woundEffect}}}</div>
</div>
{{/if}}
{{/if}}
</div>
<div style="margin-bottom : 16px;"></div>
{{/if}}
{{else}}
{{#if parsedRollResult.success}}
{{#if flavorText}}<div class="sla-chat-section sla-chat-section-success">{{{flavorText}}}</div>{{/if}}
{{#if criticalSuccessOptions.length}}
<div class="sla-chat-section sla-chat-section-success">
<div class="sla-chat-section-title">Critical Success Options</div>
<ol class="sla-chat-list">
{{#each criticalSuccessOptions as |option|}}
<li>{{option}}</li>
{{/each}}
</ol>
</div>
{{/if}}
{{#if weapon}}
<div class="description sla-chat-section">{{{flavorText}}}</div>
{{#if criticalSuccessOptions.length}}
<div class="sla-chat-section sla-chat-section-success">
<div class="sla-chat-section-title">Critical Success Options</div>
<ol class="sla-chat-list">
{{#each criticalSuccessOptions as |option|}}
<li>{{option}}</li>
{{/each}}
</ol>
</div>
{{/if}}
{{#if needsDesc}}
<div style="margin-bottom : 10px;"></div>
<div class="grid grid-2col" style="grid-template-columns: 10px 75px auto">
<div style="border-bottom:1px solid #999; height: 50%;"></div>
<div style= "font-size:0.65rem; text-align:center;">{{ localize 'Mosh.Description' }}</div>
<div style="border-bottom:1px solid #999; height: 50%;"></div>
</div>
<div style="margin-bottom : 10px;"></div>
<div class="description">
<div class="sla-chat-divider">{{ localize 'Mosh.Description' }}</div>
<div class="description sla-chat-section">
<div class="body">{{{weapon.system.description}}}</div>
{{#if woundEffect}}
<div class="body">
<br><strong>{{ localize 'Mosh.WoundEffect' }}</strong>
<div style="margin-bottom : 3px;"></div>
{{{woundEffect}}}
<div class="body sla-chat-wound-block">
<strong>{{ localize 'Mosh.WoundEffect' }}</strong>
<div class="sla-chat-wound-link">{{{woundEffect}}}</div>
</div>
{{/if}}
{{/if}}
</div>
<div style="margin-bottom : 16px;"></div>
{{else}}
<div class="sla-chat-section">{{{flavorText}}}</div>
{{#if criticalSuccessOptions.length}}
<div class="sla-chat-section sla-chat-section-success">
<div class="sla-chat-section-title">Critical Success Options</div>
<ol class="sla-chat-list">
{{#each criticalSuccessOptions as |option|}}
<li>{{option}}</li>
{{/each}}
</ol>
</div>
{{/if}}
{{/if}}
{{else}}
{{#if flavorText}}<div class="sla-chat-section">{{{flavorText}}}</div>{{/if}}
{{#if flavorText}}<div class="sla-chat-section sla-chat-section-danger">{{{flavorText}}}</div>{{/if}}
{{#if criticalFailureTable}}
<div class="sla-chat-section sla-chat-section-danger">
<div class="sla-chat-section-title">{{criticalFailureTable.label}}</div>
@@ -132,15 +124,18 @@
</div>
{{/if}}
{{#if critFail}}
<br><br>@UUID[Compendium.mosh.macros_hotbar_1e.IgWvaabVqzKDzLL4]{Make a Panic Check}
<div class="sla-chat-section sla-chat-section-danger">
<div class="body">@UUID[Compendium.mosh.macros_hotbar_1e.IgWvaabVqzKDzLL4]{Make a Panic Check}</div>
</div>
{{/if}}
{{/if}}
{{/if}}
{{#if targetingResult}}
<div class="sla-chat-section sla-chat-section-target">
<div class="sla-chat-section-title">Target Resolution</div>
<div class="body"><strong>{{targetingResult.targetName}}</strong> took <strong>{{targetingResult.finalDamage}}</strong> damage.</div>
<div class="body">Rolled {{targetingResult.rawDamage}}. Armour reduced {{targetingResult.armourReduced}}{{#if targetingResult.armourPiercing}} after AP/anti-armour effects{{/if}}.</div>
<div class="body">Rolled {{targetingResult.rawDamage}}. Armour reduced {{targetingResult.armourReduced}}{{#if targetingResult.armourPiercing}} after AP or anti-armour effects{{/if}}.</div>
{{#if targetingResult.additionalTargets}}
<div class="body">{{targetingResult.additionalTargets}} additional targeted token(s) were ignored for this automatic application.</div>
{{/if}}
@@ -152,6 +147,5 @@
{{/if}}
</div>
{{/if}}
</div>
</div>
</div>
+73 -22
View File
@@ -1,17 +1,45 @@
<div class="mosh" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
<div class="mosh sla-chat-shell sla-chat-shell-table sla-chat-variant-{{chatStyle.key}}{{#if parsedRollResult.success}} sla-chat-shell-success{{else}} sla-chat-shell-failure{{/if}}" {{#if actor._id}}data-actor-id="{{actor._id}}" {{/if}}
{{#if item._id}}data-item-id="{{item._id}}" {{/if}} {{#if tokenId}}data-token-id="{{tokenId}}" {{/if}}>
<div class="rollcontainer">
<div class="flexrow" style="margin-bottom : 5px;">
<div class="rollweaponh1">{{{tableName}}}</div>
<div style="text-align: right"><img class="roll-image" src="{{{tableImg}}}" /></div>
<div class="sla-chat-hero">
<div class="sla-chat-hero-main">
<div class="sla-chat-kicker-row">
<div class="sla-chat-kicker">Table Roll</div>
<div class="sla-chat-meta-row">
{{#if (compare 'str' specialRoll '===' 'panicCheck')}}
<span class="sla-chat-type-badge sla-chat-type-panic">PNC</span>
{{else if (compare 'str' tableName '===' 'Gunshot Wound')}}
<span class="sla-chat-type-badge sla-chat-type-wound">WND</span>
{{else if (compare 'str' tableName '===' 'Bleeding Wound')}}
<span class="sla-chat-type-badge sla-chat-type-wound">WND</span>
{{else if (compare 'str' tableName '===' 'Fire & Explosives Wound')}}
<span class="sla-chat-type-badge sla-chat-type-wound">WND</span>
{{else if (compare 'str' tableName '===' 'Gore & Massive Wound')}}
<span class="sla-chat-type-badge sla-chat-type-wound">WND</span>
{{else}}
<span class="sla-chat-type-badge sla-chat-type-table">TBL</span>
{{/if}}
<div class="sla-chat-deptline"><span class="sla-chat-emblem">{{chatStyle.emblem}}</span><span class="sla-chat-dept">{{chatStyle.label}}</span></div>
</div>
</div>
<div class="rollweaponh1 sla-chat-hero-title">{{{tableName}}}</div>
</div>
<div class="sla-chat-hero-media">
<img class="roll-image" src="{{{tableImg}}}" />
</div>
</div>
{{{parsedRollResult.outcomeHtml}}}
<div class="description sla-chat-section" style="margin-bottom: 10px;">
<div class="description sla-chat-section">
<div class="body">{{{msgDesc}}}</div>
</div>
{{{parsedRollResult.rollHtml}}}
<div class="sla-chat-rollframe">
{{{parsedRollResult.outcomeHtml}}}
{{{parsedRollResult.rollHtml}}}
</div>
{{#if secondRoll}}
<div class="description sla-chat-section" style="margin-bottom: 10px;">
<div class="description sla-chat-section">
{{#if (compare 'str' specialRoll '===' 'panicCheck')}}
<div class="body">{{ localize 'Mosh.RollingForPanicEffect' }}</div>
{{/if}}
@@ -19,34 +47,57 @@
<div class="body">{{ localize 'Mosh.RollingForMaintenanceIssue' }}</div>
{{/if}}
</div>
{{{parsedRollResult2.rollHtml}}}
<div class="sla-chat-rollframe">
{{{parsedRollResult2.rollHtml}}}
</div>
{{/if}}
{{#if parsedRollResult.success}}
<div class="description sla-chat-section sla-chat-section-success" style="margin-bottom : 20px;">{{{flavorText}}}</div>
<div class="description sla-chat-section sla-chat-section-success">{{{flavorText}}}</div>
{{else}}
{{#if woundText}}
{{{woundText}}}<br><br>
<div class="sla-chat-section sla-chat-section-danger {{severityMeta.cssClass}}">
<div class="sla-chat-section-heading">
<span class="sla-chat-severity-pill {{severityMeta.cssClass}}">{{severityMeta.label}}</span>
</div>
{{{woundText}}}
</div>
{{/if}}
{{#if (compare 'str' tableResultType '===' 'document')}}
<ol class="table-results">
{{#each tableResult}}
<li class="table-result flexrow" data-result-id="{{{this._id}}}">
<img class="result-image" src="{{{this.img}}}">
<div class="result-text"><a class="content-link broken" draggable="true" data-type="Item" data-uuid="undefined"><i class="fas fa-unlink"></i>{{{this.description}}}</a></div>
</li>
{{/each}}
</ol>
<div class="sla-chat-section sla-chat-section-target">
<div class="sla-chat-section-title">Linked Results</div>
<ol class="table-results">
{{#each tableResult}}
<li class="table-result flexrow" data-result-id="{{{this._id}}}">
<img class="result-image" src="{{{this.img}}}">
<div class="result-text"><a class="content-link broken" draggable="true" data-type="Item" data-uuid="undefined"><i class="fas fa-unlink"></i>{{{this.description}}}</a></div>
</li>
{{/each}}
</ol>
</div>
{{/if}}
{{#if (compare 'str' tableResultType '===' 'text')}}
{{#if tableResultEdited}}
<div class="description sla-chat-section sla-chat-section-danger" style="margin-bottom : 20px;">{{{tableResultEdited}}}{{{tableResultFooter}}}</div>
<div class="description sla-chat-section sla-chat-section-danger {{severityMeta.cssClass}}">
<div class="sla-chat-section-heading">
<span class="sla-chat-severity-pill {{severityMeta.cssClass}}">{{severityMeta.label}}</span>
</div>
{{{tableResultEdited}}}{{{tableResultFooter}}}
</div>
{{else}}
<div class="description sla-chat-section sla-chat-section-danger" style="margin-bottom : 20px;">{{{tableResult.[0].description}}}{{{tableResultFooter}}}</div>
<div class="description sla-chat-section sla-chat-section-danger {{severityMeta.cssClass}}">
<div class="sla-chat-section-heading">
<span class="sla-chat-severity-pill {{severityMeta.cssClass}}">{{severityMeta.label}}</span>
</div>
{{{tableResult.[0].description}}}{{{tableResultFooter}}}
</div>
{{/if}}
{{/if}}
{{#if (compare 'str' specialRoll '===' 'maintenanceCheck')}}
{{#if critFail}}
<br><br>@UUID[Compendium.mosh.macros_hotbar_1e.IgWvaabVqzKDzLL4]{Make a Panic Check}
<div class="sla-chat-section sla-chat-section-danger">
<div class="body">@UUID[Compendium.mosh.macros_hotbar_1e.IgWvaabVqzKDzLL4]{Make a Panic Check}</div>
</div>
{{/if}}
{{/if}}
{{/if}}