- Schéma xp dans CelestopolCharacter : actuel (éditable), log[] ({montant, raison, date}), depense (calculé dans prepareDerivedData)
- Bouton 'Dépenser XP' → DialogV2 (montant + raison) : décrémente actuel, logge l'entrée
- Suppression d'entrée de log avec remboursement des points (mode édition)
- Section XP en haut de l'onglet Biographie : compteurs, tableau du log, référentiel des coûts
- i18n : section CELESTOPOL.XP.* complète
- CSS : .xp-section avec compteurs, tableau de log et accordéon de référence
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
95 lines
3.3 KiB
JavaScript
95 lines
3.3 KiB
JavaScript
import { SYSTEM } from "../config/system.mjs"
|
|
|
|
export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const fields = foundry.data.fields
|
|
const reqInt = { required: true, nullable: false, integer: true }
|
|
const schema = {}
|
|
|
|
schema.concept = new fields.StringField({ required: true, nullable: false, initial: "" })
|
|
schema.initiative = new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
|
|
|
schema.anomaly = new fields.SchemaField({
|
|
type: new fields.StringField({ required: true, nullable: false, initial: "none",
|
|
choices: Object.keys(SYSTEM.ANOMALY_TYPES) }),
|
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
|
})
|
|
|
|
const skillField = (label) => new fields.SchemaField({
|
|
label: new fields.StringField({ required: true, initial: label }),
|
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
|
})
|
|
|
|
const statField = (statId) => {
|
|
const skills = SYSTEM.SKILLS[statId]
|
|
const skillSchema = {}
|
|
for (const [key, skill] of Object.entries(skills)) {
|
|
skillSchema[key] = skillField(skill.label)
|
|
}
|
|
return new fields.SchemaField({
|
|
label: new fields.StringField({ required: true, initial: SYSTEM.STATS[statId].label }),
|
|
res: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
|
actuel: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), // res + wound malus
|
|
...skillSchema,
|
|
})
|
|
}
|
|
|
|
schema.stats = new fields.SchemaField({
|
|
ame: statField("ame"),
|
|
corps: statField("corps"),
|
|
coeur: statField("coeur"),
|
|
esprit: statField("esprit"),
|
|
})
|
|
|
|
schema.blessures = new fields.SchemaField({
|
|
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
|
})
|
|
|
|
schema.prefs = new fields.SchemaField({
|
|
rollMoonDie: new fields.BooleanField({ required: true, initial: false }),
|
|
difficulty: new fields.StringField({ required: true, nullable: false, initial: "normal" }),
|
|
})
|
|
|
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
|
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
|
|
|
|
return schema
|
|
}
|
|
|
|
static LOCALIZATION_PREFIXES = ["CELESTOPOL.NPC"]
|
|
|
|
prepareDerivedData() {
|
|
super.prepareDerivedData()
|
|
const malus = this.getWoundMalus()
|
|
// Initiative PNJ : valeur du Domaine Corps
|
|
this.initiative = Math.max(0, this.stats.corps.res + malus)
|
|
for (const stat of Object.values(this.stats)) {
|
|
stat.actuel = Math.max(0, stat.res + malus)
|
|
}
|
|
}
|
|
|
|
getWoundMalus() {
|
|
const lvl = Math.max(0, Math.min(8, this.blessures.lvl))
|
|
return SYSTEM.WOUND_LEVELS[lvl]?.malus ?? 0
|
|
}
|
|
|
|
async roll(statId, skillId) {
|
|
const { CelestopolRoll } = await import("../documents/roll.mjs")
|
|
const skill = this.stats[statId][skillId]
|
|
if (!skill) return null
|
|
|
|
return CelestopolRoll.prompt({
|
|
actorId: this.parent.id,
|
|
actorName: this.parent.name,
|
|
actorImage: this.parent.img,
|
|
statId,
|
|
skillId,
|
|
skillLabel: skill.label,
|
|
skillValue: skill.value,
|
|
woundMalus: this.getWoundMalus(),
|
|
difficulty: this.prefs.difficulty,
|
|
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
|
})
|
|
}
|
|
}
|