From 891769816acda75b27deb3f6bc6c115b14072ad6 Mon Sep 17 00:00:00 2001 From: LeRatierBretonnier Date: Sat, 6 Jun 2026 15:48:18 +0200 Subject: [PATCH] Actor sheet -> working !!! --- less/items.less | 21 ++-- .../applications/sheets/base-actor-sheet.mjs | 29 +++++ .../applications/sheets/character-sheet.mjs | 1 + module/applications/sheets/creature-sheet.mjs | 8 ++ module/applications/sheets/group-sheet.mjs | 9 +- module/documents/actor.mjs | 103 +++++++++--------- module/models/_shared.mjs | 14 ++- templates/actor/appv2/character-abilities.hbs | 1 - templates/actor/appv2/character-combat.hbs | 1 - templates/actor/appv2/character-equipment.hbs | 1 - templates/actor/appv2/character-header.hbs | 8 +- templates/actor/appv2/character-stories.hbs | 1 - templates/actor/appv2/character-totem.hbs | 1 - 13 files changed, 129 insertions(+), 69 deletions(-) diff --git a/less/items.less b/less/items.less index 8c506e6..bb24167 100644 --- a/less/items.less +++ b/less/items.less @@ -24,15 +24,24 @@ .resource-label { font-size: 0.75rem; - color: @color-text-light-2; + color: @color-text-light-highlight; + text-transform: uppercase; + font-weight: bold; text-shadow: 0 0 3px rgba(0, 0, 0, 0.9); } .resource { - border: none; - border-left: 1px solid gray; - padding: 0.2rem 1rem; + border: 1px solid @color-border-dark-3; + border-left: 3px solid @theme-color-primary; + background: rgba(0, 0, 0, 0.1); + padding: 0.5rem 1rem; text-align: center; + .transition(); + + &:hover { + background: rgba(0, 0, 0, 0.2); + border-color: @theme-color-primary; + } .flexrow { min-width: 5rem; @@ -56,13 +65,11 @@ } select { - background: rgba(0, 0, 0, 0.4); + .custom-select-style(); color: @color-text-light-1; font-family: "DistressBlack", sans-serif; font-size: 0.875rem; text-align: center; - border: none; - box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.979) inset; text-shadow: 0 0 3px rgba(0, 0, 0, 0.9); } diff --git a/module/applications/sheets/base-actor-sheet.mjs b/module/applications/sheets/base-actor-sheet.mjs index 28b5b26..7b44ade 100644 --- a/module/applications/sheets/base-actor-sheet.mjs +++ b/module/applications/sheets/base-actor-sheet.mjs @@ -64,6 +64,35 @@ export class VermineBaseActorSheet extends HandlebarsApplicationMixin(foundry.ap _canDragStart() { return this.isEditable } _canDragDrop() { return this.isEditable } + // ── Soumission du formulaire ──────────────────────────────────────── + + /** @override - coerce string values from HTML form inputs to numbers */ + _prepareSubmitData(submitData, form, formData) { + const fd = foundry.utils.deepClone(formData.object) + + for (const [key, value] of Object.entries(fd)) { + if (!key.startsWith("system.") || typeof value === "number") continue + + const segments = key.slice(7).split(".") + let node = this.document.system.schema + for (const seg of segments) { + if (node instanceof foundry.data.fields.SchemaField) node = node.fields[seg] + else { node = undefined; break } + } + if (!(node instanceof foundry.data.fields.NumberField)) continue + + // Handle arrays from duplicate-named form inputs + let raw = Array.isArray(value) ? value.filter(v => v !== "" && v !== null).pop() : value + if (raw === undefined) continue + if (typeof raw === "string" && raw.trim() === "") { fd[key] = 0; continue } + + const num = Number(typeof raw === "string" ? raw.trim() : raw) + if (!isNaN(num)) fd[key] = num + } + + return fd + } + // ── Contexte commun ───────────────────────────────────────────────── async _prepareContext() { diff --git a/module/applications/sheets/character-sheet.mjs b/module/applications/sheets/character-sheet.mjs index b24bee6..b07cfd8 100644 --- a/module/applications/sheets/character-sheet.mjs +++ b/module/applications/sheets/character-sheet.mjs @@ -12,6 +12,7 @@ export class VermineCharacterSheetV2 extends VermineBaseActorSheet { } static PARTS = { + header: { template: "systems/vermine2047/templates/actor/appv2/character-header.hbs" }, main: { template: "systems/vermine2047/templates/actor/appv2/character-main.hbs" }, tabs: { template: "templates/generic/tab-navigation.hbs" }, abilities: { template: "systems/vermine2047/templates/actor/appv2/character-abilities.hbs" }, diff --git a/module/applications/sheets/creature-sheet.mjs b/module/applications/sheets/creature-sheet.mjs index dcea06d..ce27a30 100644 --- a/module/applications/sheets/creature-sheet.mjs +++ b/module/applications/sheets/creature-sheet.mjs @@ -39,6 +39,14 @@ export class VermineCreatureSheetV2 extends VermineBaseActorSheet { return context } + changeTab(tab, group, options = {}) { + super.changeTab(tab, group, options) + if (group === "sheet") { + const main = this.element?.querySelector('[data-group="sheet"][data-tab="main"]') + if (main) main.classList.add("active") + } + } + async _preparePartContext(partId, context) { const doc = this.document switch (partId) { diff --git a/module/applications/sheets/group-sheet.mjs b/module/applications/sheets/group-sheet.mjs index 477d267..a570f54 100644 --- a/module/applications/sheets/group-sheet.mjs +++ b/module/applications/sheets/group-sheet.mjs @@ -7,7 +7,6 @@ export class VermineGroupSheetV2 extends VermineBaseActorSheet { position: { width: 700, height: 600 }, window: { contentClasses: ["group-content"] }, actions: { - chooseTotem: VermineGroupSheetV2.#onChooseTotem, chooseActor: VermineGroupSheetV2.#onChooseActor, deleteMember: VermineGroupSheetV2.#onDeleteMember, deleteEncounter: VermineGroupSheetV2.#onDeleteEncounter, @@ -62,6 +61,14 @@ export class VermineGroupSheetV2 extends VermineBaseActorSheet { return context } + changeTab(tab, group, options = {}) { + super.changeTab(tab, group, options) + if (group === "sheet") { + const main = this.element?.querySelector('[data-group="sheet"][data-tab="main"]') + if (main) main.classList.add("active") + } + } + async _preparePartContext(partId, context) { const doc = this.document switch (partId) { diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 9e3789d..2c28893 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -15,6 +15,7 @@ export default class VermineActor extends Actor { * is queried and has a roll executed directly from it). */ prepareDerivedData() { + super.prepareDerivedData(); const actorData = this; const systemData = actorData.system; const flags = actorData.flags.vermine2047 || {}; @@ -64,30 +65,30 @@ export default class VermineActor extends Actor { this.system.combatStatus = { difficulty: "9", label: "Passif" }; return; } - + // Ensure difficulty exists if (!this.system.combatStatus.difficulty) { this.system.combatStatus.difficulty = "9"; } - + //combat initiative reaction difficulty const difficulty = parseInt(this.system.combatStatus.difficulty) || 9; - + // Only update if values are different to avoid triggering unnecessary updates const currentLabel = this.system.combatStatus.label; let newLabel = "Passif"; - + switch (difficulty) { case 5: newLabel = "Offensif"; break; case 7: newLabel = "Actif"; break; case 9: newLabel = "Passif"; break; } - + // Only update if label changed if (currentLabel !== newLabel) { this.system.combatStatus.label = newLabel; } - + // Only update difficulty if it was undefined or invalid if (!this.system.combatStatus.difficulty || isNaN(parseInt(this.system.combatStatus.difficulty))) { this.system.combatStatus.difficulty = "9"; @@ -102,15 +103,15 @@ export default class VermineActor extends Actor { // Make modifications to data here. For example: const systemData = actorData.system; - + // Set wound thresholds based on threat level this._setNpcThresholds(); - + // Set reserve max values based on role this._setNpcAttributes(); - + this.prepareCombatStatus(); - + // Prepare abilities with labels for (let [k, v] of Object.entries(systemData.abilities)) { v.label = game.i18n.localize(CONFIG.VERMINE.abilities[k]) ?? k; @@ -153,15 +154,15 @@ export default class VermineActor extends Actor { */ _prepareGroupData(actorData) { if (actorData.type !== 'group') return; - + this.prepareCombatStatus(); - + // Initialize group-specific data if not present this._initGroupData(); - + // Calculate reserve max based on group level this._calculateGroupReserve(); - + // Update morale level based on dice value this._updateGroupMorale(); } @@ -171,19 +172,19 @@ export default class VermineActor extends Actor { */ _initGroupData() { if (this.type !== 'group') return; - + const system = this.system; - + // Initialize objectives if not present if (!system.objectives) { system.objectives = { major: [], minor: [] }; } - + // Initialize groupAbilities if not present if (!system.groupAbilities) { system.groupAbilities = []; } - + // Initialize reserve if not present if (!system.reserve) { system.reserve = { value: 0, min: 0, max: 10 }; @@ -196,12 +197,12 @@ export default class VermineActor extends Actor { */ _calculateGroupReserve() { if (this.type !== 'group') return; - + const level = this.system.level?.value || 1; // Reserve max is based on group level (simplified: level * 1D for now) // Can be customized based on specific rules this.system.reserve.max = Math.min(10, level * 2); - + // Ensure value doesn't exceed max if (this.system.reserve.value > this.system.reserve.max) { this.system.reserve.value = this.system.reserve.max; @@ -214,13 +215,13 @@ export default class VermineActor extends Actor { */ _updateGroupMorale() { if (this.type !== 'group') return; - + const moraleValue = this.system.morale?.value || 0; const moraleLevel = this.system.morale?.level; - + // If level is already explicitly set, keep it if (moraleLevel && moraleLevel !== "high") return; - + // Determine morale level based on dice value if (moraleValue >= 7) { this.system.morale.level = "high"; @@ -239,12 +240,12 @@ export default class VermineActor extends Actor { */ _prepareCreatureData(actorData) { if (actorData.type !== 'creature') return; - + this.prepareCombatStatus(); - + // Calculate computed values from pattern, size, role, and pack this._calculateCreatureComputedValues(); - + // Set wound thresholds from creature characteristics this._calculateCreatureWoundThresholds(); } @@ -257,46 +258,46 @@ export default class VermineActor extends Actor { */ _calculateCreatureComputedValues() { if (this.type !== 'creature') return; - + const patternLevel = this.system.pattern?.value || 1; const sizeLevel = this.system.size?.value || 1; const roleLevel = this.system.role?.value || 1; const packLevel = this.system.pack?.value || 0; - + // Get config values const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}; const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}; const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel] || {}; const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}; - + // Calculate computed values this.system.computed = this.system.computed || {}; - + // Attack: pattern + size + pack + role.reaction - this.system.computed.attack = (patternConfig.attack || 0) + - (sizeConfig.attack || 0) + - (packConfig.attack || 0) + + this.system.computed.attack = (patternConfig.attack || 0) + + (sizeConfig.attack || 0) + + (packConfig.attack || 0) + (roleConfig.reaction || 0); - + // Damage: pattern + size.vigor + pack - this.system.computed.damage = (patternConfig.damage || 0) + - (sizeConfig.vigor || 0) + + this.system.computed.damage = (patternConfig.damage || 0) + + (sizeConfig.vigor || 0) + (packConfig.damage || 0); - + // Vigor: size.vigor + pack.damage this.system.computed.vigor = (sizeConfig.vigor || 0) + (packConfig.damage || 0); - + // Reaction: role.reaction + role.reaction_bonus this.system.computed.reaction = (roleConfig.reaction || 0) + (roleConfig.reaction_bonus || 0); this.system.computed.reactionBonus = roleConfig.reaction_bonus || 0; - + // Pools (reserves) this.system.computed.pools = roleConfig.pools || 0; - + // Gear and hindrance this.system.computed.gear = roleConfig.gear || 9; this.system.computed.gearHindrance = roleConfig.gear_hindrance || 0; - + // Protection this.system.computed.protection = roleConfig.protection || 1; } @@ -307,26 +308,26 @@ export default class VermineActor extends Actor { */ _calculateCreatureWoundThresholds() { if (this.type !== 'creature') return; - + const patternLevel = this.system.pattern?.value || 1; const sizeLevel = this.system.size?.value || 1; const packLevel = this.system.pack?.value || 0; - + const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}; const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}; const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}; - + // Calculate wound thresholds (sum of all sources) - this.system.minorWound.threshold = (patternConfig.minorWound || 0) + - (sizeConfig.minorWound || 0) + + this.system.minorWound.threshold = (patternConfig.minorWound || 0) + + (sizeConfig.minorWound || 0) + (packConfig.minorWound || 0); - this.system.majorWound.threshold = (patternConfig.majorWound || 0) + - (sizeConfig.majorWound || 0) + + this.system.majorWound.threshold = (patternConfig.majorWound || 0) + + (sizeConfig.majorWound || 0) + (packConfig.majorWound || 0); - this.system.deadlyWound.threshold = (patternConfig.deadlyWound || 0) + - (sizeConfig.deadlyWound || 0) + + this.system.deadlyWound.threshold = (patternConfig.deadlyWound || 0) + + (sizeConfig.deadlyWound || 0) + (packConfig.deadlyWound || 0); - + // Set max wounds this.system.minorWound.max = Math.min(5, this.system.minorWound.threshold + 2); this.system.majorWound.max = Math.min(4, this.system.majorWound.threshold + 1); diff --git a/module/models/_shared.mjs b/module/models/_shared.mjs index 0174010..d6c0c05 100644 --- a/module/models/_shared.mjs +++ b/module/models/_shared.mjs @@ -3,6 +3,18 @@ * Fonctions factory retournant des objets SchemaField réutilisables. */ +const fields = foundry.data.fields + +/** NumberField qui accepte les strings vides en les remplaçant par `initial`. */ +class LooseNumberField extends fields.NumberField { + clean(value, options) { + if (value === "" || value === null || value === undefined) { + return this.initial ?? 0 + } + return super.clean(value, options) + } +} + /** * Retourne un schema pour une blessure (minor/major/deadly) * @param {number} defaultThreshold @@ -63,7 +75,7 @@ export function attributeSchema(defaultVal = 0, defaultMin = 0, defaultMax = 10) const fields = foundry.data.fields const reqInt = { required: true, nullable: false, integer: true } return new fields.SchemaField({ - value: new fields.NumberField({ ...reqInt, initial: defaultVal, min: defaultMin }), + value: new LooseNumberField({ ...reqInt, initial: defaultVal, min: defaultMin }), min: new fields.NumberField({ ...reqInt, initial: defaultMin, min: 0 }), max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 }) }) diff --git a/templates/actor/appv2/character-abilities.hbs b/templates/actor/appv2/character-abilities.hbs index c272ed7..772c7a8 100644 --- a/templates/actor/appv2/character-abilities.hbs +++ b/templates/actor/appv2/character-abilities.hbs @@ -1,5 +1,4 @@
- {{> "systems/vermine2047/templates/actor/appv2/character-header.hbs"}} {{!-- Character --}}

{{ localize 'VERMINE.abilities' }}

diff --git a/templates/actor/appv2/character-combat.hbs b/templates/actor/appv2/character-combat.hbs index 6f18e10..6b862be 100644 --- a/templates/actor/appv2/character-combat.hbs +++ b/templates/actor/appv2/character-combat.hbs @@ -1,5 +1,4 @@
- {{> "systems/vermine2047/templates/actor/appv2/character-header.hbs"}}

situation de combat par défaut

diff --git a/templates/actor/appv2/character-equipment.hbs b/templates/actor/appv2/character-equipment.hbs index 9b5c8fc..43f7644 100644 --- a/templates/actor/appv2/character-equipment.hbs +++ b/templates/actor/appv2/character-equipment.hbs @@ -1,5 +1,4 @@
- {{> "systems/vermine2047/templates/actor/appv2/character-header.hbs"}}
    diff --git a/templates/actor/appv2/character-header.hbs b/templates/actor/appv2/character-header.hbs index a52cd48..865ba89 100644 --- a/templates/actor/appv2/character-header.hbs +++ b/templates/actor/appv2/character-header.hbs @@ -44,16 +44,16 @@
    {{#if isEditMode}} - + {{else}} {{system.attributes.reputation.value}} {{/if}} {{#if isEditMode}} - + {{else}} {{system.attributes.xp.value}} {{/if}} diff --git a/templates/actor/appv2/character-stories.hbs b/templates/actor/appv2/character-stories.hbs index 89bf48a..fb0a2ed 100644 --- a/templates/actor/appv2/character-stories.hbs +++ b/templates/actor/appv2/character-stories.hbs @@ -1,5 +1,4 @@
    - {{> "systems/vermine2047/templates/actor/appv2/character-header.hbs"}}

    {{ localize 'IDENTITY.theme'}}

    diff --git a/templates/actor/appv2/character-totem.hbs b/templates/actor/appv2/character-totem.hbs index f40b1f4..c140979 100644 --- a/templates/actor/appv2/character-totem.hbs +++ b/templates/actor/appv2/character-totem.hbs @@ -1,5 +1,4 @@
    - {{> "systems/vermine2047/templates/actor/appv2/character-header.hbs"}} {{#if system.identity.totem}}