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 @@ -
- {{#each actor.system.statistics as |statistic key| }} -
-
-
- +
+
+ {{#each statKeys as |key| }} + {{#with (lookup @root.actor.system.statistics key) as |statistic| }} +
+
-
- -
+ data-dtype="Number" + min="0" /> + +
+ {{/with}} + {{/each}}
- {{/each}}
diff --git a/templates/actor/partials/weapons-section-partial.html b/templates/actor/partials/weapons-section-partial.html index 6b29cc1..7ae33d8 100644 --- a/templates/actor/partials/weapons-section-partial.html +++ b/templates/actor/partials/weapons-section-partial.html @@ -21,8 +21,8 @@ {{localize 'DG.Gear.Name'}} - {{localize 'DG.Gear.DamageOrLethality'}} - {{localize 'DG.Gear.ArmorPiercing'}} + {{localize 'DG.Gear.DamageOrLethality'}} + {{localize 'DG.Gear.ArmorPiercing'}} - - {{#if item.system.lethality }} - {{#if item.system.isLethal }} - - - + {{#if (hasWeaponDamageAndLethality item)}} + + {{toUpperCase item.system.damage}} + {{#if_eq item.system.skill 'unarmed_combat' }} + {{actor.system.statistics.str.meleeDamageBonusFormula}} + {{/if_eq}} + {{#if_eq item.system.skill 'melee_weapons' }} + {{actor.system.statistics.str.meleeDamageBonusFormula}} + {{/if_eq}} + / {{item.system.lethality}}% + + + {{else}} + {{#if (hasWeaponDamage item.system.damage)}} + + {{toUpperCase item.system.damage}} + {{#if_eq item.system.skill 'unarmed_combat' }} + {{actor.system.statistics.str.meleeDamageBonusFormula}} + {{/if_eq}} + {{#if_eq item.system.skill 'melee_weapons' }} + {{actor.system.statistics.str.meleeDamageBonusFormula}} + {{/if_eq}} + + + {{else}} + {{#if (hasWeaponLethality item.system.lethality)}} + + {{item.system.lethality}}% + + {{else}} - - - + {{/if}} {{/if}} + {{/if}} - {{#unless item.system.isLethal}} - - {{/unless}} - - {{#if item.system.isLethal }} - - {{/if}} - - - - {{item.system.armorPiercing}} + {{item.system.armorPiercing}}
- + diff --git a/templates/dialog/damage-or-lethality-roll.html b/templates/dialog/damage-or-lethality-roll.html new file mode 100644 index 0000000..7014edc --- /dev/null +++ b/templates/dialog/damage-or-lethality-roll.html @@ -0,0 +1,6 @@ +
+

{{localize "DG.DamageOrLethalityRoll.Intro"}}

+

+ {{localize "DG.ModifyDamageRollDialogue.Weapon"}} {{itemName}} +

+
diff --git a/templates/item/parts/weapon-header.html b/templates/item/parts/weapon-header.html index dee3369..27665f5 100644 --- a/templates/item/parts/weapon-header.html +++ b/templates/item/parts/weapon-header.html @@ -63,17 +63,13 @@ {{localize 'DG.ItemWindow.Weapons.SkillModifier'}}: - {{#unless item.system.isLethal}} - {{localize 'DG.ItemWindow.Weapons.Damage'}}: - - {{/unless}} + {{localize 'DG.ItemWindow.Weapons.Damage'}}: + - {{#if item.system.isLethal}} - {{localize 'DG.ItemWindow.Weapons.Lethality'}} %: - - {{/if}} + {{localize 'DG.ItemWindow.Weapons.Lethality'}} %: + {{localize 'DG.ItemWindow.Weapons.Ammo'}}: