Actor sheet -> working !!!

This commit is contained in:
2026-06-06 15:48:18 +02:00
parent 9b77a0c552
commit 891769816a
13 changed files with 129 additions and 69 deletions
+14 -7
View File
@@ -24,15 +24,24 @@
.resource-label { .resource-label {
font-size: 0.75rem; 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); text-shadow: 0 0 3px rgba(0, 0, 0, 0.9);
} }
.resource { .resource {
border: none; border: 1px solid @color-border-dark-3;
border-left: 1px solid gray; border-left: 3px solid @theme-color-primary;
padding: 0.2rem 1rem; background: rgba(0, 0, 0, 0.1);
padding: 0.5rem 1rem;
text-align: center; text-align: center;
.transition();
&:hover {
background: rgba(0, 0, 0, 0.2);
border-color: @theme-color-primary;
}
.flexrow { .flexrow {
min-width: 5rem; min-width: 5rem;
@@ -56,13 +65,11 @@
} }
select { select {
background: rgba(0, 0, 0, 0.4); .custom-select-style();
color: @color-text-light-1; color: @color-text-light-1;
font-family: "DistressBlack", sans-serif; font-family: "DistressBlack", sans-serif;
font-size: 0.875rem; font-size: 0.875rem;
text-align: center; 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); text-shadow: 0 0 3px rgba(0, 0, 0, 0.9);
} }
@@ -64,6 +64,35 @@ export class VermineBaseActorSheet extends HandlebarsApplicationMixin(foundry.ap
_canDragStart() { return this.isEditable } _canDragStart() { return this.isEditable }
_canDragDrop() { 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 ───────────────────────────────────────────────── // ── Contexte commun ─────────────────────────────────────────────────
async _prepareContext() { async _prepareContext() {
@@ -12,6 +12,7 @@ export class VermineCharacterSheetV2 extends VermineBaseActorSheet {
} }
static PARTS = { static PARTS = {
header: { template: "systems/vermine2047/templates/actor/appv2/character-header.hbs" },
main: { template: "systems/vermine2047/templates/actor/appv2/character-main.hbs" }, main: { template: "systems/vermine2047/templates/actor/appv2/character-main.hbs" },
tabs: { template: "templates/generic/tab-navigation.hbs" }, tabs: { template: "templates/generic/tab-navigation.hbs" },
abilities: { template: "systems/vermine2047/templates/actor/appv2/character-abilities.hbs" }, abilities: { template: "systems/vermine2047/templates/actor/appv2/character-abilities.hbs" },
@@ -39,6 +39,14 @@ export class VermineCreatureSheetV2 extends VermineBaseActorSheet {
return context 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) { async _preparePartContext(partId, context) {
const doc = this.document const doc = this.document
switch (partId) { switch (partId) {
+8 -1
View File
@@ -7,7 +7,6 @@ export class VermineGroupSheetV2 extends VermineBaseActorSheet {
position: { width: 700, height: 600 }, position: { width: 700, height: 600 },
window: { contentClasses: ["group-content"] }, window: { contentClasses: ["group-content"] },
actions: { actions: {
chooseTotem: VermineGroupSheetV2.#onChooseTotem,
chooseActor: VermineGroupSheetV2.#onChooseActor, chooseActor: VermineGroupSheetV2.#onChooseActor,
deleteMember: VermineGroupSheetV2.#onDeleteMember, deleteMember: VermineGroupSheetV2.#onDeleteMember,
deleteEncounter: VermineGroupSheetV2.#onDeleteEncounter, deleteEncounter: VermineGroupSheetV2.#onDeleteEncounter,
@@ -62,6 +61,14 @@ export class VermineGroupSheetV2 extends VermineBaseActorSheet {
return context 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) { async _preparePartContext(partId, context) {
const doc = this.document const doc = this.document
switch (partId) { switch (partId) {
+52 -51
View File
@@ -15,6 +15,7 @@ export default class VermineActor extends Actor {
* is queried and has a roll executed directly from it). * is queried and has a roll executed directly from it).
*/ */
prepareDerivedData() { prepareDerivedData() {
super.prepareDerivedData();
const actorData = this; const actorData = this;
const systemData = actorData.system; const systemData = actorData.system;
const flags = actorData.flags.vermine2047 || {}; const flags = actorData.flags.vermine2047 || {};
@@ -64,30 +65,30 @@ export default class VermineActor extends Actor {
this.system.combatStatus = { difficulty: "9", label: "Passif" }; this.system.combatStatus = { difficulty: "9", label: "Passif" };
return; return;
} }
// Ensure difficulty exists // Ensure difficulty exists
if (!this.system.combatStatus.difficulty) { if (!this.system.combatStatus.difficulty) {
this.system.combatStatus.difficulty = "9"; this.system.combatStatus.difficulty = "9";
} }
//combat initiative reaction difficulty //combat initiative reaction difficulty
const difficulty = parseInt(this.system.combatStatus.difficulty) || 9; const difficulty = parseInt(this.system.combatStatus.difficulty) || 9;
// Only update if values are different to avoid triggering unnecessary updates // Only update if values are different to avoid triggering unnecessary updates
const currentLabel = this.system.combatStatus.label; const currentLabel = this.system.combatStatus.label;
let newLabel = "Passif"; let newLabel = "Passif";
switch (difficulty) { switch (difficulty) {
case 5: newLabel = "Offensif"; break; case 5: newLabel = "Offensif"; break;
case 7: newLabel = "Actif"; break; case 7: newLabel = "Actif"; break;
case 9: newLabel = "Passif"; break; case 9: newLabel = "Passif"; break;
} }
// Only update if label changed // Only update if label changed
if (currentLabel !== newLabel) { if (currentLabel !== newLabel) {
this.system.combatStatus.label = newLabel; this.system.combatStatus.label = newLabel;
} }
// Only update difficulty if it was undefined or invalid // Only update difficulty if it was undefined or invalid
if (!this.system.combatStatus.difficulty || isNaN(parseInt(this.system.combatStatus.difficulty))) { if (!this.system.combatStatus.difficulty || isNaN(parseInt(this.system.combatStatus.difficulty))) {
this.system.combatStatus.difficulty = "9"; this.system.combatStatus.difficulty = "9";
@@ -102,15 +103,15 @@ export default class VermineActor extends Actor {
// Make modifications to data here. For example: // Make modifications to data here. For example:
const systemData = actorData.system; const systemData = actorData.system;
// Set wound thresholds based on threat level // Set wound thresholds based on threat level
this._setNpcThresholds(); this._setNpcThresholds();
// Set reserve max values based on role // Set reserve max values based on role
this._setNpcAttributes(); this._setNpcAttributes();
this.prepareCombatStatus(); this.prepareCombatStatus();
// Prepare abilities with labels // Prepare abilities with labels
for (let [k, v] of Object.entries(systemData.abilities)) { for (let [k, v] of Object.entries(systemData.abilities)) {
v.label = game.i18n.localize(CONFIG.VERMINE.abilities[k]) ?? k; v.label = game.i18n.localize(CONFIG.VERMINE.abilities[k]) ?? k;
@@ -153,15 +154,15 @@ export default class VermineActor extends Actor {
*/ */
_prepareGroupData(actorData) { _prepareGroupData(actorData) {
if (actorData.type !== 'group') return; if (actorData.type !== 'group') return;
this.prepareCombatStatus(); this.prepareCombatStatus();
// Initialize group-specific data if not present // Initialize group-specific data if not present
this._initGroupData(); this._initGroupData();
// Calculate reserve max based on group level // Calculate reserve max based on group level
this._calculateGroupReserve(); this._calculateGroupReserve();
// Update morale level based on dice value // Update morale level based on dice value
this._updateGroupMorale(); this._updateGroupMorale();
} }
@@ -171,19 +172,19 @@ export default class VermineActor extends Actor {
*/ */
_initGroupData() { _initGroupData() {
if (this.type !== 'group') return; if (this.type !== 'group') return;
const system = this.system; const system = this.system;
// Initialize objectives if not present // Initialize objectives if not present
if (!system.objectives) { if (!system.objectives) {
system.objectives = { major: [], minor: [] }; system.objectives = { major: [], minor: [] };
} }
// Initialize groupAbilities if not present // Initialize groupAbilities if not present
if (!system.groupAbilities) { if (!system.groupAbilities) {
system.groupAbilities = []; system.groupAbilities = [];
} }
// Initialize reserve if not present // Initialize reserve if not present
if (!system.reserve) { if (!system.reserve) {
system.reserve = { value: 0, min: 0, max: 10 }; system.reserve = { value: 0, min: 0, max: 10 };
@@ -196,12 +197,12 @@ export default class VermineActor extends Actor {
*/ */
_calculateGroupReserve() { _calculateGroupReserve() {
if (this.type !== 'group') return; if (this.type !== 'group') return;
const level = this.system.level?.value || 1; const level = this.system.level?.value || 1;
// Reserve max is based on group level (simplified: level * 1D for now) // Reserve max is based on group level (simplified: level * 1D for now)
// Can be customized based on specific rules // Can be customized based on specific rules
this.system.reserve.max = Math.min(10, level * 2); this.system.reserve.max = Math.min(10, level * 2);
// Ensure value doesn't exceed max // Ensure value doesn't exceed max
if (this.system.reserve.value > this.system.reserve.max) { if (this.system.reserve.value > this.system.reserve.max) {
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() { _updateGroupMorale() {
if (this.type !== 'group') return; if (this.type !== 'group') return;
const moraleValue = this.system.morale?.value || 0; const moraleValue = this.system.morale?.value || 0;
const moraleLevel = this.system.morale?.level; const moraleLevel = this.system.morale?.level;
// If level is already explicitly set, keep it // If level is already explicitly set, keep it
if (moraleLevel && moraleLevel !== "high") return; if (moraleLevel && moraleLevel !== "high") return;
// Determine morale level based on dice value // Determine morale level based on dice value
if (moraleValue >= 7) { if (moraleValue >= 7) {
this.system.morale.level = "high"; this.system.morale.level = "high";
@@ -239,12 +240,12 @@ export default class VermineActor extends Actor {
*/ */
_prepareCreatureData(actorData) { _prepareCreatureData(actorData) {
if (actorData.type !== 'creature') return; if (actorData.type !== 'creature') return;
this.prepareCombatStatus(); this.prepareCombatStatus();
// Calculate computed values from pattern, size, role, and pack // Calculate computed values from pattern, size, role, and pack
this._calculateCreatureComputedValues(); this._calculateCreatureComputedValues();
// Set wound thresholds from creature characteristics // Set wound thresholds from creature characteristics
this._calculateCreatureWoundThresholds(); this._calculateCreatureWoundThresholds();
} }
@@ -257,46 +258,46 @@ export default class VermineActor extends Actor {
*/ */
_calculateCreatureComputedValues() { _calculateCreatureComputedValues() {
if (this.type !== 'creature') return; if (this.type !== 'creature') return;
const patternLevel = this.system.pattern?.value || 1; const patternLevel = this.system.pattern?.value || 1;
const sizeLevel = this.system.size?.value || 1; const sizeLevel = this.system.size?.value || 1;
const roleLevel = this.system.role?.value || 1; const roleLevel = this.system.role?.value || 1;
const packLevel = this.system.pack?.value || 0; const packLevel = this.system.pack?.value || 0;
// Get config values // Get config values
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}; const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {};
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}; const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {};
const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel] || {}; const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel] || {};
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}; const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {};
// Calculate computed values // Calculate computed values
this.system.computed = this.system.computed || {}; this.system.computed = this.system.computed || {};
// Attack: pattern + size + pack + role.reaction // Attack: pattern + size + pack + role.reaction
this.system.computed.attack = (patternConfig.attack || 0) + this.system.computed.attack = (patternConfig.attack || 0) +
(sizeConfig.attack || 0) + (sizeConfig.attack || 0) +
(packConfig.attack || 0) + (packConfig.attack || 0) +
(roleConfig.reaction || 0); (roleConfig.reaction || 0);
// Damage: pattern + size.vigor + pack // Damage: pattern + size.vigor + pack
this.system.computed.damage = (patternConfig.damage || 0) + this.system.computed.damage = (patternConfig.damage || 0) +
(sizeConfig.vigor || 0) + (sizeConfig.vigor || 0) +
(packConfig.damage || 0); (packConfig.damage || 0);
// Vigor: size.vigor + pack.damage // Vigor: size.vigor + pack.damage
this.system.computed.vigor = (sizeConfig.vigor || 0) + (packConfig.damage || 0); this.system.computed.vigor = (sizeConfig.vigor || 0) + (packConfig.damage || 0);
// Reaction: role.reaction + role.reaction_bonus // Reaction: role.reaction + role.reaction_bonus
this.system.computed.reaction = (roleConfig.reaction || 0) + (roleConfig.reaction_bonus || 0); this.system.computed.reaction = (roleConfig.reaction || 0) + (roleConfig.reaction_bonus || 0);
this.system.computed.reactionBonus = roleConfig.reaction_bonus || 0; this.system.computed.reactionBonus = roleConfig.reaction_bonus || 0;
// Pools (reserves) // Pools (reserves)
this.system.computed.pools = roleConfig.pools || 0; this.system.computed.pools = roleConfig.pools || 0;
// Gear and hindrance // Gear and hindrance
this.system.computed.gear = roleConfig.gear || 9; this.system.computed.gear = roleConfig.gear || 9;
this.system.computed.gearHindrance = roleConfig.gear_hindrance || 0; this.system.computed.gearHindrance = roleConfig.gear_hindrance || 0;
// Protection // Protection
this.system.computed.protection = roleConfig.protection || 1; this.system.computed.protection = roleConfig.protection || 1;
} }
@@ -307,26 +308,26 @@ export default class VermineActor extends Actor {
*/ */
_calculateCreatureWoundThresholds() { _calculateCreatureWoundThresholds() {
if (this.type !== 'creature') return; if (this.type !== 'creature') return;
const patternLevel = this.system.pattern?.value || 1; const patternLevel = this.system.pattern?.value || 1;
const sizeLevel = this.system.size?.value || 1; const sizeLevel = this.system.size?.value || 1;
const packLevel = this.system.pack?.value || 0; const packLevel = this.system.pack?.value || 0;
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}; const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {};
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}; const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {};
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}; const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {};
// Calculate wound thresholds (sum of all sources) // Calculate wound thresholds (sum of all sources)
this.system.minorWound.threshold = (patternConfig.minorWound || 0) + this.system.minorWound.threshold = (patternConfig.minorWound || 0) +
(sizeConfig.minorWound || 0) + (sizeConfig.minorWound || 0) +
(packConfig.minorWound || 0); (packConfig.minorWound || 0);
this.system.majorWound.threshold = (patternConfig.majorWound || 0) + this.system.majorWound.threshold = (patternConfig.majorWound || 0) +
(sizeConfig.majorWound || 0) + (sizeConfig.majorWound || 0) +
(packConfig.majorWound || 0); (packConfig.majorWound || 0);
this.system.deadlyWound.threshold = (patternConfig.deadlyWound || 0) + this.system.deadlyWound.threshold = (patternConfig.deadlyWound || 0) +
(sizeConfig.deadlyWound || 0) + (sizeConfig.deadlyWound || 0) +
(packConfig.deadlyWound || 0); (packConfig.deadlyWound || 0);
// Set max wounds // Set max wounds
this.system.minorWound.max = Math.min(5, this.system.minorWound.threshold + 2); this.system.minorWound.max = Math.min(5, this.system.minorWound.threshold + 2);
this.system.majorWound.max = Math.min(4, this.system.majorWound.threshold + 1); this.system.majorWound.max = Math.min(4, this.system.majorWound.threshold + 1);
+13 -1
View File
@@ -3,6 +3,18 @@
* Fonctions factory retournant des objets SchemaField réutilisables. * 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) * Retourne un schema pour une blessure (minor/major/deadly)
* @param {number} defaultThreshold * @param {number} defaultThreshold
@@ -63,7 +75,7 @@ export function attributeSchema(defaultVal = 0, defaultMin = 0, defaultMax = 10)
const fields = foundry.data.fields const fields = foundry.data.fields
const reqInt = { required: true, nullable: false, integer: true } const reqInt = { required: true, nullable: false, integer: true }
return new fields.SchemaField({ 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 }), min: new fields.NumberField({ ...reqInt, initial: defaultMin, min: 0 }),
max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 }) max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 })
}) })
@@ -1,5 +1,4 @@
<div class="tab abilities sheet-part" data-group="sheet" data-tab="abilities"> <div class="tab abilities sheet-part" data-group="sheet" data-tab="abilities">
{{> "systems/vermine2047/templates/actor/appv2/character-header.hbs"}}
{{!-- Character --}} {{!-- Character --}}
<h3>{{ localize 'VERMINE.abilities' }}</h3> <h3>{{ localize 'VERMINE.abilities' }}</h3>
<div class="grid grid-4col"> <div class="grid grid-4col">
@@ -1,5 +1,4 @@
<div class="tab combat sheet-part" data-group="sheet" data-tab="combat"> <div class="tab combat sheet-part" data-group="sheet" data-tab="combat">
{{> "systems/vermine2047/templates/actor/appv2/character-header.hbs"}}
<div class="align-center"> <div class="align-center">
<div class="shadow"> <div class="shadow">
<h4>situation de combat par défaut</h4> <h4>situation de combat par défaut</h4>
@@ -1,5 +1,4 @@
<div class="tab equipment sheet-part" data-group="sheet" data-tab="equipment"> <div class="tab equipment sheet-part" data-group="sheet" data-tab="equipment">
{{> "systems/vermine2047/templates/actor/appv2/character-header.hbs"}}
<div class="grid grid-2col"> <div class="grid grid-2col">
<div> <div>
<ol class="items-list"> <ol class="items-list">
+4 -4
View File
@@ -44,16 +44,16 @@
<div class="reputation flexrow flex-group-center"> <div class="reputation flexrow flex-group-center">
<label>{{ localize 'VERMINE.reputation' }}</label> <label>{{ localize 'VERMINE.reputation' }}</label>
{{#if isEditMode}} {{#if isEditMode}}
<input name="system.attributes.reputation.value" type="number" min="0" <input name="system.attributes.reputation.value" data-type="Number" type="number"
value="{{#if (or (not system.attributes.reputation.value) (eq system.attributes.reputation.value 0))}}0{{else}}{{system.attributes.reputation.value}}{{/if}}" /> value="{{system.attributes.reputation.value}}" />
{{else}} {{else}}
<span>{{system.attributes.reputation.value}}</span> <span>{{system.attributes.reputation.value}}</span>
{{/if}} {{/if}}
<label>{{ localize 'VERMINE.experience' }}</label> <label>{{ localize 'VERMINE.experience' }}</label>
{{#if isEditMode}} {{#if isEditMode}}
<input name="system.attributes.xp.value" type="number" min="0" <input name="system.attributes.xp.value" data-type="Number" type="number"
value="{{#if (or (not system.attributes.xp.value) (eq system.attributes.xp.value 0))}}0{{else}}{{system.attributes.xp.value}}{{/if}}" /> value="{{system.attributes.xp.value}}" />
{{else}} {{else}}
<span>{{system.attributes.xp.value}}</span> <span>{{system.attributes.xp.value}}</span>
{{/if}} {{/if}}
@@ -1,5 +1,4 @@
<div class="tab stories sheet-part" data-group="sheet" data-tab="stories"> <div class="tab stories sheet-part" data-group="sheet" data-tab="stories">
{{> "systems/vermine2047/templates/actor/appv2/character-header.hbs"}}
<section class="flexrow flex-group-left flex-align-left gap-md"> <section class="flexrow flex-group-left flex-align-left gap-md">
<div> <div>
<h4>{{ localize 'IDENTITY.theme'}}</h4> <h4>{{ localize 'IDENTITY.theme'}}</h4>
@@ -1,5 +1,4 @@
<div class="tab totem sheet-part" data-group="sheet" data-tab="totem"> <div class="tab totem sheet-part" data-group="sheet" data-tab="totem">
{{> "systems/vermine2047/templates/actor/appv2/character-header.hbs"}}
{{#if system.identity.totem}} {{#if system.identity.totem}}
<div class="totem-details"> <div class="totem-details">
<img <img