diff --git a/lang/en.json b/lang/en.json
index 7007ad5..2b6d02c 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -238,7 +238,7 @@
"DG.Gear.Armor": "ARMOR",
"DG.Gear.OtherGear": "OTHER GEAR",
"DG.Gear.Name": "Name",
- "DG.Gear.DamageOrLethality": "Damage/Lethality",
+ "DG.Gear.DamageOrLethality": "Damage/ Lethality",
"DG.Gear.ArmorPiercing": "Armor Piercing",
"DG.Gear.ArmorRating": "Armor Rating",
"DG.Gear.Equipped": "Equipped",
@@ -329,6 +329,13 @@
"DG.ModifyDamageRollDialogue.Weapon": "Weapon:",
"DG.ModifyDamageRollDialogue.OuterModifierTooltip": "Use this field to modify the outer part of the formula, such as to double the result.",
"DG.ModifyDamageRollDialogue.InnerModifierTooltip": "Use this field to add a flat modifier such as '+1' or another dice term such as '+1d4'.",
+ "DG.DamageOrLethalityRoll.Title": "Roll Damage or Lethality",
+ "DG.DamageOrLethalityRoll.Intro": "This weapon has both damage and lethality. Choose which to roll.",
+ "DG.DamageOrLethalityRoll.RollDamage": "Roll Damage",
+ "DG.DamageOrLethalityRoll.RollLethality": "Roll Lethality",
+ "DG.EditStats.Title": "Edit Statistics",
+ "DG.EditStats.Button": "Edit Statistics",
+ "DG.EditStats.DistinguishingFeature": "Distinguishing Feature",
"DG.Roll.Inhuman": "Inhuman",
"DG.Tooltip.BreakPoint": "The current breaking point, reset when current sanity falls to or below this value. The starting breaking point is SAN - POW.",
"DG.Skills.ritual": "Ritual",
diff --git a/module/applications/edit-stats.js b/module/applications/edit-stats.js
index d4d05b7..cc9e7c1 100644
--- a/module/applications/edit-stats.js
+++ b/module/applications/edit-stats.js
@@ -1,4 +1,5 @@
import DG, { BASE_TEMPLATE_PATH } from "../config/index.js";
+import { applyDialogTheme } from "./dg-dialog.js";
export default class ActorEditStatForm extends foundry.applications.api.HandlebarsApplicationMixin(
foundry.applications.api.ApplicationV2,
@@ -11,9 +12,13 @@ export default class ActorEditStatForm extends foundry.applications.api.Handleba
/** @override */
static DEFAULT_OPTIONS = {
tag: "form",
- classes: [DG.ID, "edit-stat-form"],
- window: { title: "Edit statistics form", resizable: true },
- position: { width: 400, height: 200 },
+ classes: [DG.ID, "edit-stat-form", "dg-dialog-app"],
+ window: {
+ title: "DG.EditStats.Title",
+ resizable: true,
+ contentClasses: ["standard-form"],
+ },
+ position: { width: 600, height: "auto" },
actions: {},
form: {
handler: this.formHandler,
@@ -28,11 +33,18 @@ export default class ActorEditStatForm extends foundry.applications.api.Handleba
},
});
+ /** @override */
+ async _onRender(context, options) {
+ await super._onRender(context, options);
+ applyDialogTheme(this);
+ }
+
/** @override */
async _prepareContext(options) {
return {
...(await super._prepareContext(options)),
actor: this.actor,
+ statKeys: DG.statistics,
sourceStatistics:
this.actor._source?.system?.statistics ?? this.actor.system.statistics,
};
diff --git a/module/roll/roll-dialogs.js b/module/roll/roll-dialogs.js
index 98c36b0..c45c4fa 100644
--- a/module/roll/roll-dialogs.js
+++ b/module/roll/roll-dialogs.js
@@ -7,6 +7,8 @@ const PERCENTILE_ROLL_DIALOG_TEMPLATE =
"systems/deltagreen/templates/dialog/modify-percentile-roll.html";
const DAMAGE_ROLL_DIALOG_TEMPLATE =
"systems/deltagreen/templates/dialog/modify-damage-roll.html";
+const DAMAGE_OR_LETHALITY_ROLL_DIALOG_TEMPLATE =
+ "systems/deltagreen/templates/dialog/damage-or-lethality-roll.html";
const QUICK_MODIFIER_PRESETS = [
{
@@ -215,3 +217,41 @@ export async function showDamageRollModifyDialog({ itemName, formula }) {
],
});
}
+
+/**
+ * @param {object} options
+ * @param {string} options.itemName
+ * @returns {Promise<"damage"|"lethality"|void>}
+ */
+export async function showDamageOrLethalityChoiceDialog({ itemName }) {
+ const content = await renderTemplate(
+ DAMAGE_OR_LETHALITY_ROLL_DIALOG_TEMPLATE,
+ {
+ itemName,
+ },
+ );
+
+ return showDgDialog({
+ modifier: "damage-or-lethality",
+ content,
+ window: {
+ title: game.i18n.localize("DG.DamageOrLethalityRoll.Title"),
+ },
+ close: () => null,
+ buttons: [
+ {
+ default: true,
+ label: game.i18n.localize("DG.DamageOrLethalityRoll.RollDamage"),
+ action: "damage",
+ icon: "fas fa-dice",
+ callback: () => "damage",
+ },
+ {
+ label: game.i18n.localize("DG.DamageOrLethalityRoll.RollLethality"),
+ action: "lethality",
+ icon: "fas fa-skull-crossbones",
+ callback: () => "lethality",
+ },
+ ],
+ });
+}
diff --git a/module/sheets/mixins/roll-sheet-mixin.js b/module/sheets/mixins/roll-sheet-mixin.js
index 0f6edcb..cb91e39 100644
--- a/module/sheets/mixins/roll-sheet-mixin.js
+++ b/module/sheets/mixins/roll-sheet-mixin.js
@@ -1,4 +1,5 @@
import { getDGRollToken } from "../../chat/dg-chat-card.js";
+import { showDamageOrLethalityChoiceDialog } from "../../roll/roll-dialogs.js";
import { createDGRollFromDataset, processDGRoll } from "../../roll/roll.js";
/** @param {typeof foundry.applications.api.ApplicationV2} Base */
@@ -46,7 +47,17 @@ export default function RollSheetMixin(Base) {
}
const item = this.actor.items.get(target.dataset.iid);
- const roll = createDGRollFromDataset(target.dataset, {
+ const rollDataset = { ...target.dataset };
+
+ if (rollDataset.rolltype === "damage-or-lethality") {
+ const choice = await showDamageOrLethalityChoiceDialog({
+ itemName: item?.name ?? "",
+ });
+ if (!choice) return;
+ rollDataset.rolltype = choice;
+ }
+
+ const roll = createDGRollFromDataset(rollDataset, {
actor: this.actor,
item,
element: target,
diff --git a/module/utils/register-helpers.js b/module/utils/register-helpers.js
index 4448418..6a44602 100644
--- a/module/utils/register-helpers.js
+++ b/module/utils/register-helpers.js
@@ -122,4 +122,24 @@ export default function registerHandlebarsHelpers() {
return result;
});
+
+ Handlebars.registerHelper("hasWeaponDamage", (damage) => {
+ const trimmed = String(damage ?? "").trim();
+ return trimmed !== "" && trimmed !== "0";
+ });
+
+ Handlebars.registerHelper("hasWeaponLethality", (lethality) => {
+ const value = Number(lethality);
+ return Number.isFinite(value) && value > 0;
+ });
+
+ Handlebars.registerHelper("hasWeaponDamageAndLethality", (item) => {
+ const damage = item?.system?.damage;
+ const lethality = item?.system?.lethality;
+ const trimmed = String(damage ?? "").trim();
+ const hasDamage = trimmed !== "" && trimmed !== "0";
+ const hasLethality =
+ Number.isFinite(Number(lethality)) && Number(lethality) > 0;
+ return hasDamage && hasLethality;
+ });
}
diff --git a/module/utils/roll-target-tooltip.js b/module/utils/roll-target-tooltip.js
index 4dfd37e..8fb1206 100644
--- a/module/utils/roll-target-tooltip.js
+++ b/module/utils/roll-target-tooltip.js
@@ -177,6 +177,22 @@ export function appendRollTargetTooltipSection(
return `${existingTooltip}
${appendix}`;
}
+/**
+ * @param {string} existingTooltip
+ * @param {string} [feature]
+ * @returns {string}
+ */
+export function appendDistinguishingFeatureSection(existingTooltip, feature) {
+ const trimmed = String(feature ?? "").trim();
+ if (!trimmed) return existingTooltip;
+
+ const label = game.i18n.localize("DG.EditStats.DistinguishingFeature");
+ const escaped = foundry.utils.escapeHTML(trimmed);
+ const section = `
${foundry.utils.escapeHTML(label)}: ${escaped}`;
+ if (!existingTooltip) return section;
+ return `${existingTooltip}${section}`;
+}
+
/**
* Stat and sanity roll-target tooltips for the agent sheet left bar (not skills).
*
@@ -191,8 +207,12 @@ export function prepareAgentStatSanityTooltips(actor) {
for (const [key, stat] of Object.entries(system.statistics ?? {})) {
const base = Number(stat.x5) || 0;
const existing = game.i18n.localize(`DG.Attributes.Tooltip.${key}`);
- stat.tooltip = appendRollTargetTooltipSection(
+ const withFeature = appendDistinguishingFeatureSection(
existing,
+ stat.distinguishing_feature,
+ );
+ stat.tooltip = appendRollTargetTooltipSection(
+ withFeature,
actor,
"system.rollTarget.statistics",
base,
diff --git a/scss/_utilities.scss b/scss/_utilities.scss
index e6528a2..bede06a 100644
--- a/scss/_utilities.scss
+++ b/scss/_utilities.scss
@@ -69,7 +69,9 @@
}
button.rollable.inline-roll {
- display: inline;
+ display: inline-block;
+ width: max-content;
+ max-width: 100%;
margin: 0;
padding: 0;
border: none;
@@ -78,6 +80,8 @@ button.rollable.inline-roll {
color: var(--dg-ink);
line-height: inherit;
vertical-align: baseline;
+ text-align: inherit;
+ box-sizing: border-box;
> i {
color: inherit;
diff --git a/scss/deltagreen/_agent-sheet.scss b/scss/deltagreen/_agent-sheet.scss
index 6f7382f..c4e0ec4 100644
--- a/scss/deltagreen/_agent-sheet.scss
+++ b/scss/deltagreen/_agent-sheet.scss
@@ -1,6 +1,7 @@
.agent-sheet {
--dg-agent-section-gap: 8px;
--dg-agent-list-row-border: 1px solid rgba(33, 33, 33, 0.25);
+ --weapons-col-damage-trail: 60px;
.window-content {
display: grid;
@@ -794,10 +795,42 @@
.tab.combat .items-list .item:not(.item-header) {
display: grid;
grid-template-columns:
- 24px minmax(0, 1fr) minmax(4rem, auto) minmax(3rem, auto)
+ 24px minmax(0, 1fr)
+ minmax(0, calc(4.25rem + var(--weapons-col-damage-trail)))
+ minmax(0, 3.5rem)
86px;
align-items: center;
- column-gap: 5px;
+ column-gap: 10px;
+ }
+
+ .tab.combat .items-list .weapons-col-damage,
+ .tab.combat .items-list .weapons-col-armor-piercing {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-width: 0;
+ text-align: center;
+ white-space: normal;
+ line-height: 1.2;
+ overflow-wrap: break-word;
+ box-sizing: border-box;
+ }
+
+ .tab.combat .items-list .weapons-col-damage {
+ padding: 0 calc(2px + var(--weapons-col-damage-trail)) 0 2px;
+ }
+
+ .tab.combat .items-list .weapons-col-armor-piercing {
+ padding: 0 2px;
+ }
+
+ .tab.combat .items-list .weapons-col-damage[data-action="roll"],
+ .tab.gear .items-list .weapons-col-damage[data-action="roll"] {
+ cursor: pointer;
+ flex-wrap: nowrap;
+ white-space: nowrap;
+ overflow-wrap: normal;
+ gap: 0.15em;
}
.tab.combat .items-list .item:not(.item-header) > .flexrow {
@@ -840,8 +873,35 @@
margin-top: var(--dg-agent-section-gap);
}
- .tab.gear .items-list .centered-item-property {
- flex: 0 0 auto;
+ .tab.gear .items-list .weapons-col-damage {
+ flex: 0 0 calc(4.25rem + var(--weapons-col-damage-trail));
+ max-width: calc(4.25rem + var(--weapons-col-damage-trail));
+ }
+
+ .tab.gear .items-list .weapons-col-armor-piercing {
+ flex: 0 0 3.5rem;
+ max-width: 3.5rem;
+ }
+
+ .tab.gear .items-list .weapons-col-damage,
+ .tab.gear .items-list .weapons-col-armor-piercing {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-width: 0;
+ text-align: center;
+ white-space: normal;
+ line-height: 1.2;
+ overflow-wrap: break-word;
+ box-sizing: border-box;
+ }
+
+ .tab.gear .items-list .weapons-col-damage {
+ padding: 0 calc(2px + var(--weapons-col-damage-trail)) 0 2px;
+ }
+
+ .tab.gear .items-list .weapons-col-armor-piercing {
+ padding: 0 2px;
}
/* Personal tab */
diff --git a/scss/deltagreen/_character-sheet.scss b/scss/deltagreen/_character-sheet.scss
index 0f4f4f4..e764476 100644
--- a/scss/deltagreen/_character-sheet.scss
+++ b/scss/deltagreen/_character-sheet.scss
@@ -205,6 +205,23 @@
}
}
+.deltagreen[id^="dg-settings"] section.window-content {
+ color: var(--color-text-primary);
+
+ .form-group > label {
+ color: var(--color-form-label);
+ }
+
+ .hint {
+ color: var(--color-form-hint);
+ }
+
+ input[type="checkbox"]:checked::before,
+ input[type="radio"]:checked::before {
+ color: var(--color-text-primary);
+ }
+}
+
a.btn-tiny {
height: 26px;
margin-left: 5px;
diff --git a/scss/deltagreen/_stats-edit.scss b/scss/deltagreen/_stats-edit.scss
index 545154a..86f7542 100644
--- a/scss/deltagreen/_stats-edit.scss
+++ b/scss/deltagreen/_stats-edit.scss
@@ -5,8 +5,8 @@
/* Edit icon */
.stats-edit-btn {
position: absolute;
- top: -10px;
- right: 0;
+ top: 50%;
+ right: -12px;
width: 24px;
height: 24px;
@@ -26,12 +26,13 @@
transition:
opacity 0.15s ease,
transform 0.15s ease;
+ transform: translateY(-50%) scale(1);
}
/* Show on hover of whole stats block */
.stats-wrapper:hover .stats-edit-btn {
opacity: 1;
- transform: scale(1.05);
+ transform: translateY(-50%) scale(1.05);
}
/* Hover feedback */
@@ -39,12 +40,6 @@
background: var(--color-border-highlight, #c9a227);
}
-.edit-stat-form {
- label {
- color: var(--color-text-primary);
- }
-}
-
.physical-stats-outer-grid-2col {
display: grid;
grid-template-columns: 1fr 1fr;
diff --git a/scss/dg-dialog/_dialogs.scss b/scss/dg-dialog/_dialogs.scss
index 0847c38..2c4b768 100644
--- a/scss/dg-dialog/_dialogs.scss
+++ b/scss/dg-dialog/_dialogs.scss
@@ -885,3 +885,46 @@
padding: 0;
border: 0;
}
+
+/* Edit statistics */
+.dg-dialog--edit-stats .edit-stats-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 0.75rem 0.5rem;
+}
+
+.dg-dialog--edit-stats .edit-stats-stat-block {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ min-width: 0;
+}
+
+.dg-dialog--edit-stats .edit-stats-value-row {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ align-items: stretch;
+}
+
+.dg-dialog--edit-stats .edit-stats-value-row input {
+ width: 100%;
+ text-align: center;
+}
+
+.dg-dialog--edit-stats .edit-stats-feature-row {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.dg-dialog--edit-stats .edit-stats-feature-row textarea {
+ width: 100%;
+ min-height: 2.5rem;
+ resize: vertical;
+}
+
+/* Damage or lethality roll choice */
+.dg-dialog--damage-or-lethality .damage-or-lethality-weapon {
+ margin: 0.5rem 0 0;
+}
diff --git a/templates/actor/edit-stats.html b/templates/actor/edit-stats.html
index ee4ccef..19757fc 100644
--- a/templates/actor/edit-stats.html
+++ b/templates/actor/edit-stats.html
@@ -1,26 +1,26 @@
-
{{localize "DG.DamageOrLethalityRoll.Intro"}}
++ {{localize "DG.ModifyDamageRollDialogue.Weapon"}} {{itemName}} +
+