feat: add interactive combat roll modifier slider snappable UI and visual ticks

This commit is contained in:
2026-05-24 21:01:06 +01:00
parent f66ac84c0b
commit d81034f6f4
49 changed files with 258 additions and 128 deletions
+20
View File
@@ -2338,6 +2338,26 @@ p {
box-shadow: none !important;
}
/* Skill picker scroll list — thin scrollbar, no overflow on the dialog */
.macro-popup-dialog .sla-skill-scroll-list {
scrollbar-width: thin;
scrollbar-color: #aaa #e6e6e6;
}
.macro-popup-dialog .sla-skill-scroll-list::-webkit-scrollbar {
width: 6px;
}
.macro-popup-dialog .sla-skill-scroll-list::-webkit-scrollbar-track {
background: #e6e6e6;
border-radius: 3px;
}
.macro-popup-dialog .sla-skill-scroll-list::-webkit-scrollbar-thumb {
background: #aaa;
border-radius: 3px;
}
.macro-popup-dialog .sla-skill-scroll-list::-webkit-scrollbar-thumb:hover {
background: #888;
}
.mosh .sla-panel {
margin-bottom: 14px;
padding: 12px;
+2 -2
View File
@@ -826,7 +826,7 @@ export class MothershipActorSheet extends foundry.appv1.sheets.ActorSheet {
var item;
item = foundry.utils.duplicate(this.actor.getEmbeddedDocument("Item", li.dataset.itemId));
this.actor.rollCheck(null, null, null, item.name, item.system.bonus, null);
this.actor.rollCheck(null, null, null, item.name, item.system.bonus, null, null, { promptModifier: true });
});
html.find('.sla-ebb-skill-roll').click(ev => {
@@ -858,7 +858,7 @@ export class MothershipActorSheet extends foundry.appv1.sheets.ActorSheet {
//const item = duplicate(this.actor.getEmbeddedDocument("Item", li.dataset.itemId));
var item;
item = foundry.utils.duplicate(this.actor.getEmbeddedDocument("Item", li.dataset.itemId));
this.actor.rollWeaponAttack(item);
this.actor.rollWeaponAttack(item, { promptModifier: true });
});
// Rollable Damage
+166 -27
View File
@@ -4,6 +4,21 @@ import { SLADrugSystem } from "../sla-drug-system.js";
import { SLA_SPECIES_BALANCE_VERSION, getSlaSpeciesStatAdjustments } from "../sla-species-balance.js";
import { buildSlaTraumaResponseText } from "../sla-trauma.js";
// Global event delegation to handle interactive range slider value updates
// in a way that is fully compatible with Foundry VTT's HTML sanitization.
if (typeof document !== "undefined") {
document.addEventListener("input", (event) => {
const target = event.target;
if (target && target.tagName === "INPUT" && target.name === "rollModifier") {
const value = Number(target.value) || 0;
const label = target.closest(".resource")?.querySelector(".slider-value");
if (label) {
label.innerText = (value >= 0 ? "+" : "") + value;
}
}
});
}
const SLA_AMMO_TYPES = {
STD: { label: "Standard", multiplier: 1, damageBonus: "", woundEffect: "", summary: "Standard ammunition." },
AP: { label: "AP", multiplier: 2, damageBonus: "-1", woundEffect: "Gunshot [-]", summary: "Armour-piercing ammunition. Treat target armour as halved and reduce damage by 1." },
@@ -1770,11 +1785,21 @@ export class MothershipActor extends Actor {
//wrap skill list in a scrollable container so long lists don't push buttons off-screen
const scrollableSkillList = `<div class="sla-skill-scroll-list" style="max-height: 340px; overflow-y: auto; padding-right: 4px; margin-bottom: 4px;">${skillList}</div>`;
//create final dialog data
const showModifier = !foundry.utils.hasProperty(context.attackContext ?? {}, "rollModifier");
const modifierHtml = showModifier ? `
<div class="resource" style="margin-top: 15px; margin-bottom: 10px; border-top: 1px dashed rgba(255,255,255,0.15); padding-top: 10px; padding-left: 10px; padding-right: 10px;">
<label class="resource-label" style="display: flex; justify-content: space-between; font-weight: bold; margin-bottom: 8px;">
<span>Roll Modifier</span>
<span class="slider-value" style="font-weight: bold; color: #cc2828;">0</span>
</label>
<input type="range" name="rollModifier" min="-30" max="30" step="10" value="0" style="width: 100%; cursor: pointer; accent-color: #cc2828;" oninput="this.previousElementSibling.querySelector('.slider-value').innerText = (this.value >= 0 ? '+' : '') + this.value">
</div>
` : "";
const dialogData = {
window: {title: dlgTitle},
classes: ["macro-popup-dialog"],
position: {width: 600},
content: skillHeader + scrollableSkillList + buttonDesc,
content: skillHeader + scrollableSkillList + modifierHtml + buttonDesc,
buttons: []
};
//add adv/normal/dis buttons if we need a rollString
@@ -1788,8 +1813,9 @@ export class MothershipActor extends Actor {
rollString = `1d100 [+]`;
skill = button.form.querySelector("input[name='skill']:checked")?.getAttribute("id");
skillValue = button.form.querySelector("input[name='skill']:checked")?.getAttribute("value");
resolve([rollString, skill, skillValue]);
slaDebug(`User left the chooseSkill dialog with: rollString:${rollString}, skill:${skill}, skillValue:${skillValue}`);
const rollModifier = showModifier ? (Number(button.form.querySelector("input[name='rollModifier']")?.value ?? 0) || 0) : (Number(context.attackContext?.rollModifier ?? 0) || 0);
resolve([rollString, skill, skillValue, rollModifier]);
slaDebug(`User left the chooseSkill dialog with: rollString:${rollString}, skill:${skill}, skillValue:${skillValue}, modifier:${rollModifier}`);
},
icon: `fas fa-angle-double-up`
},
@@ -1800,8 +1826,9 @@ export class MothershipActor extends Actor {
rollString = `1d100`;
skill = button.form.querySelector("input[name='skill']:checked")?.getAttribute("id");
skillValue = button.form.querySelector("input[name='skill']:checked")?.getAttribute("value");
resolve([rollString, skill, skillValue]);
slaDebug(`User left the chooseSkill dialog with: rollString:${rollString}, skill:${skill}, skillValue:${skillValue}`);
const rollModifier = showModifier ? (Number(button.form.querySelector("input[name='rollModifier']")?.value ?? 0) || 0) : (Number(context.attackContext?.rollModifier ?? 0) || 0);
resolve([rollString, skill, skillValue, rollModifier]);
slaDebug(`User left the chooseSkill dialog with: rollString:${rollString}, skill:${skill}, skillValue:${skillValue}, modifier:${rollModifier}`);
},
icon: `fas fa-minus`
},
@@ -1812,8 +1839,9 @@ export class MothershipActor extends Actor {
rollString = `1d100 [-]`;
skill = button.form.querySelector("input[name='skill']:checked")?.getAttribute("id");
skillValue = button.form.querySelector("input[name='skill']:checked")?.getAttribute("value");
resolve([rollString, skill, skillValue]);
slaDebug(`User left the chooseSkill dialog with: rollString:${rollString}, skill:${skill}, skillValue:${skillValue}`);
const rollModifier = showModifier ? (Number(button.form.querySelector("input[name='rollModifier']")?.value ?? 0) || 0) : (Number(context.attackContext?.rollModifier ?? 0) || 0);
resolve([rollString, skill, skillValue, rollModifier]);
slaDebug(`User left the chooseSkill dialog with: rollString:${rollString}, skill:${skill}, skillValue:${skillValue}, modifier:${rollModifier}`);
},
icon: `fas fa-angle-double-down`
}
@@ -1827,8 +1855,9 @@ export class MothershipActor extends Actor {
callback: (event, button, dialog) => {
skill = button.form.querySelector("input[name='skill']:checked")?.getAttribute("id");
skillValue = button.form.querySelector("input[name='skill']:checked")?.getAttribute("value");
resolve([rollString, skill, skillValue]);
slaDebug(`User left the chooseSkill dialog with: rollString:${rollString}, skill:${skill}, skillValue:${skillValue}`);
const rollModifier = showModifier ? (Number(button.form.querySelector("input[name='rollModifier']")?.value ?? 0) || 0) : (Number(context.attackContext?.rollModifier ?? 0) || 0);
resolve([rollString, skill, skillValue, rollModifier]);
slaDebug(`User left the chooseSkill dialog with: rollString:${rollString}, skill:${skill}, skillValue:${skillValue}, modifier:${rollModifier}`);
},
icon: `fas fa-chevron-circle-right`
}
@@ -1849,19 +1878,29 @@ export class MothershipActor extends Actor {
let dieAdv = die + ` [+]`;
let dieDis = die + ` [-]`;
//create final dialog data
const modifierHtml = `
<div class="resource" style="margin-top: 15px; margin-bottom: 10px; border-top: 1px dashed rgba(255,255,255,0.15); padding-top: 10px; padding-left: 10px; padding-right: 10px;">
<label class="resource-label" style="display: flex; justify-content: space-between; font-weight: bold; margin-bottom: 8px;">
<span>Roll Modifier</span>
<span class="slider-value" style="font-weight: bold; color: #cc2828;">0</span>
</label>
<input type="range" name="rollModifier" min="-30" max="30" step="10" value="0" style="width: 100%; cursor: pointer; accent-color: #cc2828;" oninput="this.previousElementSibling.querySelector('.slider-value').innerText = (this.value >= 0 ? '+' : '') + this.value">
</div>
`;
const dialogData = {
window: {title: dlgTitle},
classes: ["macro-popup-dialog"],
position: {width: 600},
content: `<div class="macro_prompt">` + game.i18n.localize("Mosh.SelectYourRollType") + `:</div>`,
content: `<form style="padding: 5px 10px;"><div class="macro_prompt" style="margin-bottom:10px;">` + game.i18n.localize("Mosh.SelectYourRollType") + `:</div>` + modifierHtml + `</form>`,
buttons: [
{
label: game.i18n.localize("Mosh.Advantage"),
action: `action_advantage`,
callback: (event, button, dialog) => {
rollString = dieAdv;
resolve([rollString]);
slaDebug(`User left the chooseAdvantage dialog with: rollString:${rollString}`);
const rollModifier = Number(button.form.querySelector("input[name='rollModifier']")?.value ?? 0) || 0;
resolve([rollString, rollModifier]);
slaDebug(`User left the chooseAdvantage dialog with: rollString:${rollString}, modifier:${rollModifier}`);
},
icon: `fas fa-angle-double-up`
},
@@ -1870,8 +1909,9 @@ export class MothershipActor extends Actor {
action: `action_normal`,
callback: (event, button, dialog) => {
rollString = die;
resolve([rollString]);
slaDebug(`User left the chooseAdvantage dialog with: rollString:${rollString}`);
const rollModifier = Number(button.form.querySelector("input[name='rollModifier']")?.value ?? 0) || 0;
resolve([rollString, rollModifier]);
slaDebug(`User left the chooseAdvantage dialog with: rollString:${rollString}, modifier:${rollModifier}`);
},
icon: `fas fa-minus`
},
@@ -1880,8 +1920,9 @@ export class MothershipActor extends Actor {
action: `action_disadvantage`,
callback: (event, button, dialog) => {
rollString = dieDis;
resolve([rollString]);
slaDebug(`User left the chooseAdvantage dialog with: rollString:${rollString}`);
const rollModifier = Number(button.form.querySelector("input[name='rollModifier']")?.value ?? 0) || 0;
resolve([rollString, rollModifier]);
slaDebug(`User left the chooseAdvantage dialog with: rollString:${rollString}, modifier:${rollModifier}`);
},
icon: `fas fa-angle-double-down`
}
@@ -2474,6 +2515,9 @@ export class MothershipActor extends Actor {
rollString = chosenSkills[0];
skill = chosenSkills[1];
skillValue = chosenSkills[2];
if (chosenSkills[3] !== undefined) {
attackContext.rollModifier = chosenSkills[3];
}
}
//if rollString is STILL blank, redirect player to choose the roll
if (!rollString) {
@@ -2481,6 +2525,9 @@ export class MothershipActor extends Actor {
let chosenRollType = await this.chooseAdvantage(this.system.stats[attribute].rollLabel, '1d100');
//set variables
rollString = chosenRollType[0];
if (chosenRollType[1] !== undefined) {
attackContext.rollModifier = chosenRollType[1];
}
}
//if this is a weapon roll
if (weapon) {
@@ -2635,6 +2682,18 @@ export class MothershipActor extends Actor {
} else {
rollTarget = rollTargetOverride;
}
const modifier = Number(attackContext?.rollModifier ?? 0) || 0;
if (modifier) {
rollTarget += modifier;
if (effectiveRollBreakdown) {
effectiveRollBreakdown.parts.push({
label: "Roll Modifier",
value: modifier
});
effectiveRollBreakdown.total += modifier;
effectiveRollBreakdown.formulaText += ` ${modifier >= 0 ? "+" : ""}${modifier}`;
}
}
rollTarget = this.clampSlaRollTarget(rollTarget, rollString);
if (effectiveRollBreakdown && (String(rollString ?? "").includes("1d100"))) {
effectiveRollBreakdown = {
@@ -4005,10 +4064,58 @@ export class MothershipActor extends Actor {
return { total, parts };
}
async chooseWeaponAttackOptions(weapon) {
async promptRollModifier(title = "Roll Modifier") {
return new Promise((resolve) => {
const dialogData = {
window: { title },
content: `
<form style="padding: 5px 10px;">
<div class="resource" style="margin-bottom: 15px;">
<label class="resource-label" style="display: flex; justify-content: space-between; font-weight: bold; margin-bottom: 8px;">
<span>Modifier Value</span>
<span class="slider-value" style="font-weight: bold; color: #cc2828;">0</span>
</label>
<input type="range" name="rollModifier" min="-30" max="30" step="10" value="0" style="width: 100%; cursor: pointer; accent-color: #cc2828;">
<div class="slider-ticks" style="display: flex; justify-content: space-between; margin-top: 5px; font-size: 0.85em; color: rgba(255,255,255,0.6); padding: 0 8px;">
<span>-30</span>
<span>-20</span>
<span>-10</span>
<span style="color: #cc2828; font-weight: bold;">0</span>
<span>+10</span>
<span>+20</span>
<span>+30</span>
</div>
</div>
</form>
`,
buttons: [
{
label: "Roll",
action: "roll",
icon: "fas fa-dice-d20",
callback: (_event, button) => {
const modifier = Number(button.form.querySelector("input[name='rollModifier']")?.value ?? 0) || 0;
resolve(modifier);
}
},
{
label: "Cancel",
action: "cancel",
icon: "fas fa-times",
callback: () => resolve(null)
}
],
close: () => resolve(null)
};
new foundry.applications.api.DialogV2(dialogData).render({ force: true });
});
}
async chooseWeaponAttackOptions(weapon, attackContext = {}) {
const fireModes = this.getWeaponFireModes(weapon);
const activeFireMode = this.getActiveWeaponFireMode(weapon);
const ammoChoices = this.getWeaponAmmoChoices(weapon);
const showModifier = Boolean(attackContext?.promptModifier);
return new Promise((resolve) => {
const fireOptions = fireModes.map((mode) => `
@@ -4018,20 +4125,40 @@ export class MothershipActor extends Actor {
<option value="${choice.tag}" ${choice.selected ? "selected" : ""}>${choice.label}</option>
`).join("");
const modifierHtml = showModifier ? `
<div class="resource" style="margin-top: 15px; margin-bottom: 10px; border-top: 1px dashed rgba(255,255,255,0.15); padding-top: 10px;">
<label class="resource-label" style="display: flex; justify-content: space-between; font-weight: bold; margin-bottom: 8px;">
<span>Roll Modifier</span>
<span class="slider-value" style="font-weight: bold; color: #cc2828;">0</span>
</label>
<input type="range" name="rollModifier" min="-30" max="30" step="10" value="0" style="width: 100%; cursor: pointer; accent-color: #cc2828;">
<div class="slider-ticks" style="display: flex; justify-content: space-between; margin-top: 5px; font-size: 0.85em; color: rgba(255,255,255,0.6); padding: 0 8px;">
<span>-30</span>
<span>-20</span>
<span>-10</span>
<span style="color: #cc2828; font-weight: bold;">0</span>
<span>+10</span>
<span>+20</span>
<span>+30</span>
</div>
</div>
` : "";
const dialogData = {
window: {
title: `${weapon.name} Attack Setup`
},
content: `
<form>
<form style="padding: 5px 10px;">
<div class="resource" style="margin-bottom: 10px;">
<label class="resource-label">Fire Mode</label>
<select name="fireMode">${fireOptions}</select>
<label class="resource-label" style="font-weight: bold; margin-bottom: 5px; display: block;">Fire Mode</label>
<select name="fireMode" style="width: 100%;">${fireOptions}</select>
</div>
<div class="resource" style="margin-bottom: 10px;">
<label class="resource-label">Ammo Type</label>
<select name="ammoTag">${ammoOptions}</select>
<label class="resource-label" style="font-weight: bold; margin-bottom: 5px; display: block;">Ammo Type</label>
<select name="ammoTag" style="width: 100%;">${ammoOptions}</select>
</div>
${modifierHtml}
</form>
`,
buttons: [
@@ -4043,10 +4170,12 @@ export class MothershipActor extends Actor {
const fireLabel = button.form.querySelector("select[name='fireMode']")?.value ?? activeFireMode.label;
const fireMode = fireModes.find((mode) => mode.label === fireLabel) ?? activeFireMode;
const ammoTag = button.form.querySelector("select[name='ammoTag']")?.value ?? ammoChoices[0]?.tag ?? "STD";
const rollModifier = showModifier ? (Number(button.form.querySelector("input[name='rollModifier']")?.value ?? 0) || 0) : 0;
resolve({
fireMode: fireMode.label,
shotsPerFire: fireMode.shots,
ammoTag
ammoTag,
rollModifier
});
}
},
@@ -4063,10 +4192,20 @@ export class MothershipActor extends Actor {
});
}
async rollWeaponAttack(weapon) {
const attackContext = weapon?.system?.useAmmo ? await this.chooseWeaponAttackOptions(weapon) : {};
if (weapon?.system?.useAmmo && !attackContext) return;
return this.rollCheck(null, 'low', 'combat', null, null, weapon, null, attackContext ?? {});
async rollWeaponAttack(weapon, attackContext = {}) {
let context = { ...attackContext };
if (weapon?.system?.useAmmo) {
const ammoContext = await this.chooseWeaponAttackOptions(weapon, context);
if (!ammoContext) return null;
context = { ...context, ...ammoContext };
} else {
if (context.promptModifier) {
const rollMod = await this.promptRollModifier(`${weapon.name} Attack Roll`);
if (rollMod === null) return null;
context.rollModifier = rollMod;
}
}
return this.rollCheck(null, 'low', 'combat', null, null, weapon, null, context);
}
async cycleWeaponFireMode(itemId, direction = 1) {
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000075
MANIFEST-000085
+3 -3
View File
@@ -1,3 +1,3 @@
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
2026/05/19-23:19:40.611150 173b87000 Recovering log #84
2026/05/19-23:19:40.611608 173b87000 Delete type=3 #83
2026/05/19-23:19:40.611656 173b87000 Delete type=0 #84
+3 -7
View File
@@ -1,7 +1,3 @@
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)
2026/04/16-22:16:27.606022 16eb7b000 Recovering log #82
2026/04/16-22:16:27.612707 16eb7b000 Delete type=3 #81
2026/04/16-22:16:27.612921 16eb7b000 Delete type=0 #82
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000075
MANIFEST-000085
+3 -3
View File
@@ -1,3 +1,3 @@
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
2026/05/19-23:19:40.613309 172363000 Recovering log #84
2026/05/19-23:19:40.613576 172363000 Delete type=3 #83
2026/05/19-23:19:40.613610 172363000 Delete type=0 #84
+3 -7
View File
@@ -1,7 +1,3 @@
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)
2026/04/16-22:16:27.615553 16f387000 Recovering log #82
2026/04/16-22:16:27.616188 16f387000 Delete type=3 #81
2026/04/16-22:16:27.616243 16f387000 Delete type=0 #82
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000075
MANIFEST-000085
+3 -3
View File
@@ -1,3 +1,3 @@
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
2026/05/19-23:19:40.614698 172b6f000 Recovering log #84
2026/05/19-23:19:40.614916 172b6f000 Delete type=3 #83
2026/05/19-23:19:40.614942 172b6f000 Delete type=0 #84
+3 -7
View File
@@ -1,7 +1,3 @@
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)
2026/04/16-22:16:27.618035 16fb93000 Recovering log #82
2026/04/16-22:16:27.618455 16fb93000 Delete type=3 #81
2026/04/16-22:16:27.618488 16fb93000 Delete type=0 #82
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000075
MANIFEST-000085
+3 -3
View File
@@ -1,3 +1,3 @@
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
2026/05/19-23:19:40.615928 17337b000 Recovering log #84
2026/05/19-23:19:40.616184 17337b000 Delete type=3 #83
2026/05/19-23:19:40.616214 17337b000 Delete type=0 #84
+3 -7
View File
@@ -1,7 +1,3 @@
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)
2026/04/16-22:16:27.619986 16e36f000 Recovering log #82
2026/04/16-22:16:27.620339 16e36f000 Delete type=3 #81
2026/04/16-22:16:27.620370 16e36f000 Delete type=0 #82
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000075
MANIFEST-000085
+3 -3
View File
@@ -1,3 +1,3 @@
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
2026/05/19-23:19:40.617145 17337b000 Recovering log #84
2026/05/19-23:19:40.617442 17337b000 Delete type=3 #83
2026/05/19-23:19:40.617486 17337b000 Delete type=0 #84
+3 -7
View File
@@ -1,7 +1,3 @@
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)
2026/04/16-22:16:27.621457 16e36f000 Recovering log #82
2026/04/16-22:16:27.621716 16e36f000 Delete type=3 #81
2026/04/16-22:16:27.621743 16e36f000 Delete type=0 #82
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000075
MANIFEST-000085
+3 -3
View File
@@ -1,3 +1,3 @@
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
2026/05/19-23:19:40.618390 17337b000 Recovering log #84
2026/05/19-23:19:40.618663 17337b000 Delete type=3 #83
2026/05/19-23:19:40.618694 17337b000 Delete type=0 #84
+3 -7
View File
@@ -1,7 +1,3 @@
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)
2026/04/16-22:16:27.622897 16e36f000 Recovering log #82
2026/04/16-22:16:27.623174 16e36f000 Delete type=3 #81
2026/04/16-22:16:27.623240 16e36f000 Delete type=0 #82
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000077
MANIFEST-000087
+3 -3
View File
@@ -1,3 +1,3 @@
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
2026/05/19-23:19:40.619688 17337b000 Recovering log #86
2026/05/19-23:19:40.620003 17337b000 Delete type=3 #85
2026/05/19-23:19:40.620026 17337b000 Delete type=0 #86
+3 -7
View File
@@ -1,7 +1,3 @@
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)
2026/04/16-22:16:27.624570 16e36f000 Recovering log #84
2026/04/16-22:16:27.625146 16e36f000 Delete type=3 #83
2026/04/16-22:16:27.625212 16e36f000 Delete type=0 #84
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000075
MANIFEST-000085
+3 -3
View File
@@ -1,3 +1,3 @@
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
2026/05/19-23:19:40.621197 17337b000 Recovering log #84
2026/05/19-23:19:40.621497 17337b000 Delete type=3 #83
2026/05/19-23:19:40.621534 17337b000 Delete type=0 #84
+3 -7
View File
@@ -1,7 +1,3 @@
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)
2026/04/16-22:16:27.626995 16e36f000 Recovering log #82
2026/04/16-22:16:27.627395 16e36f000 Delete type=3 #81
2026/04/16-22:16:27.627527 16e36f000 Delete type=0 #82
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000075
MANIFEST-000085
+3 -3
View File
@@ -1,3 +1,3 @@
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
2026/05/19-23:19:40.622748 173b87000 Recovering log #84
2026/05/19-23:19:40.622969 173b87000 Delete type=3 #83
2026/05/19-23:19:40.622990 173b87000 Delete type=0 #84
+3 -7
View File
@@ -1,7 +1,3 @@
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)
2026/04/16-22:16:27.629071 16fb93000 Recovering log #82
2026/04/16-22:16:27.629605 16fb93000 Delete type=3 #81
2026/04/16-22:16:27.629821 16fb93000 Delete type=0 #82
+7
View File
@@ -70,6 +70,13 @@
{{/if}}
</div>
{{/if}}
{{#unless isSlaEbbSkill}}
{{#if rollBreakdown}}
<div class="sla-chat-section sla-chat-section-meta" style="margin-top: 5px; border-top: 1px dashed rgba(255,255,255,0.15); padding-top: 5px;">
<div class="body"><strong>Target Number:</strong> {{#each rollBreakdown.parts as |part index|}}{{#if index}} + {{/if}}{{part.label}} {{part.value}}{{/each}} = <strong>{{rollBreakdown.total}}</strong></div>
</div>
{{/if}}
{{/unless}}
{{/if}}
{{#if (compare 'str' specialRoll '===' 'damage')}}