Actor sheet -> working !!!
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+52
-51
@@ -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);
|
||||
|
||||
@@ -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 })
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user