diff --git a/module/MODULES.md b/module/MODULES.md new file mode 100644 index 0000000..f9c8e65 --- /dev/null +++ b/module/MODULES.md @@ -0,0 +1,29 @@ +# Delta Green system — `module/` layout + +Entry point: [`deltagreen.js`](deltagreen.js) (registered in `system.json`). + +## Folder responsibilities + +| Path | Purpose | +| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`config/`](config/) | System constants (`DG`, skill list, template paths) | +| [`data/`](data/) | TypeDataModels (`actor/`, `item/`, `derived/`) | +| [`active-effect/`](active-effect/) | Active effect metadata, data model, document class, runtime sync | +| [`profession/`](profession/) | Profession item skill catalog, validation, and character-creation math | +| [`roll/`](roll/) | Dice roll subclasses, roll pipeline, and roll UX. [`roll/roll.js`](roll/roll.js) is the public API. Internal Roll subclasses live in [`roll/classes/`](roll/classes/). | +| [`sheets/`](sheets/) | ApplicationV2 sheets and mixins | +| [`applications/`](applications/) | Standalone dialogs (profession setup, stats, active effect config) | +| [`actor/`](actor/), [`item/`](item/) | Document subclasses | +| [`chat/`](chat/) | Chat cards and inline actions | +| [`integrations/`](integrations/) | Optional module hooks (Dice So Nice) | +| [`utils/`](utils/) | Generic helpers only (Handlebars, migration, skill layout/tooltips) | + +## Import conventions + +- **Roll types and pipeline:** import `DGRoll`, `DGPercentileRoll`, `createDGRollFromDataset`, `processDGRoll`, etc. from [`roll/roll.js`](roll/roll.js) only — not from [`roll/classes/`](roll/classes/). +- **Roll modifier dialogs:** import from [`roll/roll-dialogs.js`](roll/roll-dialogs.js) only when calling dialogs directly (today only used from roll classes). +- **Skill improvement rolls:** import from [`roll/skill-improvement-roll.js`](roll/skill-improvement-roll.js) (used by agent sheet). +- **Profession logic:** import from [`profession/index.js`](profession/index.js). +- **Active effect runtime:** import exhaustion/stimulant sync from [`active-effect/runtime/`](active-effect/runtime/). +- **Derived actor math:** import from [`data/derived/actor-derived.js`](data/derived/actor-derived.js). +- **Agent skill defaults:** single source in [`data/actor/base/agent-skill-defaults.js`](data/actor/base/agent-skill-defaults.js) (used by human actor schema and profession code). diff --git a/module/README.md b/module/README.md deleted file mode 100644 index a976f0c..0000000 --- a/module/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Delta Green system — `module/` layout - -Entry point: [`deltagreen.js`](deltagreen.js) (registered in `system.json`). - -## Folder responsibilities - -| Path | Purpose | -|------|---------| -| [`config/`](config/) | System constants (`DG`, skill list, template paths) | -| [`data/`](data/) | TypeDataModels (`actor/`, `item/`, `derived/`) | -| [`active-effect/`](active-effect/) | Active effect metadata, data model, document class, runtime sync | -| [`profession/`](profession/) | Profession item skill catalog, validation, and character-creation math | -| [`roll/`](roll/) | Dice roll subclasses, roll pipeline, and roll UX. [`roll/roll.js`](roll/roll.js) is the public API. Internal Roll subclasses live in [`roll/classes/`](roll/classes/). | -| [`sheets/`](sheets/) | ApplicationV2 sheets and mixins | -| [`applications/`](applications/) | Standalone dialogs (profession setup, stats, active effect config) | -| [`actor/`](actor/), [`item/`](item/) | Document subclasses | -| [`chat/`](chat/) | Chat cards and inline actions | -| [`integrations/`](integrations/) | Optional module hooks (Dice So Nice) | -| [`utils/`](utils/) | Generic helpers only (Handlebars, migration, skill layout/tooltips) | - -## Import conventions - -- **Roll types and pipeline:** import `DGRoll`, `DGPercentileRoll`, `createDGRollFromDataset`, `processDGRoll`, etc. from [`roll/roll.js`](roll/roll.js) only — not from [`roll/classes/`](roll/classes/). -- **Roll modifier dialogs:** import from [`roll/roll-dialogs.js`](roll/roll-dialogs.js) only when calling dialogs directly (today only used from roll classes). -- **Skill improvement rolls:** import from [`roll/skill-improvement-roll.js`](roll/skill-improvement-roll.js) (used by agent sheet). -- **Profession logic:** import from [`profession/index.js`](profession/index.js). -- **Active effect runtime:** import exhaustion/stimulant sync from [`active-effect/runtime/`](active-effect/runtime/). -- **Derived actor math:** import from [`data/derived/actor-derived.js`](data/derived/actor-derived.js). -- **Agent skill defaults:** single source in [`data/actor/base/agent-skill-defaults.js`](data/actor/base/agent-skill-defaults.js) (used by human actor schema and profession code). diff --git a/module/active-effect/data/dg-active-effect-data.js b/module/active-effect/data/dg-active-effect-data.js index 35722f1..a5d4360 100644 --- a/module/active-effect/data/dg-active-effect-data.js +++ b/module/active-effect/data/dg-active-effect-data.js @@ -41,7 +41,8 @@ export default class DGActiveEffectTypeDataModel extends foundry.data * @returns {true} */ static #validateType(type) { - if (type.length < 3) throw new Error("must be at least three characters long"); + if (type.length < 3) + throw new Error("must be at least three characters long"); if ( !/^custom\.-?\d+$/.test(type) && !type.split(".").every((s) => /^[a-z0-9]+$/i.test(s)) diff --git a/module/active-effect/documents/dg-active-effect.js b/module/active-effect/documents/dg-active-effect.js index d9fc4e6..7bcd27b 100644 --- a/module/active-effect/documents/dg-active-effect.js +++ b/module/active-effect/documents/dg-active-effect.js @@ -6,7 +6,7 @@ function resolveTransferItem(effect) { const item = effect.parent; if (item?.documentName !== "Item") return undefined; - const actor = item.actor; + const { actor } = item; if (actor?.documentName === "Actor") { return actor.items.get(item.id) ?? item; } diff --git a/module/active-effect/effect-fields.js b/module/active-effect/effect-fields.js index e6a45b9..b427843 100644 --- a/module/active-effect/effect-fields.js +++ b/module/active-effect/effect-fields.js @@ -46,7 +46,7 @@ export const SUPPORTED_ITEM_TYPES = new Set([ * @returns {Record }>} */ export function getEffectFieldGroupsForDocument(effect) { - const parent = effect.parent; + const { parent } = effect; if (parent?.documentName === "Actor") { if (parent.type !== "agent") return {}; diff --git a/module/active-effect/runtime/agent-condition-sync.js b/module/active-effect/runtime/agent-condition-sync.js index 62b3f48..b2ee904 100644 --- a/module/active-effect/runtime/agent-condition-sync.js +++ b/module/active-effect/runtime/agent-condition-sync.js @@ -18,7 +18,7 @@ function hasActiveStimulantEffect(actor) { * @param {Actor} actor * @returns {boolean} */ -export function getEffectiveSuppressExhaustion(actor) { +export default function getEffectiveSuppressExhaustion(actor) { const persisted = actor._source?.system?.physical?.suppressExhaustion ?? false; return Boolean(persisted) || hasActiveStimulantEffect(actor); diff --git a/module/active-effect/runtime/derived.js b/module/active-effect/runtime/derived.js index 56ed41f..969c48d 100644 --- a/module/active-effect/runtime/derived.js +++ b/module/active-effect/runtime/derived.js @@ -77,8 +77,8 @@ function attachRollTargetDisplay(entry, modifier) { export function prepareAgentRollTargetDisplay(actor) { if (actor.type !== "agent") return; - const system = actor.system; - const rollTarget = system.rollTarget; + const { system } = actor; + const { rollTarget } = system; if (!rollTarget) return; const allSkillsMod = Number(rollTarget.allSkills) || 0; @@ -108,7 +108,7 @@ export function prepareAgentRollTargetDisplay(actor) { export function prepareAgentActiveEffectDisplay(actor) { if (actor.type !== "agent") return; - const system = actor.system; + const { system } = actor; const source = actor._source?.system; if (!source) return; @@ -128,7 +128,9 @@ export function prepareAgentActiveEffectDisplay(actor) { const healthFormulaMax = calculateHealthMax(persistedStats, persistedStats); const wpFormulaMax = persistedStats.pow?.value ?? 0; const persistedUnnatural = - source.skills?.unnatural?.proficiency ?? system.skills?.unnatural?.proficiency ?? 0; + source.skills?.unnatural?.proficiency ?? + system.skills?.unnatural?.proficiency ?? + 0; const sanityFormulaMax = 99 - persistedUnnatural; const healthBaseMax = healthFormulaMax + (source.health?.maxBonus ?? 0); @@ -159,7 +161,7 @@ export function prepareAgentActiveEffectDisplay(actor) { export function refreshDerivedAfterActiveEffects(actor) { if (actor.type !== "agent") return; - const system = actor.system; + const { system } = actor; const sourceStatistics = actor._source?.system?.statistics; prepareStatisticsX5(system.statistics, sourceStatistics); diff --git a/module/active-effect/runtime/exhaustion-effect.js b/module/active-effect/runtime/exhaustion-effect.js index 30643c3..1780904 100644 --- a/module/active-effect/runtime/exhaustion-effect.js +++ b/module/active-effect/runtime/exhaustion-effect.js @@ -1,6 +1,6 @@ import DG from "../../config/index.js"; import { ROLL_TARGET_FIELD_KEYS } from "../effect-fields.js"; -import { getEffectiveSuppressExhaustion } from "./agent-condition-sync.js"; +import getEffectiveSuppressExhaustion from "./agent-condition-sync.js"; /** @type {WeakMap>} */ const syncLocks = new WeakMap(); @@ -10,7 +10,9 @@ const syncLocks = new WeakMap(); * @returns {ActiveEffect[]} */ function getExhaustionEffects(actor) { - return actor.effects?.filter((effect) => effect.getFlag(DG.ID, "exhaustion")) ?? []; + return ( + actor.effects?.filter((effect) => effect.getFlag(DG.ID, "exhaustion")) ?? [] + ); } /** @@ -77,9 +79,7 @@ async function syncExhaustionEffectInner(actor) { const { exhausted, exhaustedPenalty } = actor.system.physical; const suppressExhaustion = getEffectiveSuppressExhaustion(actor); const effect = getExhaustionEffect(actor); - const penalty = exhausted - ? -1 * Math.abs(Number(exhaustedPenalty) || 0) - : 0; + const penalty = exhausted ? -1 * Math.abs(Number(exhaustedPenalty) || 0) : 0; if (!exhausted) { if (effect) await effect.delete(); @@ -95,7 +95,7 @@ async function syncExhaustionEffectInner(actor) { return; } - const documentClass = getDocumentClass("ActiveEffect"); + const documentClass = foundry.utils.getDocumentClass("ActiveEffect"); await documentClass.create( { name: game.i18n.localize("DG.Physical.ExhaustionEffectName"), diff --git a/module/active-effect/runtime/stimulant-effect.js b/module/active-effect/runtime/stimulant-effect.js index 9206c49..2ba3b12 100644 --- a/module/active-effect/runtime/stimulant-effect.js +++ b/module/active-effect/runtime/stimulant-effect.js @@ -51,7 +51,7 @@ function getStimulantRemainingHours(effect) { return 0; } -export { getEffectiveSuppressExhaustion } from "./agent-condition-sync.js"; +export { default as getEffectiveSuppressExhaustion } from "./agent-condition-sync.js"; /** * @param {number} hours @@ -99,7 +99,7 @@ export async function applyStimulantEffect(actor, newHours) { return hours; } - const documentClass = getDocumentClass("ActiveEffect"); + const documentClass = foundry.utils.getDocumentClass("ActiveEffect"); await documentClass.create(data, { parent: actor }); return hours; } @@ -150,8 +150,9 @@ export async function clearStimulantEffects(actor) { export async function pruneAllAgentsExpiredStimulants() { if (!game.user.isActiveGM) return; for (const actor of game.actors) { - if (actor.type !== "agent") continue; - await pruneExpiredStimulantEffects(actor); - await syncExhaustionEffect(actor); + if (actor.type === "agent") { + await pruneExpiredStimulantEffects(actor); + await syncExhaustionEffect(actor); + } } } diff --git a/module/active-effect/runtime/submit.js b/module/active-effect/runtime/submit.js index 691942d..9161946 100644 --- a/module/active-effect/runtime/submit.js +++ b/module/active-effect/runtime/submit.js @@ -1,23 +1,6 @@ import DG from "../../config/index.js"; import { getStatisticEffectiveValue } from "../../data/derived/actor-derived.js"; -/** - * Strip form values that match post–AE prepared numbers so persisted data stays at base values. - * @param {Actor} actor - * @param {object} data Update payload (mutated in place). - * @returns {object} - */ -export function sanitizeActiveEffectBackedUpdateData(actor, data) { - if (!data?.system) return data; - - const source = actor._source?.system; - if (!source) return data; - - sanitizeStatisticValues(actor, data, source); - - return data; -} - /** * @param {Actor} actor * @param {object} data @@ -29,17 +12,38 @@ function sanitizeStatisticValues(actor, data, source) { data, `system.statistics.${key}.value`, ); - if (submitted === undefined) continue; + if (submitted !== undefined) { + const sourceStat = source.statistics?.[key]; + const preparedStat = actor.system.statistics?.[key]; + if (sourceStat && preparedStat) { + const base = sourceStat.value; + const effective = getStatisticEffectiveValue(preparedStat, sourceStat); - const sourceStat = source.statistics?.[key]; - const preparedStat = actor.system.statistics?.[key]; - if (!sourceStat || !preparedStat) continue; - - const base = sourceStat.value; - const effective = getStatisticEffectiveValue(preparedStat, sourceStat); - - if (submitted === effective && submitted !== base) { - foundry.utils.setProperty(data, `system.statistics.${key}.value`, base); + if (submitted === effective && submitted !== base) { + foundry.utils.setProperty( + data, + `system.statistics.${key}.value`, + base, + ); + } + } } } } + +/** + * Strip form values that match post–AE prepared numbers so persisted data stays at base values. + * @param {Actor} actor + * @param {object} data Update payload (mutated in place). + * @returns {object} + */ +export default function sanitizeActiveEffectBackedUpdateData(actor, data) { + if (!data?.system) return data; + + const source = actor._source?.system; + if (!source) return data; + + sanitizeStatisticValues(actor, data, source); + + return data; +} diff --git a/module/actor/actor.js b/module/actor/actor.js index f434548..9c1c639 100644 --- a/module/actor/actor.js +++ b/module/actor/actor.js @@ -5,7 +5,7 @@ import { updateTouchesExhaustionPhysical, } from "../active-effect/runtime/exhaustion-effect.js"; import { pruneExpiredStimulantEffects } from "../active-effect/runtime/stimulant-effect.js"; -import { sanitizeActiveEffectBackedUpdateData } from "../active-effect/runtime/submit.js"; +import sanitizeActiveEffectBackedUpdateData from "../active-effect/runtime/submit.js"; /** * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. diff --git a/module/applications/add-profession-dialog.js b/module/applications/add-profession-dialog.js index 7a4b8b8..db100ee 100644 --- a/module/applications/add-profession-dialog.js +++ b/module/applications/add-profession-dialog.js @@ -34,7 +34,7 @@ const { renderTemplate } = foundry.applications.handlebars; * @param {Actor} actor * @returns {Promise} */ -export async function showPickSkillsDialog(professionItem, actor) { +export default async function showPickSkillsDialog(professionItem, actor) { // eslint-disable-next-line no-use-before-define -- class defined below in this module const controller = new AddProfessionDialogController(professionItem, actor); return controller.run(); @@ -428,11 +428,12 @@ class AddProfessionDialogController { `[name="optionSkillChecked"][value="${CSS.escape(key)}"]`, ); if (cb) cb.checked = true; - if (!this.optionSkillMeta[key]?.chooseOne) continue; - const nameInput = this.#getOptionChooseOneInput(key); - if (nameInput) { - nameInput.disabled = false; - nameInput.value = this.chooseOneLabels[key] ?? ""; + if (this.optionSkillMeta[key]?.chooseOne) { + const nameInput = this.#getOptionChooseOneInput(key); + if (nameInput) { + nameInput.disabled = false; + nameInput.value = this.chooseOneLabels[key] ?? ""; + } } } @@ -519,9 +520,7 @@ class AddProfessionDialogController { .join(""); return ( - toErrorHtml(validationMessages) + - capHtml + - toErrorHtml(wasteMessages) + toErrorHtml(validationMessages) + capHtml + toErrorHtml(wasteMessages) ); } diff --git a/module/applications/assign-stats-dialog.js b/module/applications/assign-stats-dialog.js index 591bbdb..2f9bd82 100644 --- a/module/applications/assign-stats-dialog.js +++ b/module/applications/assign-stats-dialog.js @@ -24,10 +24,11 @@ function syncValuesFromDom(dialog, values) { for (const key of STAT_KEYS) { const input = root.querySelector(`[data-stat-key="${key}"]`); - if (!input) continue; - let value = Number(input.value); - if (!Number.isFinite(value)) value = STAT_MIN; - values[key] = Math.clamp(Math.trunc(value), STAT_MIN, STAT_MAX); + if (input) { + let value = Number(input.value); + if (!Number.isFinite(value)) value = STAT_MIN; + values[key] = Math.clamp(Math.trunc(value), STAT_MIN, STAT_MAX); + } } } @@ -85,7 +86,7 @@ function bindAssignStatsListeners(dialog, values) { * @param {Actor} actor * @returns {Promise} */ -export async function showAssignStatsDialog(actor) { +export default async function showAssignStatsDialog(actor) { /** @type {Record} */ const values = getDefaultPointBuyValues(); const { remaining } = validatePointBuyValues(values); diff --git a/module/applications/dg-active-effect-config.js b/module/applications/dg-active-effect-config.js index 284b503..641742f 100644 --- a/module/applications/dg-active-effect-config.js +++ b/module/applications/dg-active-effect-config.js @@ -54,10 +54,13 @@ export default class DGActiveEffectConfig extends ActiveEffectConfig { Object.assign( change, - ["key", "type", "value", "phase", "priority"].reduce((paths, fieldName) => { - paths[`${fieldName}Path`] = `system.changes.${index}.${fieldName}`; - return paths; - }, {}), + ["key", "type", "value", "phase", "priority"].reduce( + (paths, fieldName) => { + paths[`${fieldName}Path`] = `system.changes.${index}.${fieldName}`; + return paths; + }, + {}, + ), ); return renderTemplate( diff --git a/module/applications/pick-statistics-dialog.js b/module/applications/pick-statistics-dialog.js index 38d94da..e275e2b 100644 --- a/module/applications/pick-statistics-dialog.js +++ b/module/applications/pick-statistics-dialog.js @@ -8,7 +8,7 @@ const { renderTemplate } = foundry.applications.handlebars; /** * @returns {Promise} */ -export async function showPickStatisticsDialog() { +export default async function showPickStatisticsDialog() { const content = await renderTemplate( `${BASE_TEMPLATE_PATH}/dialog/pick-statistics.html`, {}, @@ -25,7 +25,9 @@ export async function showPickStatisticsDialog() { buttons: [ { action: "roll", - label: game.i18n.localize("DG.ProfessionSetup.PickStatistics.RollStats"), + label: game.i18n.localize( + "DG.ProfessionSetup.PickStatistics.RollStats", + ), tooltip: game.i18n.localize( "DG.ProfessionSetup.PickStatistics.RollStatsTooltip", ), diff --git a/module/applications/profession-setup-flow.js b/module/applications/profession-setup-flow.js index cdb2559..047eec2 100644 --- a/module/applications/profession-setup-flow.js +++ b/module/applications/profession-setup-flow.js @@ -1,7 +1,7 @@ -import { showPickSkillsDialog } from "./add-profession-dialog.js"; -import { showAssignStatsDialog } from "./assign-stats-dialog.js"; -import { showPickStatisticsDialog } from "./pick-statistics-dialog.js"; -import { showRollStatsDialog } from "./roll-stats-dialog.js"; +import showPickSkillsDialog from "./add-profession-dialog.js"; +import showAssignStatsDialog from "./assign-stats-dialog.js"; +import showPickStatisticsDialog from "./pick-statistics-dialog.js"; +import showRollStatsDialog from "./roll-stats-dialog.js"; /** * @param {Actor} actor @@ -58,7 +58,7 @@ async function runProfessionSetupFlow( * @param {TokenDocument|null} [options.token] * @returns {Promise} */ -export async function assignProfessionToAgent( +export default async function assignProfessionToAgent( actor, itemData, { token = null } = {}, diff --git a/module/applications/rename-profession-dialog.js b/module/applications/rename-profession-dialog.js index ca7709d..61a1332 100644 --- a/module/applications/rename-profession-dialog.js +++ b/module/applications/rename-profession-dialog.js @@ -7,7 +7,7 @@ const { renderTemplate } = foundry.applications.handlebars; * @param {Item} professionItem * @returns {Promise} True if the name was updated. */ -export async function showRenameProfessionDialog(professionItem) { +export default async function showRenameProfessionDialog(professionItem) { if (professionItem.type !== "profession") return false; const content = await renderTemplate( diff --git a/module/applications/roll-stats-dialog.js b/module/applications/roll-stats-dialog.js index a3c1820..af4fee8 100644 --- a/module/applications/roll-stats-dialog.js +++ b/module/applications/roll-stats-dialog.js @@ -43,12 +43,14 @@ function syncDomFromAssignments(state, dialog) { for (const key of STAT_KEYS) { const rollIndex = state.assignments[key]; - if (rollIndex === null) continue; - - const slot = root.querySelector(`[data-stat-slot][data-stat-key="${key}"]`); - const token = pool.querySelector(`[data-roll-index="${rollIndex}"]`); - if (slot && token) { - slot.appendChild(token); + if (rollIndex !== null) { + const slot = root.querySelector( + `[data-stat-slot][data-stat-key="${key}"]`, + ); + const token = pool.querySelector(`[data-roll-index="${rollIndex}"]`); + if (slot && token) { + slot.appendChild(token); + } } } } @@ -183,7 +185,10 @@ function bindRollStatsListeners(state, dialog) { * @param {TokenDocument|null} [options.token] * @returns {Promise} */ -export async function showRollStatsDialog(actor, { token = null } = {}) { +export default async function showRollStatsDialog( + actor, + { token = null } = {}, +) { const rolled = await rollStatisticScores({ actor, token }); const rolls = rolled.map((entry, index) => ({ index, diff --git a/module/chat/resource-chat.js b/module/chat/resource-chat.js index 20fe6be..79b54cb 100644 --- a/module/chat/resource-chat.js +++ b/module/chat/resource-chat.js @@ -11,7 +11,7 @@ import { createDGRollChatMessage } from "./dg-chat-card.js"; * @param {string|null} [params.extraContentKey] Optional i18n key appended on a new line after the main body. * @returns {Promise} */ -export async function createAgentResourceChatMessage({ +export default async function createAgentResourceChatMessage({ actor, token = null, contentKey, diff --git a/module/config/rich-text-fields.js b/module/config/rich-text-fields.js index eff831a..37b0bba 100644 --- a/module/config/rich-text-fields.js +++ b/module/config/rich-text-fields.js @@ -41,7 +41,7 @@ const ITEM_RICH_TEXT_BY_PART = { * @param {boolean} [options.showNotesInSkills] * @returns {{ path: string, key: string }[]} */ -export function getRichTextFieldsForPart( +export default function getRichTextFieldsForPart( documentName, partId, { actorType, itemType, showNotesInSkills = true } = {}, diff --git a/module/data/actor/agent.js b/module/data/actor/agent.js index be66f0c..cc0e10f 100644 --- a/module/data/actor/agent.js +++ b/module/data/actor/agent.js @@ -1,16 +1,14 @@ import HumanSkillsActorData from "./base/human-skills.js"; import CharacterData from "./base/character.js"; import DGHTMLField from "../fields/html-content-field.js"; -import { - agentResourceField, -} from "./base/general.js"; +import { agentResourceField } from "./base/general.js"; import { computeEquippedArmorProtection, initializeSanityIfUnset, prepareSanityAdaptations, prepareStatisticsX5, removeLegacyRitualSkill, -} from "../../data/derived/actor-derived.js"; +} from "../derived/actor-derived.js"; const { SchemaField, NumberField, StringField, BooleanField } = foundry.data.fields; @@ -96,9 +94,7 @@ export default class AgentData extends CharacterData { initializeSanityIfUnset(this.sanity, this.statistics); prepareSanityAdaptations(this.sanity.adaptations); - this.health.protection = computeEquippedArmorProtection( - this.parent.items, - ); + this.health.protection = computeEquippedArmorProtection(this.parent.items); if (this.physical.exhaustedPenalty > 0) { this.physical.exhaustedPenalty = diff --git a/module/data/actor/base/agent-skill-defaults.js b/module/data/actor/base/agent-skill-defaults.js index 7b65a88..b4f1a75 100644 --- a/module/data/actor/base/agent-skill-defaults.js +++ b/module/data/actor/base/agent-skill-defaults.js @@ -1,5 +1,5 @@ /** Default proficiency for fixed agent skills (single source of truth for schema initials). */ -export const AGENT_SKILL_DEFAULTS = /** @type {Record} */ ({ +const AGENT_SKILL_DEFAULTS = /** @type {Record} */ ({ accounting: 10, alertness: 20, anthropology: 0, @@ -37,3 +37,5 @@ export const AGENT_SKILL_DEFAULTS = /** @type {Record} */ ({ swim: 20, unarmed_combat: 40, }); + +export default AGENT_SKILL_DEFAULTS; diff --git a/module/data/actor/base/human-skills.js b/module/data/actor/base/human-skills.js index 39176a3..200a72a 100644 --- a/module/data/actor/base/human-skills.js +++ b/module/data/actor/base/human-skills.js @@ -1,4 +1,4 @@ -import { AGENT_SKILL_DEFAULTS } from "./agent-skill-defaults.js"; +import AGENT_SKILL_DEFAULTS from "./agent-skill-defaults.js"; import { skillField } from "./general.js"; const { SchemaField, NumberField, StringField, ArrayField, ObjectField } = @@ -47,7 +47,12 @@ export default class HumanSkillsActorData extends TypeDataModel { swim: skillField(d.swim, "Swim"), unarmed_combat: skillField(d.unarmed_combat, "Unarmed Combat"), unnatural: new SchemaField({ - proficiency: new NumberField({ initial: 0, min: 0, max: 99, integer: true }), + proficiency: new NumberField({ + initial: 0, + min: 0, + max: 99, + integer: true, + }), label: new StringField({ initial: "Unnatural" }), }), }), diff --git a/module/data/actor/npc.js b/module/data/actor/npc.js index f7d4930..c3e5eea 100644 --- a/module/data/actor/npc.js +++ b/module/data/actor/npc.js @@ -11,7 +11,7 @@ import { prepareStatisticsX5, removeLegacyRitualSkill, setSkillTargetProficiencies, -} from "../../data/derived/actor-derived.js"; +} from "../derived/actor-derived.js"; const { SchemaField, NumberField, StringField, BooleanField } = foundry.data.fields; diff --git a/module/data/actor/unnatural.js b/module/data/actor/unnatural.js index 09dfe84..5a3b565 100644 --- a/module/data/actor/unnatural.js +++ b/module/data/actor/unnatural.js @@ -8,7 +8,7 @@ import { prepareStatisticsX5, removeLegacyRitualSkill, setSkillTargetProficiencies, -} from "../../data/derived/actor-derived.js"; +} from "../derived/actor-derived.js"; const { SchemaField, NumberField, StringField, BooleanField } = foundry.data.fields; diff --git a/module/data/actor/vehicle.js b/module/data/actor/vehicle.js index a0f933b..376909d 100644 --- a/module/data/actor/vehicle.js +++ b/module/data/actor/vehicle.js @@ -1,6 +1,6 @@ import SheetSettingsActorData from "./base/sheet-settings.js"; import DGHTMLField from "../fields/html-content-field.js"; -import { computeEquippedArmorProtection } from "../../data/derived/actor-derived.js"; +import { computeEquippedArmorProtection } from "../derived/actor-derived.js"; const { SchemaField, NumberField, StringField, ArrayField } = foundry.data.fields; diff --git a/module/data/derived/actor-derived.js b/module/data/derived/actor-derived.js index 0e888f1..5e96382 100644 --- a/module/data/derived/actor-derived.js +++ b/module/data/derived/actor-derived.js @@ -90,6 +90,22 @@ export function cleanDerivedNumber(model, path, raw) { return field.clean(numeric, { persisted: false }); } +/** + * @param {object} statistics + * @returns {number} + */ +export function calculateHealthMax(statistics, sourceStatistics) { + try { + return Math.ceil( + (getStatisticEffectiveValue(statistics.con, sourceStatistics?.con) + + getStatisticEffectiveValue(statistics.str, sourceStatistics?.str)) / + 2, + ); + } catch { + return 10; + } +} + /** * @param {foundry.abstract.TypeDataModel} model Agent system TypeDataModel. * @returns {void} @@ -111,9 +127,7 @@ export function applyAgentResourceMaxBonuses(model, sourceStatistics) { model.sanity.max = cleanDerivedNumber( model, "sanity.max", - 99 - - model.skills.unnatural.proficiency + - (model.sanity.maxBonus ?? 0), + 99 - model.skills.unnatural.proficiency + (model.sanity.maxBonus ?? 0), ); } @@ -132,8 +146,7 @@ export function initializeSanityIfUnset(sanity, statistics) { if (sanity.value >= 100) { sanity.value = statistics.pow.x5; sanity.currentBreakingPoint = - sanity.value - - (statistics.pow.effectiveValue ?? statistics.pow.value); + sanity.value - (statistics.pow.effectiveValue ?? statistics.pow.value); } } @@ -198,22 +211,3 @@ export function removeLegacyRitualSkill(system) { // Legacy field may be absent or non-configurable. } } - -/** - * @param {object} statistics - * @returns {number} - */ -export function calculateHealthMax(statistics, sourceStatistics) { - try { - return Math.ceil( - (getStatisticEffectiveValue( - statistics.con, - sourceStatistics?.con, - ) + - getStatisticEffectiveValue(statistics.str, sourceStatistics?.str)) / - 2, - ); - } catch { - return 10; - } -} diff --git a/module/data/item/profession.js b/module/data/item/profession.js index cce5c37..fc6c76a 100644 --- a/module/data/item/profession.js +++ b/module/data/item/profession.js @@ -19,7 +19,9 @@ export default class ProfessionItemData extends foundry.abstract.TypeDataModel { bonds: new NumberField({ initial: 1, min: 1, max: 5, integer: true }), automaticSkills: new ObjectField({ initial: {} }), automaticSkillMeta: new ObjectField({ initial: {} }), - optionSkills: new ObjectField({ initial: { [PROFESSION_OPTION_PICKS_KEY]: 0 } }), + optionSkills: new ObjectField({ + initial: { [PROFESSION_OPTION_PICKS_KEY]: 0 }, + }), optionSkillMeta: new ObjectField({ initial: {} }), }; } diff --git a/module/deltagreen.js b/module/deltagreen.js index b5138ae..562b143 100644 --- a/module/deltagreen.js +++ b/module/deltagreen.js @@ -23,7 +23,7 @@ import { rollSkillTestAndDamageForOwnedItem, } from "./macros/macro-functions.js"; import { handleInlineActions } from "./chat/inline.js"; -import { runWorldMigration } from "./utils/world-migration.js"; +import runWorldMigration from "./utils/world-migration.js"; import DGNPCSheet from "./sheets/npc-sheet.js"; import DGUnnaturalSheet from "./sheets/unnatural-sheet.js"; import DGVehicleSheet from "./sheets/vehicle-sheet.js"; diff --git a/module/item/item.js b/module/item/item.js index d8d08b8..bc1fb07 100644 --- a/module/item/item.js +++ b/module/item/item.js @@ -1,5 +1,5 @@ import { getDGRollToken } from "../chat/dg-chat-card.js"; -import { appendMeleeDamageBonus } from "../roll/melee-damage.js"; +import appendMeleeDamageBonus from "../roll/melee-damage.js"; import { DGDamageRoll, DGLethalityRoll } from "../roll/roll.js"; /** diff --git a/module/macros/stat-parser-macro.js b/module/macros/stat-parser-macro.js index a2929f5..5082b81 100644 --- a/module/macros/stat-parser-macro.js +++ b/module/macros/stat-parser-macro.js @@ -115,9 +115,7 @@ function GetAttacksFromInput(inputText) { if (attackLine.indexOf("ARMOR PIERCING") >= 0) { const apMatch = attackLine.match(/ARMOR\s+PIERCING\s*(\d+)/i); - const armorPiercing = apMatch?.[1] - ? parseInt(apMatch[1], 10) - : 5; + const armorPiercing = apMatch?.[1] ? parseInt(apMatch[1]) : 5; weaponData.armorPiercing = armorPiercing; weaponData.system.armorPiercing = armorPiercing; } diff --git a/module/profession/catalog.js b/module/profession/catalog.js index 2069e53..0d2e1fe 100644 --- a/module/profession/catalog.js +++ b/module/profession/catalog.js @@ -167,16 +167,16 @@ export function collectBonusCapValidationErrors( const catalogId = bonusCatalogIds?.[i]; if (!catalogId) { slotTrackKeys.push(null); - continue; + } else { + const ref = catalogIdToSkillRef(catalogId, bonusTypedLabels?.[i] ?? ""); + if (!ref) { + slotTrackKeys.push(null); + } else { + const trackKey = getBonusTrackKey(ref); + slotTrackKeys.push(trackKey); + counts[trackKey] = (counts[trackKey] ?? 0) + 1; + } } - const ref = catalogIdToSkillRef(catalogId, bonusTypedLabels?.[i] ?? ""); - if (!ref) { - slotTrackKeys.push(null); - continue; - } - const trackKey = getBonusTrackKey(ref); - slotTrackKeys.push(trackKey); - counts[trackKey] = (counts[trackKey] ?? 0) + 1; } /** @type {Set} */ @@ -200,19 +200,19 @@ export function collectBonusCapValidationErrors( const errors = []; for (let i = 0; i < BONUS_SKILL_COUNT; i++) { const trackKey = slotTrackKeys[i]; - if (!trackKey || !violatingTrackKeys.has(trackKey)) continue; + if (trackKey && violatingTrackKeys.has(trackKey)) { + const base = getBonusTrackBaseValue( + trackKey, + baseFixed, + baseTyped, + defaults, + ); + const count = counts[trackKey] ?? 0; + const final = base + count * BONUS_SKILL_INCREMENT; + const waste = Math.max(0, final - SKILL_CAP); - const base = getBonusTrackBaseValue( - trackKey, - baseFixed, - baseTyped, - defaults, - ); - const count = counts[trackKey] ?? 0; - const final = base + count * BONUS_SKILL_INCREMENT; - const waste = Math.max(0, final - SKILL_CAP); - - errors.push(`bonusWaste:${i}|${waste}|${trackKey}`); + errors.push(`bonusWaste:${i}|${waste}|${trackKey}`); + } } return errors; diff --git a/module/profession/compute.js b/module/profession/compute.js index 09ad678..639839b 100644 --- a/module/profession/compute.js +++ b/module/profession/compute.js @@ -40,7 +40,7 @@ import { validateProfessionFormState } from "./validation.js"; * modifiedTypedKeys: string[], * }} */ -export function computeSkillValues( +export default function computeSkillValues( automaticSkills, optionSkills, optionPicks, @@ -100,39 +100,46 @@ export function computeSkillValues( for (const [key, rating] of Object.entries(automaticSkills ?? {})) { const ref = parseProfessionSkillKey(key); - if (!ref) continue; - - let labelOverride; - if (ref.kind === "typed" && automaticMeta[key]?.chooseOne) { - labelOverride = formState.chooseOneLabels?.[key]?.trim(); - if (!labelOverride) continue; - } - - const storageKey = applyOverride(key, rating, { labelOverride }); - if (ref.kind === "fixed") modifiedFixedKeys.add(ref.key); - else if (storageKey) { - markTypedModified(ref, labelOverride ?? ref.label); + if (ref) { + let labelOverride; + let skip = false; + if (ref.kind === "typed" && automaticMeta[key]?.chooseOne) { + labelOverride = formState.chooseOneLabels?.[key]?.trim(); + if (!labelOverride) skip = true; + } + if (!skip) { + const storageKey = applyOverride(key, rating, { labelOverride }); + if (ref.kind === "fixed") modifiedFixedKeys.add(ref.key); + else if (storageKey) { + markTypedModified(ref, labelOverride ?? ref.label); + } + } } } for (const key of formState.checkedOptionKeys ?? []) { - if (!optionSkills[key] && optionSkills[key] !== 0) continue; - const ref = parseProfessionSkillKey(key); - if (!ref) continue; - - let labelOverride; - if (ref.kind === "typed" && optionMeta[key]?.chooseOne) { - labelOverride = formState.chooseOneLabels?.[key]?.trim(); - if (!labelOverride) continue; - } else if (ref.kind === "typed") { - labelOverride = ref.label?.trim(); - if (!labelOverride) continue; - } - - const storageKey = applyOverride(key, optionSkills[key], { labelOverride }); - if (ref.kind === "fixed") modifiedFixedKeys.add(ref.key); - else if (storageKey) { - markTypedModified(ref, labelOverride ?? ref.label); + if (optionSkills[key] || optionSkills[key] === 0) { + const ref = parseProfessionSkillKey(key); + if (ref) { + let labelOverride; + let skip = false; + if (ref.kind === "typed" && optionMeta[key]?.chooseOne) { + labelOverride = formState.chooseOneLabels?.[key]?.trim(); + if (!labelOverride) skip = true; + } else if (ref.kind === "typed") { + labelOverride = ref.label?.trim(); + if (!labelOverride) skip = true; + } + if (!skip) { + const storageKey = applyOverride(key, optionSkills[key], { + labelOverride, + }); + if (ref.kind === "fixed") modifiedFixedKeys.add(ref.key); + else if (storageKey) { + markTypedModified(ref, labelOverride ?? ref.label); + } + } + } } } @@ -144,21 +151,22 @@ export function computeSkillValues( for (let i = 0; i < BONUS_SKILL_COUNT; i++) { const catalogId = bonusIds[i]; - if (!catalogId) continue; - const typedLabel = bonusLabels[i] ?? ""; - const ref = catalogIdToSkillRef(catalogId, typedLabel); - if (!ref) continue; + if (catalogId) { + const typedLabel = bonusLabels[i] ?? ""; + const ref = catalogIdToSkillRef(catalogId, typedLabel); + if (ref) { + if (ref.kind === "fixed") modifiedFixedKeys.add(ref.key); + else modifiedTypedKeys.add(formatProfessionSkillKey(ref)); - if (ref.kind === "fixed") modifiedFixedKeys.add(ref.key); - else modifiedTypedKeys.add(formatProfessionSkillKey(ref)); - - let trackKey; - if (ref.kind === "fixed") { - trackKey = `fixed:${ref.key}`; - } else { - trackKey = formatProfessionSkillKey(ref); + let trackKey; + if (ref.kind === "fixed") { + trackKey = `fixed:${ref.key}`; + } else { + trackKey = formatProfessionSkillKey(ref); + } + bonusCounts[trackKey] = (bonusCounts[trackKey] ?? 0) + 1; + } } - bonusCounts[trackKey] = (bonusCounts[trackKey] ?? 0) + 1; } /** Pre-bonus base for cap warnings */ @@ -172,14 +180,14 @@ export function computeSkillValues( fixedValues[key] = (fixedValues[key] ?? defaults[key] ?? 0) + bonus; } else { const ref = parseProfessionSkillKey(trackKey); - if (ref?.kind !== "typed") continue; - - const base = typedValues[trackKey]?.value ?? 0; - typedValues[trackKey] = { - group: ref.group, - label: ref.label, - value: base + bonus, - }; + if (ref?.kind === "typed") { + const base = typedValues[trackKey]?.value ?? 0; + typedValues[trackKey] = { + group: ref.group, + label: ref.label, + value: base + bonus, + }; + } } } diff --git a/module/profession/index.js b/module/profession/index.js index c71f103..2563e8c 100644 --- a/module/profession/index.js +++ b/module/profession/index.js @@ -21,7 +21,7 @@ export { catalogIdToSkillRef, collectBonusCapValidationErrors, } from "./catalog.js"; -export { computeSkillValues } from "./compute.js"; +export { default as computeSkillValues } from "./compute.js"; export { validateProfessionFormState, formatProfessionValidationMessages, diff --git a/module/profession/keys.js b/module/profession/keys.js index aea75ca..bea1d5b 100644 --- a/module/profession/keys.js +++ b/module/profession/keys.js @@ -1,5 +1,5 @@ import DG from "../config/index.js"; -import { AGENT_SKILL_DEFAULTS } from "../data/actor/base/agent-skill-defaults.js"; +import AGENT_SKILL_DEFAULTS from "../data/actor/base/agent-skill-defaults.js"; import { PROFESSION_OPTION_PICKS_KEY } from "../data/item/profession.js"; import { INTERNAL_TYPED_KEY_PATTERN, @@ -48,6 +48,64 @@ export function isChooseOneProfessionSkillKey( ); } +/** + * @returns {Record} + */ +export function getAgentSkillDefaults() { + return { ...AGENT_SKILL_DEFAULTS }; +} + +/** + * @param {string} group + * @returns {string} + */ +export function normalizeTypedGroup(group) { + if (group == null) return ""; + const trimmed = String(group).trim(); + const map = { + "Foreign Language": "ForeignLanguage", + "Military Science": "MilitaryScience", + }; + return map[trimmed] ?? trimmed.replace(/\s+/g, ""); +} + +/** + * @param {string} key + * @returns {ProfessionSkillRef | null} + */ +export function parseProfessionSkillKey(key) { + if (!key || key === PROFESSION_OPTION_PICKS_KEY) return null; + if (DG.skills.includes(key)) return { kind: "fixed", key }; + + const internalMatch = key.match(INTERNAL_TYPED_KEY_PATTERN); + if (internalMatch) { + let label = ""; + if (internalMatch[3] !== undefined) { + try { + label = decodeURIComponent(internalMatch[3]); + } catch { + const [, , , fallbackLabel] = internalMatch; + label = fallbackLabel; + } + } + return { + kind: "typed", + group: internalMatch[1], + label, + }; + } + + const match = key.match(TYPED_KEY_PATTERN); + if (match) { + return { + kind: "typed", + group: normalizeTypedGroup(match[1]), + label: match[2].trim(), + }; + } + return null; +} + /** * @param {object} params * @param {string} params.group @@ -85,44 +143,24 @@ export function findTypedSkillNameConflict({ for (const [skillMap, metaMap] of maps) { for (const mapKey of Object.keys(skillMap ?? {})) { - if (mapKey === excludeKey || mapKey === PROFESSION_OPTION_PICKS_KEY) - continue; - - const ref = parseProfessionSkillKey(mapKey); - if (!ref || ref.kind !== "typed") continue; - if (normalizeTypedGroup(ref.group) !== normalizedGroup) continue; - - // Unresolved choose-one slots have no fixed name yet. - if (metaMap[mapKey]?.chooseOne) continue; - - if (normalizeTypedSkillName(ref.label) === normalizedNew) return mapKey; + if (mapKey !== excludeKey && mapKey !== PROFESSION_OPTION_PICKS_KEY) { + const ref = parseProfessionSkillKey(mapKey); + if ( + ref && + ref.kind === "typed" && + normalizeTypedGroup(ref.group) === normalizedGroup && + !metaMap[mapKey]?.chooseOne && + normalizeTypedSkillName(ref.label) === normalizedNew + ) { + return mapKey; + } + } } } return null; } -/** - * @returns {Record} - */ -export function getAgentSkillDefaults() { - return { ...AGENT_SKILL_DEFAULTS }; -} - -/** - * @param {string} group - * @returns {string} - */ -export function normalizeTypedGroup(group) { - if (group == null) return ""; - const trimmed = String(group).trim(); - const map = { - "Foreign Language": "ForeignLanguage", - "Military Science": "MilitaryScience", - }; - return map[trimmed] ?? trimmed.replace(/\s+/g, ""); -} - /** * @param {string} group * @returns {string} @@ -169,42 +207,6 @@ export function allocateProfessionSkillStorageKey( }); } -/** - * @param {string} key - * @returns {ProfessionSkillRef | null} - */ -export function parseProfessionSkillKey(key) { - if (!key || key === PROFESSION_OPTION_PICKS_KEY) return null; - if (DG.skills.includes(key)) return { kind: "fixed", key }; - - const internalMatch = key.match(INTERNAL_TYPED_KEY_PATTERN); - if (internalMatch) { - let label = ""; - if (internalMatch[3] !== undefined) { - try { - label = decodeURIComponent(internalMatch[3]); - } catch { - label = internalMatch[3]; - } - } - return { - kind: "typed", - group: internalMatch[1], - label, - }; - } - - const match = key.match(TYPED_KEY_PATTERN); - if (match) { - return { - kind: "typed", - group: normalizeTypedGroup(match[1]), - label: match[2].trim(), - }; - } - return null; -} - /** * @param {ProfessionSkillRef} ref * @returns {string} @@ -262,8 +264,9 @@ export function splitProfessionSkillMap(obj) { /** @type {Record} */ const skills = {}; for (const [key, value] of Object.entries(source)) { - if (key === PROFESSION_OPTION_PICKS_KEY) continue; - skills[key] = Number(value); + if (key !== PROFESSION_OPTION_PICKS_KEY) { + skills[key] = Number(value); + } } return { optionPicks, skills }; } diff --git a/module/profession/validation.js b/module/profession/validation.js index 882aa35..8147372 100644 --- a/module/profession/validation.js +++ b/module/profession/validation.js @@ -28,7 +28,6 @@ export function validateProfessionFormState( const picks = Number(optionPicks) || 0; const { automaticSkills = {}, - optionSkills = {}, automaticMeta = {}, optionMeta = {}, bondCount = 0, @@ -49,18 +48,19 @@ export function validateProfessionFormState( const catalogId = formState.bonusCatalogIds?.[i]; if (!catalogId) { errors.push(`bonus${i}`); - continue; - } - const ref = catalogIdToSkillRef( - catalogId, - formState.bonusTypedLabels?.[i] ?? "", - ); - if (!ref) { - errors.push(`bonus${i}`); - continue; - } - if (ref.kind === "typed" && !formState.bonusTypedLabels?.[i]?.trim()) { - errors.push(`bonusType${i}`); + } else { + const ref = catalogIdToSkillRef( + catalogId, + formState.bonusTypedLabels?.[i] ?? "", + ); + if (!ref) { + errors.push(`bonus${i}`); + } else if ( + ref.kind === "typed" && + !formState.bonusTypedLabels?.[i]?.trim() + ) { + errors.push(`bonusType${i}`); + } } } @@ -86,21 +86,23 @@ export function validateProfessionFormState( for (const [key] of Object.entries(automaticSkills)) { const ref = parseProfessionSkillKey(key); - if (!ref || ref.kind !== "typed") continue; - if (automaticMeta[key]?.chooseOne) { - registerResolvedName(ref, formState.chooseOneLabels?.[key]); - } else { - registerResolvedName(ref, ref.label); + if (ref && ref.kind === "typed") { + if (automaticMeta[key]?.chooseOne) { + registerResolvedName(ref, formState.chooseOneLabels?.[key]); + } else { + registerResolvedName(ref, ref.label); + } } } for (const key of checked) { const ref = parseProfessionSkillKey(key); - if (!ref || ref.kind !== "typed") continue; - if (optionMeta[key]?.chooseOne) { - registerResolvedName(ref, formState.chooseOneLabels?.[key]); - } else { - registerResolvedName(ref, ref.label); + if (ref && ref.kind === "typed") { + if (optionMeta[key]?.chooseOne) { + registerResolvedName(ref, formState.chooseOneLabels?.[key]); + } else { + registerResolvedName(ref, ref.label); + } } } @@ -140,46 +142,35 @@ export function formatProfessionValidationMessages( for (const code of errors) { if (code === "optionPicks") { needsOptionPicks = true; - continue; - } - if (code.startsWith("bondName")) { + } else if (code.startsWith("bondName")) { needsBondName = true; - continue; - } - if (code.startsWith("bondRelationship")) { + } else if (code.startsWith("bondRelationship")) { needsBondRelationship = true; - continue; - } - const bonusWaste = code.match(/^bonusWaste:\d+\|(\d+)\|(.+)$/); - if (bonusWaste) { - bonusWasteTrackKeys.push(bonusWaste[2]); - continue; - } - - if (code.startsWith("bonusType")) { - needsBonusSkillType = true; - continue; - } - if (/^bonus\d+$/.test(code)) { - needsBonusSkill = true; - continue; - } - - const typedNameRequired = code.match(/^typedNameRequired:(.+)$/); - if (typedNameRequired) { - typedNameRequiredGroups.add(typedNameRequired[1]); - continue; - } - - const conflict = code.match(/^typedNameConflict:([^:]+):(.+)$/); - if (conflict) { - const [, group, name] = conflict; - push( - game.i18n.format("DG.Profession.Dialog.DuplicateTypedName", { - type: getTypedGroupDisplayName(group), - name, - }), - ); + } else { + const bonusWaste = code.match(/^bonusWaste:\d+\|(\d+)\|(.+)$/); + if (bonusWaste) { + bonusWasteTrackKeys.push(bonusWaste[2]); + } else if (code.startsWith("bonusType")) { + needsBonusSkillType = true; + } else if (/^bonus\d+$/.test(code)) { + needsBonusSkill = true; + } else { + const typedNameRequired = code.match(/^typedNameRequired:(.+)$/); + if (typedNameRequired) { + typedNameRequiredGroups.add(typedNameRequired[1]); + } else { + const conflict = code.match(/^typedNameConflict:([^:]+):(.+)$/); + if (conflict) { + const [, group, name] = conflict; + push( + game.i18n.format("DG.Profession.Dialog.DuplicateTypedName", { + type: getTypedGroupDisplayName(group), + name, + }), + ); + } + } + } } } diff --git a/module/roll/melee-damage.js b/module/roll/melee-damage.js index f9c9129..46b357e 100644 --- a/module/roll/melee-damage.js +++ b/module/roll/melee-damage.js @@ -6,7 +6,7 @@ * @param {string|null|undefined} skill Weapon skill key from item system * @returns {string} */ -export function appendMeleeDamageBonus(diceFormula, actor, skill) { +export default function appendMeleeDamageBonus(diceFormula, actor, skill) { if ( !actor || (actor.type !== "agent" && actor.type !== "npc") || diff --git a/module/roll/roll.js b/module/roll/roll.js index 55dae63..d622b66 100644 --- a/module/roll/roll.js +++ b/module/roll/roll.js @@ -3,7 +3,7 @@ import { DGPercentileRoll } from "./classes/dg-percentile-roll.js"; import { DGLethalityRoll } from "./classes/dg-lethality-roll.js"; import { DGDamageRoll } from "./classes/dg-damage-roll.js"; import { DGSanityDamageRoll } from "./classes/dg-sanity-damage-roll.js"; -import { appendMeleeDamageBonus } from "./melee-damage.js"; +import appendMeleeDamageBonus from "./melee-damage.js"; export { DGRoll, diff --git a/module/roll/skill-improvement-roll.js b/module/roll/skill-improvement-roll.js index 61941cd..682f66c 100644 --- a/module/roll/skill-improvement-roll.js +++ b/module/roll/skill-improvement-roll.js @@ -86,7 +86,10 @@ export async function createSkillImprovementChatMessage({ }; const failedSkillNames = localizeFailedSkills(failedSkills, "fixed"); - const failedTypedSkillNames = localizeFailedSkills(failedTypedSkills, "typed"); + const failedTypedSkillNames = localizeFailedSkills( + failedTypedSkills, + "typed", + ); const content = [...failedSkillNames, ...failedTypedSkillNames].join(", "); const label = game.i18n.format( diff --git a/module/settings.js b/module/settings.js index a1579bd..801ab19 100644 --- a/module/settings.js +++ b/module/settings.js @@ -88,7 +88,7 @@ const SettingForm = class extends HandlebarsApplicationMixin(ApplicationV2) { } // Once all promises resolve, show a notification. - Promise.allSettled(settingsPromises).then((values) => { + Promise.allSettled(settingsPromises).then(() => { ui.notifications.info(game.i18n.localize("DG.Settings.Saved")); }); } @@ -298,7 +298,7 @@ export default function registerSystemSettings() { program: game.i18n.localize("DG.Settings.charactersheet.program"), }, default: "program", // The default value for the setting - onChange: (value) => { + onChange: () => { // A callback function which triggers when the setting is changed // console.log(value) }, diff --git a/module/sheets/agent-sheet.js b/module/sheets/agent-sheet.js index 3c2bfc1..ba9ccc8 100644 --- a/module/sheets/agent-sheet.js +++ b/module/sheets/agent-sheet.js @@ -2,7 +2,7 @@ import DG, { BASE_TEMPLATE_PATH } from "../config/index.js"; import { prepareAgentSkillColumns } from "../utils/skill-layout.js"; import { formatProfessionSkillLabel } from "../profession/index.js"; import { buildSkillTooltip } from "../utils/skill-tooltip.js"; -import { createAgentResourceChatMessage } from "../chat/resource-chat.js"; +import createAgentResourceChatMessage from "../chat/resource-chat.js"; import { createSkillImprovementChatMessage, evaluateSkillImprovementRolls, @@ -17,9 +17,9 @@ import { getEffectiveSuppressExhaustion, hasActiveStimulantEffect, } from "../active-effect/runtime/stimulant-effect.js"; -import { assignProfessionToAgent } from "../applications/profession-setup-flow.js"; +import assignProfessionToAgent from "../applications/profession-setup-flow.js"; import { showDgDialog } from "../applications/dg-dialog.js"; -import { showRenameProfessionDialog } from "../applications/rename-profession-dialog.js"; +import showRenameProfessionDialog from "../applications/rename-profession-dialog.js"; import { PROFESSION_OPTION_PICKS_KEY } from "../data/item/profession.js"; const { renderTemplate } = foundry.applications.handlebars; @@ -304,8 +304,9 @@ export default class DGAgentSheet extends AgentSheetBase { static _clearBondDamage() { for (const bond of this.actor.itemTypes.bond) { - if (!bond.system.hasBeenDamagedSinceLastHomeScene) continue; - bond.update({ "system.hasBeenDamagedSinceLastHomeScene": false }); + if (bond.system.hasBeenDamagedSinceLastHomeScene) { + bond.update({ "system.hasBeenDamagedSinceLastHomeScene": false }); + } } } @@ -558,9 +559,7 @@ export default class DGAgentSheet extends AgentSheetBase { */ static _collectFailedSkills(skillMap) { return Object.entries(skillMap) - .filter( - ([, skill]) => skill.failure && !skill.cannotBeImprovedByFailure, - ) + .filter(([, skill]) => skill.failure && !skill.cannotBeImprovedByFailure) .map(([key, skill]) => ({ ...skill, key })); } diff --git a/module/sheets/base-item-sheet.js b/module/sheets/base-item-sheet.js index c11270a..55dee7f 100644 --- a/module/sheets/base-item-sheet.js +++ b/module/sheets/base-item-sheet.js @@ -39,10 +39,7 @@ function activeEffectItemLayout({ header, description, descriptionLabel }) { return { tabs: { initial: "description", - tabs: [ - { id: "description", label: descriptionLabel }, - EFFECTS_TAB, - ], + tabs: [{ id: "description", label: descriptionLabel }, EFFECTS_TAB], }, parts: { header: { template: header }, @@ -195,8 +192,13 @@ export default class DGItemSheet extends ItemSheetBase { .filter((tab) => !tab.gmOnly || game.user.isGM) .map((tab) => tab.id); for (const partId of Object.keys(parts)) { - if (partId === "tabs" || partId === "header") continue; - if (!tabIds.includes(partId)) delete parts[partId]; + if ( + partId !== "tabs" && + partId !== "header" && + !tabIds.includes(partId) + ) { + delete parts[partId]; + } } } Object.values(parts).forEach((p) => { @@ -212,13 +214,19 @@ export default class DGItemSheet extends ItemSheetBase { return super._getTabsConfig(group); } - const tabs = layout.tabs.tabs.filter((tab) => !tab.gmOnly || game.user.isGM); + const tabs = layout.tabs.tabs.filter( + (tab) => !tab.gmOnly || game.user.isGM, + ); return { ...layout.tabs, tabs }; } /** @inheritdoc */ async _preparePartContext(partId, context, options) { - const partContext = await super._preparePartContext(partId, context, options); + const partContext = await super._preparePartContext( + partId, + context, + options, + ); if (partId === "tabs") partContext.tabClasses = "sheet-tabs"; return partContext; } diff --git a/module/sheets/base-sheet.js b/module/sheets/base-sheet.js index 7692c46..5b773fa 100644 --- a/module/sheets/base-sheet.js +++ b/module/sheets/base-sheet.js @@ -1,5 +1,5 @@ import DG, { BASE_TEMPLATE_PATH } from "../config/index.js"; -import { getRichTextFieldsForPart } from "../config/rich-text-fields.js"; +import getRichTextFieldsForPart from "../config/rich-text-fields.js"; import { prepareRichTextContext } from "../utils/rich-text.js"; const HbsAppMixin = foundry.applications.api.HandlebarsApplicationMixin; @@ -40,7 +40,7 @@ const DGSheetMixin = (Base) => { async _preparePartContext(partId, context, options) { context = await super._preparePartContext(partId, context, options); - const documentName = this.document.documentName; + const { documentName } = this.document; const fieldSpecs = getRichTextFieldsForPart(documentName, partId, { actorType: this.document.type, itemType: this.document.type, diff --git a/module/sheets/mixins/ae-input-mixin.js b/module/sheets/mixins/ae-input-mixin.js index ff7fc06..2a06e25 100644 --- a/module/sheets/mixins/ae-input-mixin.js +++ b/module/sheets/mixins/ae-input-mixin.js @@ -27,34 +27,34 @@ export default function AeInputMixin(Base) { const input = host.querySelector("input"); const display = host.querySelector(".ae-input-display"); - if (!input || !display) continue; + if (input && display) { + const modifier = Number(display.dataset.aeModifier) || 0; - const modifier = Number(display.dataset.aeModifier) || 0; + const syncTypography = () => syncAeDisplayTypography(input, display); - const syncTypography = () => syncAeDisplayTypography(input, display); + const syncDisplay = () => { + if (host.matches(":focus-within")) return; + const base = Number(input.value); + if (Number.isFinite(base)) { + const effective = Math.max(0, Math.round(base + modifier)); + display.textContent = String(effective); + } + }; - const syncDisplay = () => { - if (host.matches(":focus-within")) return; - const base = Number(input.value); - if (Number.isFinite(base)) { - const effective = Math.max(0, Math.round(base + modifier)); - display.textContent = String(effective); - } - }; + input.addEventListener("focus", () => input.select()); + input.addEventListener("blur", () => { + syncTypography(); + syncDisplay(); + }); - input.addEventListener("focus", () => input.select()); - input.addEventListener("blur", () => { syncTypography(); + requestAnimationFrame(syncTypography); syncDisplay(); - }); - syncTypography(); - requestAnimationFrame(syncTypography); - syncDisplay(); - - const observer = new ResizeObserver(syncTypography); - observer.observe(input); - hostObservers.set(host, observer); + const observer = new ResizeObserver(syncTypography); + observer.observe(input); + hostObservers.set(host, observer); + } } } }; diff --git a/module/sheets/mixins/effects-tab-mixin.js b/module/sheets/mixins/effects-tab-mixin.js index a3ea9cb..4aece5a 100644 --- a/module/sheets/mixins/effects-tab-mixin.js +++ b/module/sheets/mixins/effects-tab-mixin.js @@ -96,10 +96,10 @@ export default function EffectsTabMixin(Base) { * @param {PointerEvent} _event * @param {HTMLElement} target */ - static async createEffect(_event, target) { + static async createEffect() { const sheet = /** @type {EffectsTabHost} */ (this); const parent = sheet._effectsParent; - const documentClass = getDocumentClass("ActiveEffect"); + const documentClass = foundry.utils.getDocumentClass("ActiveEffect"); const docData = { name: game.i18n.localize("DG.ActiveEffects.DefaultName"), diff --git a/module/sheets/mixins/item-list-mixin.js b/module/sheets/mixins/item-list-mixin.js index 1f06599..7cf1240 100644 --- a/module/sheets/mixins/item-list-mixin.js +++ b/module/sheets/mixins/item-list-mixin.js @@ -4,7 +4,7 @@ import { showDgDialog } from "../../applications/dg-dialog.js"; export default function ItemListMixin(Base) { return class extends Base { /** @param {object} [context] */ - _prepareCharacterItems(context) { + _prepareCharacterItems() { const { actor } = this; const armor = []; @@ -190,7 +190,9 @@ export default function ItemListMixin(Base) { static _browsePack(event, target) { const { packType } = target.dataset; if (packType === "weapon") { - void this._browseWeaponPack(); + this._browseWeaponPack().catch((error) => { + console.error(error); + }); return; } diff --git a/module/sheets/mixins/profession-item-mixin.js b/module/sheets/mixins/profession-item-mixin.js index 677de44..e6fcb18 100644 --- a/module/sheets/mixins/profession-item-mixin.js +++ b/module/sheets/mixins/profession-item-mixin.js @@ -21,7 +21,11 @@ export default function ProfessionItemMixin(Base) { return class extends Base { /** @inheritdoc */ async _preparePartContext(partId, context, options) { - const partContext = await super._preparePartContext(partId, context, options); + const partContext = await super._preparePartContext( + partId, + context, + options, + ); if (this.item.type !== "profession" || partId !== "skills") { return partContext; } @@ -83,7 +87,9 @@ export default function ProfessionItemMixin(Base) { onRender: (dialog) => { const root = getDialogContentRoot(dialog); const select = root?.querySelector('[name="skillCatalogId"]'); - const typedGroup = root?.querySelector(".profession-typed-label-group"); + const typedGroup = root?.querySelector( + ".profession-typed-label-group", + ); const typeInput = root?.querySelector('[name="typedSkillLabel"]'); const chooseOneInput = root?.querySelector('[name="chooseOne"]'); @@ -135,14 +141,20 @@ export default function ProfessionItemMixin(Base) { const isTyped = catalogId.startsWith("typed:"); if (isTyped && !chooseOne && !typedLabel) { ui.notifications.warn( - game.i18n.localize("DG.ItemWindow.Profession.TypedSkillRequired"), + game.i18n.localize( + "DG.ItemWindow.Profession.TypedSkillRequired", + ), ); return false; } - const ref = catalogIdToSkillRef(catalogId, chooseOne ? "" : typedLabel, { - allowEmptyTypedLabel: chooseOne, - }); + const ref = catalogIdToSkillRef( + catalogId, + chooseOne ? "" : typedLabel, + { + allowEmptyTypedLabel: chooseOne, + }, + ); if (!ref) return false; const skillKey = allocateProfessionSkillStorageKey(ref, { @@ -156,7 +168,9 @@ export default function ProfessionItemMixin(Base) { const automaticMeta = { ...(this.item.system.automaticSkillMeta ?? {}), }; - const optionMeta = { ...(this.item.system.optionSkillMeta ?? {}) }; + const optionMeta = { + ...(this.item.system.optionSkillMeta ?? {}), + }; const conflict = ref.kind === "typed" @@ -262,8 +276,9 @@ export default function ProfessionItemMixin(Base) { */ static removeAutomaticSkill(event, target) { const skillKey = this.#getProfessionSkillKeyFromTarget(target); - if (!skillKey) return; - return this.#removeProfessionSkill("automaticSkills", skillKey); + if (skillKey) { + this.#removeProfessionSkill("automaticSkills", skillKey); + } } /** @@ -272,9 +287,9 @@ export default function ProfessionItemMixin(Base) { */ static removeOptionSkill(event, target) { const skillKey = this.#getProfessionSkillKeyFromTarget(target); - if (!skillKey) return; - if (skillKey === PROFESSION_OPTION_PICKS_KEY) return; - return this.#removeProfessionSkill("optionSkills", skillKey); + if (skillKey && skillKey !== PROFESSION_OPTION_PICKS_KEY) { + this.#removeProfessionSkill("optionSkills", skillKey); + } } }; } diff --git a/module/sheets/mixins/roll-sheet-mixin.js b/module/sheets/mixins/roll-sheet-mixin.js index 210c386..7367ac4 100644 --- a/module/sheets/mixins/roll-sheet-mixin.js +++ b/module/sheets/mixins/roll-sheet-mixin.js @@ -1,8 +1,5 @@ import { getDGRollToken } from "../../chat/dg-chat-card.js"; -import { - createDGRollFromDataset, - processDGRoll, -} from "../../roll/roll.js"; +import { createDGRollFromDataset, processDGRoll } from "../../roll/roll.js"; /** @param {typeof foundry.applications.api.ApplicationV2} Base */ export default function RollSheetMixin(Base) { @@ -39,7 +36,8 @@ export default function RollSheetMixin(Base) { * @private */ static async _onRoll(event, target) { - if (target.classList.contains("not-rollable") || event.which === 2) return; + if (target.classList.contains("not-rollable") || event.which === 2) + return; const item = this.actor.items.get(target.dataset.iid); const roll = createDGRollFromDataset(target.dataset, { diff --git a/module/sheets/mixins/skill-prep-mixin.js b/module/sheets/mixins/skill-prep-mixin.js index 3fba0eb..4aa0713 100644 --- a/module/sheets/mixins/skill-prep-mixin.js +++ b/module/sheets/mixins/skill-prep-mixin.js @@ -76,7 +76,9 @@ export default function SkillPrepMixin(Base) { const specialTrainings = this._prepareSpecialTrainings(); const sortedCustomSkills = []; - for (const [key, skill] of Object.entries(this.actor.system.typedSkills)) { + for (const [key, skill] of Object.entries( + this.actor.system.typedSkills, + )) { skill.type = "typeSkill"; skill.key = key; skill.sortLabel = `${skill.group}.${skill.label}`; @@ -180,7 +182,7 @@ export default function SkillPrepMixin(Base) { simplifiedTraining.attributeTooltip = attributeTooltip; if (this.actor.type === "agent") { - const rollTarget = this.actor.system.rollTarget; + const { rollTarget } = this.actor.system; let modifier = 0; if (rollTarget) { diff --git a/module/sheets/mixins/special-training-mixin.js b/module/sheets/mixins/special-training-mixin.js index 567d079..3a95062 100644 --- a/module/sheets/mixins/special-training-mixin.js +++ b/module/sheets/mixins/special-training-mixin.js @@ -23,7 +23,9 @@ export default function SpecialTrainingMixin(Base) { } break; default: - void this._showSpecialTrainingDialog(actionType, id); + this._showSpecialTrainingDialog(actionType, id).catch((error) => { + console.error(error); + }); break; } } diff --git a/module/sheets/mixins/typed-skill-mixin.js b/module/sheets/mixins/typed-skill-mixin.js index 1204206..3828bab 100644 --- a/module/sheets/mixins/typed-skill-mixin.js +++ b/module/sheets/mixins/typed-skill-mixin.js @@ -26,12 +26,16 @@ export default function TypedSkillMixin(Base) { const { actionType, typedskill } = target.dataset; switch (actionType) { case "create": - void this._showTypedSkillDialog({ mode: "create" }); + this._showTypedSkillDialog({ mode: "create" }).catch((error) => { + console.error(error); + }); break; case "edit": - void this._showTypedSkillDialog({ + this._showTypedSkillDialog({ mode: "edit", targetSkill: typedskill, + }).catch((error) => { + console.error(error); }); break; case "delete": @@ -47,7 +51,7 @@ export default function TypedSkillMixin(Base) { */ async _showTypedSkillDialog({ mode, targetSkill }) { const isEdit = mode === "edit"; - const typedSkills = this.actor.system.typedSkills; + const { typedSkills } = this.actor.system; const currentLabel = isEdit ? typedSkills[targetSkill].label : ""; const currentGroup = isEdit ? typedSkills[targetSkill].group diff --git a/module/sheets/unnatural-sheet.js b/module/sheets/unnatural-sheet.js index adddb0a..307298f 100644 --- a/module/sheets/unnatural-sheet.js +++ b/module/sheets/unnatural-sheet.js @@ -1,4 +1,4 @@ -import { flagUnnaturalResourceMaxUpdates } from "../utils/actor-submit.js"; +import flagUnnaturalResourceMaxUpdates from "../utils/actor-submit.js"; import DGNPCSheet from "./npc-sheet.js"; /** @extends {DGNPCSheet} */ diff --git a/module/utils/actor-submit.js b/module/utils/actor-submit.js index dc9eb7a..71d9da9 100644 --- a/module/utils/actor-submit.js +++ b/module/utils/actor-submit.js @@ -3,7 +3,7 @@ * @param {Actor} actor * @returns {void} */ -export function flagUnnaturalResourceMaxUpdates(submittedData, actor) { +export default function flagUnnaturalResourceMaxUpdates(submittedData, actor) { const sourceStats = actor._source?.system?.statistics; submittedData.system.wp.maxNeedsUpdate = diff --git a/module/utils/register-helpers.js b/module/utils/register-helpers.js index 7e91c7e..807761d 100644 --- a/module/utils/register-helpers.js +++ b/module/utils/register-helpers.js @@ -44,9 +44,9 @@ export default function registerHandlebarsHelpers() { Handlebars.registerHelper("getAvailableRollModes", () => { try { return Object.fromEntries( - ROLL_MESSAGE_MODE_KEYS.filter((key) => key in CONFIG.ChatMessage.modes).map( - (key) => [key, CONFIG.ChatMessage.modes[key]], - ), + ROLL_MESSAGE_MODE_KEYS.filter( + (key) => key in CONFIG.ChatMessage.modes, + ).map((key) => [key, CONFIG.ChatMessage.modes[key]]), ); } catch (error) { return console.log(error); diff --git a/module/utils/rich-text.js b/module/utils/rich-text.js index e90234c..a9343de 100644 --- a/module/utils/rich-text.js +++ b/module/utils/rich-text.js @@ -7,7 +7,11 @@ const { HTMLField } = foundry.data.fields; * @param {object} [options] Additional options passed to DataField#toInput * @returns {Promise} */ -export async function prepareProseMirrorInput(document, fieldPath, options = {}) { +export async function prepareProseMirrorInput( + document, + fieldPath, + options = {}, +) { const field = document.system.schema.getField(fieldPath); if (!(field instanceof HTMLField)) { throw new Error( diff --git a/module/utils/roll-target-tooltip.js b/module/utils/roll-target-tooltip.js index cb83828..4dfd37e 100644 --- a/module/utils/roll-target-tooltip.js +++ b/module/utils/roll-target-tooltip.js @@ -27,7 +27,7 @@ function getRollTargetEffectLabel(effect) { if (effect.name?.trim()) return effect.name; - const parent = effect.parent; + const { parent } = effect; if (parent?.documentName === "Item") return parent.name ?? ""; return effect.name ?? ""; @@ -39,27 +39,30 @@ function getRollTargetEffectLabel(effect) { * @returns {Array<{ name: string, modifier: number }>} */ export function collectRollTargetContributions(actor, rollTargetFieldKey) { - if (actor.type !== "agent" || !ROLL_TARGET_FIELD_KEYS.includes(rollTargetFieldKey)) { + if ( + actor.type !== "agent" || + !ROLL_TARGET_FIELD_KEYS.includes(rollTargetFieldKey) + ) { return []; } const contributions = []; for (const effect of actor.appliedEffects ?? []) { - if (!isRollTargetEffectActive(effect)) continue; - - const change = (effect.changes ?? []).find( - (entry) => entry.key === rollTargetFieldKey && entry.type === "add", - ); - if (!change) continue; - - const modifier = Number(change.value); - if (!Number.isFinite(modifier) || modifier === 0) continue; - - contributions.push({ - name: getRollTargetEffectLabel(effect), - modifier, - }); + if (isRollTargetEffectActive(effect)) { + const change = (effect.changes ?? []).find( + (entry) => entry.key === rollTargetFieldKey && entry.type === "add", + ); + if (change) { + const modifier = Number(change.value); + if (Number.isFinite(modifier) && modifier !== 0) { + contributions.push({ + name: getRollTargetEffectLabel(effect), + modifier, + }); + } + } + } } return contributions; @@ -119,7 +122,9 @@ function buildRollTargetBreakdownTable( const body = rows .map( ([label, value]) => - `${foundry.utils.escapeHTML(label)}${foundry.utils.escapeHTML(value)}`, + `${foundry.utils.escapeHTML( + label, + )}${foundry.utils.escapeHTML(value)}`, ) .join(""); @@ -181,7 +186,7 @@ export function appendRollTargetTooltipSection( export function prepareAgentStatSanityTooltips(actor) { if (actor.type !== "agent") return; - const system = actor.system; + const { system } = actor; for (const [key, stat] of Object.entries(system.statistics ?? {})) { const base = Number(stat.x5) || 0; @@ -195,9 +200,11 @@ export function prepareAgentStatSanityTooltips(actor) { ); } - const sanity = system.sanity; + const { sanity } = system; if (sanity) { - const existing = `${game.i18n.localize("DG.Tooltip.CurrentSanityPartOne")}${sanity.currentBreakingPoint}${game.i18n.localize("DG.Tooltip.CurrentSanityPartTwo")}`; + const existing = `${game.i18n.localize("DG.Tooltip.CurrentSanityPartOne")}${ + sanity.currentBreakingPoint + }${game.i18n.localize("DG.Tooltip.CurrentSanityPartTwo")}`; const base = Number(sanity.value) || 0; sanity.tooltip = appendRollTargetTooltipSection( existing, diff --git a/module/utils/world-migration.js b/module/utils/world-migration.js index 9362813..19b5a1d 100644 --- a/module/utils/world-migration.js +++ b/module/utils/world-migration.js @@ -7,43 +7,6 @@ const OBSOLETE_WORLD_SETTINGS = [ "characterSheetBackgroundImageSetting", ]; -/** - * Run one-time world migrations for the Delta Green system. - * - * @returns {Promise} - */ -export async function runWorldMigration() { - if (!game.user.isGM) return; - - const currentVersion = - game.settings.get(DG.ID, "schemaMigrationVersion") ?? 0; - if (currentVersion >= MIGRATION_VERSION) return; - - let migratedActors = 0; - - const actors = game.actors.filter((actor) => - ACTOR_TYPES_WITH_SKILLS.includes(actor.type), - ); - - for (const actor of actors) { - if (!actor.system.skills?.ritual) continue; - - await actor.update({ - "system.skills.-=ritual": null, - "system.schemaVersion": MIGRATION_VERSION, - }); - migratedActors += 1; - } - - removeObsoleteWorldSettings(); - - await game.settings.set(DG.ID, "schemaMigrationVersion", MIGRATION_VERSION); - - console.log( - `Delta Green | World migration v${MIGRATION_VERSION} complete. Removed legacy ritual skill from ${migratedActors} actor(s).`, - ); -} - /** * @returns {void} */ @@ -56,3 +19,40 @@ function removeObsoleteWorldSettings() { delete dgConfig[key]; } } + +/** + * Run one-time world migrations for the Delta Green system. + * + * @returns {Promise} + */ +export default async function runWorldMigration() { + if (!game.user.isGM) return; + + const currentVersion = + game.settings.get(DG.ID, "schemaMigrationVersion") ?? 0; + if (currentVersion >= MIGRATION_VERSION) return; + + let migratedActors = 0; + + const actors = game.actors.filter((actor) => + ACTOR_TYPES_WITH_SKILLS.includes(actor.type), + ); + + for (const actor of actors) { + if (actor.system.skills?.ritual) { + await actor.update({ + "system.skills.-=ritual": null, + "system.schemaVersion": MIGRATION_VERSION, + }); + migratedActors += 1; + } + } + + removeObsoleteWorldSettings(); + + await game.settings.set(DG.ID, "schemaMigrationVersion", MIGRATION_VERSION); + + console.log( + `Delta Green | World migration v${MIGRATION_VERSION} complete. Removed legacy ritual skill from ${migratedActors} actor(s).`, + ); +} diff --git a/scss/deltagreen/_agent-sheet.scss b/scss/deltagreen/_agent-sheet.scss index 9654e15..e09958a 100644 --- a/scss/deltagreen/_agent-sheet.scss +++ b/scss/deltagreen/_agent-sheet.scss @@ -793,7 +793,10 @@ .tab.combat .items-list .item-header, .tab.combat .items-list .item:not(.item-header) { display: grid; - grid-template-columns: 24px minmax(0, 1fr) minmax(4rem, auto) minmax(3rem, auto) 86px; + grid-template-columns: 24px minmax(0, 1fr) minmax(4rem, auto) minmax( + 3rem, + auto + ) 86px; align-items: center; column-gap: 5px; } @@ -807,7 +810,10 @@ } .tab.combat .items-list .item-header .item-image, - .tab.combat .items-list .item-header > span:not(.centered-item-property):not(.item-controls) { + .tab.combat + .items-list + .item-header + > span:not(.centered-item-property):not(.item-controls) { min-width: 0; } @@ -858,10 +864,9 @@ } .tab.personal .bio-grid-5col { - grid-template-columns: minmax(4rem, max-content) minmax(0, 1fr) 20px minmax( - 4rem, - max-content - ) minmax(0, 1fr); + grid-template-columns: + minmax(4rem, max-content) minmax(0, 1fr) 20px minmax(4rem, max-content) + minmax(0, 1fr); } .tab.personal .notes-wrapper { diff --git a/scss/deltagreen/_profession.scss b/scss/deltagreen/_profession.scss index 093a428..e9a2863 100644 --- a/scss/deltagreen/_profession.scss +++ b/scss/deltagreen/_profession.scss @@ -14,7 +14,10 @@ padding: 0 0.5rem 0.5rem; } -.deltagreen .tab.profession-skills .profession-skills-section + .profession-skills-section { +.deltagreen + .tab.profession-skills + .profession-skills-section + + .profession-skills-section { margin-top: 1rem; padding-top: 0.75rem; border-top: 1px solid var(--color-border-light-2, rgba(0, 0, 0, 0.15)); diff --git a/system.json b/system.json index 1383701..07d6792 100644 --- a/system.json +++ b/system.json @@ -28,7 +28,11 @@ } }, "esmodules": ["module/deltagreen.js"], - "styles": ["css/deltagreen.css", "css/dg-dialog.css", "css/damage-modify.css"], + "styles": [ + "css/deltagreen.css", + "css/dg-dialog.css", + "css/damage-modify.css" + ], "languages": [ { "lang": "en",