Migration complète vers DataModels Foundry VTT

- Ajout de 14 DataModels (10 Items + 3 Acteurs)
  * Items: equipment, weapon, trait, specialization, maneuver, scar, annency, boheme, contact, confrontation
  * Acteurs: pc, npc, annency

- Corrections d'initialisation
  * Ordre d'initialisation corrigé (CONFIG.dataModels avant game.system)
  * Import dynamique des DataModels pour éviter timing issues
  * Helper functions pour éviter réutilisation de champs

- Documentation complète
  * AUDIT_DATAMODELS.md: Rapport d'audit complet (85+ champs vérifiés)
  * MIGRATION_DATAMODELS.md: Guide de migration
  * FIX_INIT_ERROR.md: Résolution des erreurs
  * BABELE_ERROR_ANALYSIS.md: Analyse erreur Babele
  * RESUME_MIGRATION.md: Résumé complet
  * modules/models/README.md: Documentation des DataModels

- template.json marqué comme DEPRECATED
- changelog.md mis à jour

Note: Erreur Babele/LibWrapper non résolue (problème de module externe)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-02-18 11:32:29 +01:00
parent e47ad95a38
commit f130f24a23
24 changed files with 1276 additions and 4 deletions

View File

@@ -29,10 +29,8 @@ Hooks.once("init", async function () {
console.log(`Initializing Ecryme RPG`);
game.system.ecryme = {
config: ECRYME_CONFIG,
EcrymeHotbar
}
// Import DataModels dynamically to avoid timing issues
const models = await import("./models/_module.js");
/* -------------------------------------------- */
// preload handlebars templates
@@ -54,7 +52,31 @@ Hooks.once("init", async function () {
// Define custom Entity classes
CONFIG.Combat.documentClass = EcrymeCombat
CONFIG.Actor.documentClass = EcrymeActor
CONFIG.Actor.dataModels = {
pc: models.EcrymePCDataModel,
npc: models.EcrymeNPCDataModel,
annency: models.EcrymeAnnencyDataModel
}
CONFIG.Item.documentClass = EcrymeItem
CONFIG.Item.dataModels = {
equipment: models.EcrymeEquipmentDataModel,
weapon: models.EcrymeWeaponDataModel,
trait: models.EcrymeTraitDataModel,
specialization: models.EcrymeSpecializationDataModel,
maneuver: models.EcrymeManeuverDataModel,
scar: models.EcrymeScarDataModel,
annency: models.EcrymeAnnencyItemDataModel,
boheme: models.EcrymeBohemeDataModel,
contact: models.EcrymeContactDataModel,
confrontation: models.EcrymeConfrontationDataModel
}
game.system.ecryme = {
config: ECRYME_CONFIG,
models,
EcrymeHotbar
}
/* -------------------------------------------- */
// Register sheet application classes

1
modules/models/.gitkeep Normal file
View File

@@ -0,0 +1 @@
# This file ensures the models directory is tracked by git

90
modules/models/README.md Normal file
View File

@@ -0,0 +1,90 @@
# DataModels Ecryme
## Vue d'ensemble
Ce dossier contient les DataModels pour le système Ecryme. Les DataModels sont la méthode moderne de Foundry VTT (v10+) pour définir les structures de données des acteurs et des items.
## Migration depuis template.json
Le système Ecryme a été migré de l'ancien système `template.json` vers les DataModels. Le fichier `template.json` est conservé pour référence mais est maintenant marqué comme deprecated.
## Structure des fichiers
### Modèles d'Items
- **equipment.js** - Équipements génériques
- **weapon.js** - Armes (mêlée et distance)
- **trait.js** - Traits de personnage
- **specialization.js** - Spécialisations de compétences
- **maneuver.js** - Manœuvres de combat
- **scar.js** - Cicatrices (impacts permanents)
- **annency-item.js** - Items Annency
- **boheme.js** - Bohèmes
- **contact.js** - Contacts
- **confrontation.js** - Confrontations
### Modèles d'Acteurs
- **pc.js** - Personnages joueurs (PC)
- **npc.js** - Personnages non-joueurs (NPC)
- **annency.js** - Annency (acteurs spéciaux)
### Fichier d'index
- **_module.js** - Centralise tous les exports des DataModels
## Utilisation
Les DataModels sont automatiquement enregistrés dans `CONFIG.Actor.dataModels` et `CONFIG.Item.dataModels` lors de l'initialisation du système dans `ecryme-main.js`.
### Accès aux données
Dans les acteurs et items, les données du système sont accessibles via `actor.system` ou `item.system` :
```javascript
// Exemple avec un PC
const athletics = actor.system.skills.physical.skilllist.athletics.value;
// Exemple avec une arme
const weaponType = item.system.weapontype;
```
## Avantages des DataModels
1. **Validation automatique** - Les types de champs sont vérifiés automatiquement
2. **Valeurs par défaut** - Chaque champ a une valeur initiale définie
3. **Type safety** - Meilleure autocomplete dans les IDEs
4. **Performance** - Optimisation interne de Foundry VTT
5. **Maintenance** - Code plus propre et organisé
## Compatibilité
Les DataModels sont rétrocompatibles avec les données existantes. Les acteurs et items créés avec l'ancien système `template.json` seront automatiquement migrés vers les nouveaux DataModels lors de leur chargement.
## Développement
Pour ajouter un nouveau type d'acteur ou d'item :
1. Créer un nouveau fichier DataModel dans ce dossier
2. Définir le schema avec `static defineSchema()`
3. Exporter le modèle dans `_module.js`
4. Enregistrer le modèle dans `ecryme-main.js` (CONFIG.Actor.dataModels ou CONFIG.Item.dataModels)
### Exemple minimal
```javascript
export default class MyNewItemDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
value: new fields.NumberField({ initial: 0, integer: true, min: 0 })
};
}
}
```
## Documentation Foundry VTT
Pour plus d'informations sur les DataModels :
https://foundryvtt.com/article/system-data-models/

21
modules/models/_module.js Normal file
View File

@@ -0,0 +1,21 @@
/**
* Index des DataModels pour Ecryme
* Ce fichier centralise tous les exports des modèles de données
*/
// Modèles d'items
export { default as EcrymeEquipmentDataModel } from './equipment.js';
export { default as EcrymeWeaponDataModel } from './weapon.js';
export { default as EcrymeTraitDataModel } from './trait.js';
export { default as EcrymeSpecializationDataModel } from './specialization.js';
export { default as EcrymeManeuverDataModel } from './maneuver.js';
export { default as EcrymeScarDataModel } from './scar.js';
export { default as EcrymeAnnencyItemDataModel } from './annency-item.js';
export { default as EcrymeBohemeDataModel } from './boheme.js';
export { default as EcrymeContactDataModel } from './contact.js';
export { default as EcrymeConfrontationDataModel } from './confrontation.js';
// Modèles d'acteurs
export { default as EcrymePCDataModel } from './pc.js';
export { default as EcrymeNPCDataModel } from './npc.js';
export { default as EcrymeAnnencyDataModel } from './annency.js';

View File

@@ -0,0 +1,14 @@
/**
* Data model pour les annency (items)
*/
export default class EcrymeAnnencyItemDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
collective: new fields.BooleanField({ initial: false }),
multiple: new fields.BooleanField({ initial: false }),
improvements: new fields.StringField({ initial: "" })
};
}
}

32
modules/models/annency.js Normal file
View File

@@ -0,0 +1,32 @@
/**
* Data model pour les Annency (acteurs)
*/
export default class EcrymeAnnencyDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
base: new fields.SchemaField({
iscollective: new fields.BooleanField({ initial: false }),
ismultiple: new fields.BooleanField({ initial: false }),
characters: new fields.ArrayField(new fields.StringField(), { initial: [] }),
location: new fields.SchemaField({
"1": new fields.StringField({ initial: "" }),
"2": new fields.StringField({ initial: "" }),
"3": new fields.StringField({ initial: "" }),
"4": new fields.StringField({ initial: "" }),
"5": new fields.StringField({ initial: "" })
}),
description: new fields.HTMLField({ initial: "" }),
enhancements: new fields.StringField({ initial: "" })
}),
boheme: new fields.SchemaField({
name: new fields.StringField({ initial: "" }),
ideals: new fields.StringField({ initial: "" }),
politic: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
})
};
}
}

13
modules/models/boheme.js Normal file
View File

@@ -0,0 +1,13 @@
/**
* Data model pour les bohèmes
*/
export default class EcrymeBohemeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
ideals: new fields.StringField({ initial: "" }),
political: new fields.StringField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,16 @@
/**
* Data model pour les confrontations
*/
export default class EcrymeConfrontationDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
attackerId: new fields.StringField({ initial: "" }),
defenserId: new fields.StringField({ initial: "" }),
rolllist: new fields.ArrayField(new fields.ObjectField(), { initial: [] }),
bonusexecution: new fields.NumberField({ initial: 0, integer: true }),
bonuspreservation: new fields.NumberField({ initial: 0, integer: true })
};
}
}

23
modules/models/contact.js Normal file
View File

@@ -0,0 +1,23 @@
/**
* Data model pour les contacts
*/
export default class EcrymeContactDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
attitude: new fields.StringField({
initial: "neutral",
choices: {
hostile: "Hostile",
unfriendly: "Inamical",
neutral: "Neutre",
friendly: "Amical",
allied: "Allié"
}
}),
organization: new fields.StringField({ initial: "" }),
location: new fields.StringField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,15 @@
/**
* Data model pour les équipements
*/
export default class EcrymeEquipmentDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
weight: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
cost: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
costunit: new fields.StringField({ initial: "" }),
quantity: new fields.NumberField({ initial: 1, integer: true, min: 0 })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les manœuvres
*/
export default class EcrymeManeuverDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

9
modules/models/npc.js Normal file
View File

@@ -0,0 +1,9 @@
/**
* Data model pour les PNJs (NPC)
* Utilise la même structure que les PC
*/
import EcrymePCDataModel from './pc.js';
export default class EcrymeNPCDataModel extends EcrymePCDataModel {
// Les NPCs utilisent exactement la même structure que les PCs
}

129
modules/models/pc.js Normal file
View File

@@ -0,0 +1,129 @@
/**
* Data model pour les personnages joueurs (PC)
*/
export default class EcrymePCDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
// Template biodata
const biodataSchema = {
age: new fields.StringField({ initial: "" }),
size: new fields.StringField({ initial: "" }),
lieunaissance: new fields.StringField({ initial: "" }),
nationalite: new fields.StringField({ initial: "" }),
profession: new fields.StringField({ initial: "" }),
residence: new fields.StringField({ initial: "" }),
milieusocial: new fields.StringField({ initial: "" }),
poids: new fields.StringField({ initial: "" }),
cheveux: new fields.StringField({ initial: "" }),
sexe: new fields.StringField({ initial: "" }),
yeux: new fields.StringField({ initial: "" }),
enfance: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
};
// Helper function to create a skill schema (creates new instances each time)
const createSkillSchema = (keyValue, nameValue, maxValue = 0) => ({
key: new fields.StringField({ initial: keyValue }),
name: new fields.StringField({ initial: nameValue }),
value: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
max: new fields.NumberField({ initial: maxValue, integer: true, min: 0 })
});
// Skills categories
const physicalSkills = {
athletics: new fields.SchemaField(createSkillSchema("athletics", "ECRY.ui.athletics")),
driving: new fields.SchemaField(createSkillSchema("driving", "ECRY.ui.driving")),
fencing: new fields.SchemaField(createSkillSchema("fencing", "ECRY.ui.fencing")),
brawling: new fields.SchemaField(createSkillSchema("brawling", "ECRY.ui.brawling")),
shooting: new fields.SchemaField(createSkillSchema("shooting", "ECRY.ui.shooting"))
};
const mentalSkills = {
anthropomecanology: new fields.SchemaField(createSkillSchema("anthropomecanology", "ECRY.ui.anthropomecanology", 10)),
ecrymology: new fields.SchemaField(createSkillSchema("ecrymology", "ECRY.ui.ecrymology", 10)),
traumatology: new fields.SchemaField(createSkillSchema("traumatology", "ECRY.ui.traumatology", 10)),
traversology: new fields.SchemaField(createSkillSchema("traversology", "ECRY.ui.traversology", 10)),
urbatechnology: new fields.SchemaField(createSkillSchema("urbatechnology", "ECRY.ui.urbatechnology", 10))
};
const socialSkills = {
quibbling: new fields.SchemaField(createSkillSchema("quibbling", "ECRY.ui.quibbling", 10)),
creativity: new fields.SchemaField(createSkillSchema("creativity", "ECRY.ui.creativity", 10)),
loquacity: new fields.SchemaField(createSkillSchema("loquacity", "ECRY.ui.loquacity", 10)),
guile: new fields.SchemaField(createSkillSchema("guile", "ECRY.ui.guile", 10)),
performance: new fields.SchemaField(createSkillSchema("performance", "ECRY.ui.performance", 10))
};
// Helper function to create a cephaly skill schema
const createCephalySkillSchema = (nameValue) => ({
name: new fields.StringField({ initial: nameValue }),
value: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
max: new fields.NumberField({ initial: 10, integer: true })
});
// Cephaly skills
const cephalySkills = {
elegy: new fields.SchemaField(createCephalySkillSchema("ECRY.ui.elegy")),
entelechy: new fields.SchemaField(createCephalySkillSchema("ECRY.ui.entelechy")),
mekany: new fields.SchemaField(createCephalySkillSchema("ECRY.ui.mekany")),
psyche: new fields.SchemaField(createCephalySkillSchema("ECRY.ui.psyche")),
scoria: new fields.SchemaField(createCephalySkillSchema("ECRY.ui.scoria"))
};
// Helper function to create an impact schema
const createImpactSchema = () => ({
superficial: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
light: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
serious: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
major: new fields.NumberField({ initial: 0, integer: true, min: 0 })
});
return {
// Biodata
biodata: new fields.SchemaField(biodataSchema),
// Core data
subactors: new fields.ArrayField(new fields.StringField(), { initial: [] }),
equipmentfree: new fields.StringField({ initial: "" }),
// Skills
skills: new fields.SchemaField({
physical: new fields.SchemaField({
name: new fields.StringField({ initial: "ECRY.ui.physical" }),
pnjvalue: new fields.NumberField({ initial: 0, integer: true }),
skilllist: new fields.SchemaField(physicalSkills)
}),
mental: new fields.SchemaField({
name: new fields.StringField({ initial: "ECRY.ui.mental" }),
pnjvalue: new fields.NumberField({ initial: 0, integer: true }),
skilllist: new fields.SchemaField(mentalSkills)
}),
social: new fields.SchemaField({
name: new fields.StringField({ initial: "ECRY.ui.social" }),
pnjvalue: new fields.NumberField({ initial: 0, integer: true }),
skilllist: new fields.SchemaField(socialSkills)
})
}),
// Impacts
impacts: new fields.SchemaField({
physical: new fields.SchemaField(createImpactSchema()),
mental: new fields.SchemaField(createImpactSchema()),
social: new fields.SchemaField(createImpactSchema())
}),
// Cephaly
cephaly: new fields.SchemaField({
name: new fields.StringField({ initial: "ECRY.ui.cephaly" }),
skilllist: new fields.SchemaField(cephalySkills)
}),
// Internals
internals: new fields.SchemaField({
confrontbonus: new fields.NumberField({ initial: 0, integer: true })
})
};
}
}

25
modules/models/scar.js Normal file
View File

@@ -0,0 +1,25 @@
/**
* Data model pour les cicatrices
*/
export default class EcrymeScarDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
skillcategory: new fields.ArrayField(
new fields.StringField({
choices: {
physical: "Physique",
mental: "Mental",
social: "Social",
cephalie: "Céphalie"
}
}),
{
initial: ["physical", "mental", "social", "cephalie"]
}
),
scarLevel: new fields.NumberField({ initial: 1, integer: true, min: 1 })
};
}
}

View File

@@ -0,0 +1,13 @@
/**
* Data model pour les spécialisations
*/
export default class EcrymeSpecializationDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
bonus: new fields.NumberField({ initial: 2, integer: true }),
skillkey: new fields.StringField({ initial: "" })
};
}
}

13
modules/models/trait.js Normal file
View File

@@ -0,0 +1,13 @@
/**
* Data model pour les traits
*/
export default class EcrymeTraitDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
traitype: new fields.StringField({ initial: "normal" }),
level: new fields.NumberField({ initial: 1, integer: true, min: 1 })
};
}
}

16
modules/models/weapon.js Normal file
View File

@@ -0,0 +1,16 @@
/**
* Data model pour les armes
*/
export default class EcrymeWeaponDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
weight: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
cost: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
costunit: new fields.StringField({ initial: "" }),
weapontype: new fields.StringField({ initial: "melee", choices: { melee: "Mêlée", ranged: "Distance" } }),
effect: new fields.NumberField({ initial: 0, integer: true })
};
}
}