Files
vermine2047/module/models/group.mjs
T
uberwald 9d36b92fa9 feat: nouveaux apprentissages (Capacités de Totem, Historique, Réputation) et expérience de Groupe
- Dialogue d'apprentissage personnage : 4 nouveaux types d'achat.
  - Capacité de Totem : options filtrées (mode de jeu, niveau de
    groupe, totem, non-apprises), difficulté = valeur d'Apprentissage,
    limites 3×N1 / 1×N2-N3 / max 5, marque system.learned de l'item
    (convention type character) et learned.ability du personnage.
  - Élément d'Historique : difficulté 9, handicap 1 si astérisque
    (background.asterisk), marque learned.background + item.
  - Test de Réputation (réduire/augmenter) : réduire = difficulté
    10 − score visé (−1 pt), augmenter = difficulté = réputation
    actuelle (+1 par succès), bornes 0-10, marque learned.reputation.
  - Évolution : option Dé de Totem Adapté ×2 quand Adapté > Humain
    (1D remplacé par un dé adapté compté ×2 via VermineUtils.roll).
- Correction règles : les Dés d'Expérience sont dépensés même en cas
  d'échec du jet d'apprentissage (décompte hors #applySuccess).
- Expérience de Groupe : nouveau dialogue d'apprentissage de Groupe
  (niveau, réserve, capacité de totem), réserve d'expérience et phase
  dans system.experience, boutons sur la fiche Groupe, reset des
  limites par phase (level/reserve/ability).
- Phase d'expérience : filtre par groupe sélectionné, associe chaque
  personnage à son premier groupe, reset des limites de tous les
  nouveaux types d'apprentissage.
- Corrections : l'attaque de créature n'ajoute plus la Réaction du Rôle
  (regles_creatures.txt), suppression de la case XP des en-têtes PJ
  (legacy et AppV2).
- Divers : .eslintrc.js (règles inexistantes/obsolètes retirées),
  libellé jaune de dialogue en couleur chitine, i18n FR/EN, chips
  « appris » sur l'onglet expérience du personnage.
2026-08-05 11:56:59 +02:00

181 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* DataModel pour les acteurs de type "group" (groupe).
* Étend foundry.abstract.TypeDataModel.
*/
import {
woundSchema,
combatStatusSchema,
equipmentSchema,
attributeSchema,
levelSchema
} from "./_shared.mjs"
export default class VermineGroupData extends foundry.abstract.TypeDataModel {
/** @override */
static LOCALIZATION_PREFIXES = ["VERMINE.group"]
/** @override */
static defineSchema() {
const fields = foundry.data.fields
return {
// Blessures (base)
minorWound: new fields.SchemaField(woundSchema(1, 5)),
majorWound: new fields.SchemaField(woundSchema(4, 4)),
deadlyWound: new fields.SchemaField(woundSchema(8, 2)),
// Statut de combat (base)
combatStatus: combatStatusSchema(),
// Identité du groupe
identity: new fields.SchemaField({
totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
origin: new fields.StringField({ required: true, nullable: false, initial: "" }),
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
instincts: new fields.StringField({ required: true, nullable: false, initial: "" }),
prohibits: new fields.StringField({ required: true, nullable: false, initial: "" }),
notes: new fields.HTMLField({ required: true, initial: "" })
}),
// Équipement
equipment: equipmentSchema(),
// Notes de voyage (lieux, dangers, routes…)
roadNotes: new fields.HTMLField({ required: true, initial: "" }),
// Niveau du groupe (1-10)
level: levelSchema(1, 1, 10),
// Réputation
reputation: new fields.SchemaField({
value: new fields.NumberField({ required: true, nullable: true, integer: true, initial: 10, min: 2 }),
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 2, min: 0 }),
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 10, min: 0 })
}),
// Moral
morale: new fields.SchemaField({
level: new fields.StringField({ required: true, nullable: false, initial: "high" }),
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 7, min: 0, max: 7 }),
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 7, min: 0 })
}),
// Réserve
reserve: attributeSchema(0, 0, 10),
// Objectifs (majeurs et mineurs)
objectives: new fields.SchemaField({
major: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
minor: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" }))
}),
// Capacités de groupe
groupAbilities: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
// Membres (IDs d'acteurs)
members: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
// Dés d'Expérience collectifs (réserve de Groupe)
experience: new fields.SchemaField({
dice: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
phase: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
// Limites d'apprentissage par phase
learned: new fields.SchemaField({
phase: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
level: new fields.BooleanField({ required: true, initial: false }),
reserve: new fields.BooleanField({ required: true, initial: false }),
ability: new fields.BooleanField({ required: true, initial: false })
})
}),
// Rencontres
encounters: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" }))
}
}
/** @override */
prepareDerivedData() {
super.prepareDerivedData()
// 1. Initialiser les données de groupe si absentes
this._initGroupData()
// 2. Calculer la réserve max selon le niveau
this._calculateGroupReserve()
// 3. Mettre à jour le moral selon la valeur de dés
this._updateGroupMorale()
// 4. Mettre à jour le statut de combat
this._updateCombatStatus()
}
/**
* Initialise les champs optionnels du groupe s'ils ne sont pas présents.
*/
_initGroupData() {
if (!this.objectives) {
this.objectives = { major: [], minor: [] }
}
if (!this.groupAbilities) {
this.groupAbilities = []
}
if (!this.reserve) {
this.reserve = { value: 0, min: 0, max: 10 }
}
}
/**
* Calcule la réserve max en fonction du niveau du groupe.
* Règle simplifiée : niveau × 2, plafonné à 10.
*/
_calculateGroupReserve() {
const level = this.level?.value || 1
this.reserve.max = Math.min(10, level * 2)
if (this.reserve.value > this.reserve.max) {
this.reserve.value = this.reserve.max
}
}
/**
* Met à jour le niveau de moral en fonction de la valeur de dés.
* Règles : 7D+ = Haut, 6-3D = Normal, 2-1D = Bas, 0D = Crise.
*/
_updateGroupMorale() {
const moraleValue = this.morale?.value || 0
// Ne pas écraser un niveau explicitement défini (sauf "high" qui est la valeur par défaut)
if (this.morale.level && this.morale.level !== "high") return
if (moraleValue >= 7) {
this.morale.level = "high"
} else if (moraleValue >= 3) {
this.morale.level = "normal"
} else if (moraleValue >= 1) {
this.morale.level = "low"
} else {
this.morale.level = "crisis"
}
}
/**
* Met à jour le label du statut de combat en fonction de la difficulté.
*/
_updateCombatStatus() {
const difficulty = parseInt(this.combatStatus.difficulty) || 9
let newLabel = "Passif"
switch (difficulty) {
case 5: newLabel = "Offensif"; break
case 7: newLabel = "Actif"; break
case 9: newLabel = "Passif"; break
}
if (this.combatStatus.label !== newLabel) {
this.combatStatus.label = newLabel
}
}
}