Files
fvtt-celestopol/module/models/npc.mjs

130 lines
4.6 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.
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
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.npcType = new fields.StringField({ required: true, nullable: false, initial: "standard",
choices: Object.keys(SYSTEM.NPC_TYPES) })
schema.concept = new fields.StringField({ required: true, nullable: false, initial: "" })
schema.faction = 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 }),
})
// PNJs : 4 domaines uniquement (pas de sous-compétences)
const domainField = (statId) => 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 }),
})
schema.stats = new fields.SchemaField({
ame: domainField("ame"),
corps: domainField("corps"),
coeur: domainField("coeur"),
esprit: domainField("esprit"),
})
schema.blessures = new fields.SchemaField({
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
})
schema.histoire = new fields.HTMLField({ required: true, textSearch: true })
schema.descriptionPhysique = 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 (avec malus blessures)
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)
}
this.armorMalus = this.getArmorMalus()
}
getWoundMalus() {
const lvl = Math.max(0, Math.min(8, this.blessures.lvl))
return SYSTEM.WOUND_LEVELS[lvl]?.malus ?? 0
}
/** Somme des malus des armures équipées (valeur négative ou 0). */
getArmorMalus() {
const armures = this.parent?.itemTypes?.armure ?? []
return armures
.filter(a => a.system.equipped)
.reduce((sum, a) => {
const value = a.system.protection ?? a.system.malus
return sum + (value ? -Math.abs(value) : 0)
}, 0)
}
/**
* Retourne le malus d'armure applicable pour un jet PNJ.
* Règle : sur tous les jets de Corps uniquement.
* @param {string} statId
* @returns {number}
*/
getArmorMalusForRoll(statId) {
return statId === "corps" ? this.getArmorMalus() : 0
}
/**
* Lance un jet sur un domaine (Âme/Corps/Cœur/Esprit).
* Le label affiché tient compte du type de PNJ (standard vs antagoniste).
*/
async roll(statId) {
const { CelestopolRoll } = await import("../documents/roll.mjs")
const statData = this.stats[statId]
if (!statData) return null
const isAntagoniste = this.npcType === "antagoniste"
const skillLabel = isAntagoniste
? SYSTEM.ANTAGONISTE_STATS[statId]?.label
: SYSTEM.STATS[statId]?.label
return CelestopolRoll.prompt({
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
statId,
skillLabel,
skillValue: statData.res,
woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalusForRoll(statId),
woundLevel: this.blessures.lvl,
})
}
/** Alias pour compatibilité avec le handler _onRoll (clic sans skillId). */
async rollResistance(statId) {
return this.roll(statId)
}
}