Fix apv2, WIP
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
export { default as VermineCharacterData } from "./character.mjs"
|
||||
export { default as VermineNpcData } from "./npc.mjs"
|
||||
export { default as VermineGroupData } from "./group.mjs"
|
||||
export { default as VermineCreatureData } from "./creature.mjs"
|
||||
export { default as VermineItemData } from "./item.mjs"
|
||||
export { default as VermineWeaponData } from "./weapon.mjs"
|
||||
export { default as VermineDefenseData } from "./defense.mjs"
|
||||
export { default as VermineVehicleData } from "./vehicle.mjs"
|
||||
export { default as VermineAbilityData } from "./ability.mjs"
|
||||
export { default as VermineSpecialtyData } from "./specialty.mjs"
|
||||
export { default as VermineBackgroundData } from "./background.mjs"
|
||||
export { default as VermineTraumaData } from "./trauma.mjs"
|
||||
export { default as VermineEvolutionData } from "./evolution.mjs"
|
||||
export { default as VermineRumorData } from "./rumor.mjs"
|
||||
export { default as VermineTargetData } from "./target.mjs"
|
||||
export { default as VermineRiteData } from "./rite.mjs"
|
||||
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* Schémas partagés pour les DataModels Vermine 2047.
|
||||
* Fonctions factory retournant des objets SchemaField réutilisables.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retourne un schema pour une blessure (minor/major/deadly)
|
||||
* @param {number} defaultThreshold
|
||||
* @param {number} defaultMax
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function woundSchema(defaultThreshold = 1, defaultMax = 5) {
|
||||
const fields = foundry.data.fields
|
||||
const reqInt = { required: true, nullable: false, integer: true }
|
||||
return {
|
||||
threshold: new fields.NumberField({ ...reqInt, initial: defaultThreshold, min: 0 }),
|
||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema des 3 types de blessures présents sur tous les acteurs.
|
||||
*/
|
||||
export function woundsSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return new fields.SchemaField({
|
||||
minorWound: new fields.SchemaField(woundSchema(1, 5)),
|
||||
majorWound: new fields.SchemaField(woundSchema(4, 4)),
|
||||
deadlyWound: new fields.SchemaField(woundSchema(8, 2))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Statut de combat (offensif/actif/passif).
|
||||
*/
|
||||
export function combatStatusSchema(defaultDifficulty = "7") {
|
||||
const fields = foundry.data.fields
|
||||
return new fields.SchemaField({
|
||||
label: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
difficulty: new fields.StringField({ required: true, nullable: false, initial: defaultDifficulty })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Description d'équipement.
|
||||
*/
|
||||
export function equipmentSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return new fields.SchemaField({
|
||||
description: new fields.HTMLField({ required: true, initial: "", textSearch: true })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribut avec value/min/max.
|
||||
* @param {number} defaultVal
|
||||
* @param {number} defaultMin
|
||||
* @param {number} defaultMax
|
||||
*/
|
||||
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 }),
|
||||
min: new fields.NumberField({ ...reqInt, initial: defaultMin, min: 0 }),
|
||||
max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Caractéristique (capacité) avec catégorie.
|
||||
* @param {string} category - physical, manual, mental, social
|
||||
*/
|
||||
export function abilityField(category) {
|
||||
const fields = foundry.data.fields
|
||||
const reqInt = { required: true, nullable: false, integer: true }
|
||||
return new fields.SchemaField({
|
||||
value: new fields.NumberField({ ...reqInt, initial: 1, min: 0, max: 5 }),
|
||||
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
max: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||
category: new fields.StringField({ required: true, nullable: false, initial: category })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Les 8 caractéristiques communes à character et npc.
|
||||
*/
|
||||
export function abilitiesSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return new fields.SchemaField({
|
||||
vigor: abilityField("physical"),
|
||||
health: abilityField("physical"),
|
||||
precision: abilityField("manual"),
|
||||
reflexes: abilityField("manual"),
|
||||
knowledge: abilityField("mental"),
|
||||
perception: abilityField("mental"),
|
||||
will: abilityField("social"),
|
||||
empathy: abilityField("social")
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Une compétence individuelle.
|
||||
* @param {string} category
|
||||
* @param {number} rarity - 0, 1, ou 2
|
||||
*/
|
||||
export function skillField(category, rarity = 0) {
|
||||
const fields = foundry.data.fields
|
||||
const reqInt = { required: true, nullable: false, integer: true }
|
||||
return new fields.SchemaField({
|
||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 5 }),
|
||||
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
max: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||
category: new fields.StringField({ required: true, nullable: false, initial: category }),
|
||||
rarity: new fields.NumberField({ ...reqInt, initial: rarity, min: 0, max: 2 })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Les 30 compétences (character et npc).
|
||||
*/
|
||||
export function skillsSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return new fields.SchemaField({
|
||||
// man
|
||||
arts: skillField("man", 1),
|
||||
civilization: skillField("man", 2),
|
||||
psychology: skillField("man", 1),
|
||||
rumors: skillField("man", 0),
|
||||
healing: skillField("man", 1),
|
||||
// animal
|
||||
animalism: skillField("animal", 1),
|
||||
dissection: skillField("animal", 2),
|
||||
wildlife: skillField("animal", 1),
|
||||
repulsion: skillField("animal", 0),
|
||||
tracks: skillField("animal", 0),
|
||||
// tool
|
||||
crafting: skillField("tool", 2),
|
||||
diy: skillField("tool", 0),
|
||||
mecanical: skillField("tool", 2),
|
||||
piloting: skillField("tool", 1),
|
||||
technology: skillField("tool", 2),
|
||||
// weapon
|
||||
firearms: skillField("weapon", 2),
|
||||
archery: skillField("weapon", 0),
|
||||
armory: skillField("weapon", 2),
|
||||
throwing: skillField("weapon", 0),
|
||||
melee: skillField("weapon", 0),
|
||||
// survival
|
||||
alertness: skillField("survival", 0),
|
||||
atletics: skillField("survival", 0),
|
||||
food: skillField("survival", 0),
|
||||
stealth: skillField("survival", 0),
|
||||
close: skillField("survival", 0),
|
||||
// world
|
||||
environment: skillField("world", 1),
|
||||
flora: skillField("world", 1),
|
||||
road: skillField("world", 0),
|
||||
toxics: skillField("world", 2),
|
||||
ruins: skillField("world", 1)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Catégories de compétences avec domaine de prédilection.
|
||||
*/
|
||||
export function skillCategoriesSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return new fields.SchemaField({
|
||||
preferred: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
man: new fields.SchemaField({
|
||||
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.man" }),
|
||||
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||
}),
|
||||
animal: new fields.SchemaField({
|
||||
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.animal" }),
|
||||
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||
}),
|
||||
tool: new fields.SchemaField({
|
||||
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.tool" }),
|
||||
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||
}),
|
||||
weapon: new fields.SchemaField({
|
||||
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.weapon" }),
|
||||
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||
}),
|
||||
survival: new fields.SchemaField({
|
||||
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.survival" }),
|
||||
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||
}),
|
||||
world: new fields.SchemaField({
|
||||
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.world" }),
|
||||
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// ── Item shared schemas ──────────────────────────────────────────────────
|
||||
|
||||
const reqInt = { required: true, nullable: false, integer: true }
|
||||
|
||||
/**
|
||||
* Rareté avec handicap.
|
||||
*/
|
||||
export function raritySchema() {
|
||||
const fields = foundry.data.fields
|
||||
return new fields.SchemaField({
|
||||
value: new fields.NumberField({ ...reqInt, initial: 3, min: 1, max: 5 }),
|
||||
handicap: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Dégâts des items (hors arme).
|
||||
*/
|
||||
export function itemDamagesSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return new fields.SchemaField({
|
||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 5 }),
|
||||
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
max: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||
pannes: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||
initial: ["mineure", "mineure", "grave", "grave", "critique"]
|
||||
}),
|
||||
state: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||
initial: ["endommagé", "endommagé", "défectueux", "défectueux", "hors d'usage"]
|
||||
}),
|
||||
effect: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||
initial: ["bonus annulé", "bonus annulé", "malus 1D", "malus 1D", "inutilisable"]
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Base commune à tous les items (template "base" dans l'ancien template.json).
|
||||
*/
|
||||
export function baseItemSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return {
|
||||
description: new fields.HTMLField({ required: true, initial: "", textSearch: true }),
|
||||
rarity: raritySchema(),
|
||||
reliability: new fields.NumberField({ ...reqInt, initial: 3, min: 1, max: 5 }),
|
||||
handicap: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
quantity: new fields.NumberField({ ...reqInt, initial: 1, min: 1 }),
|
||||
weight: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
traits: new fields.ObjectField({ required: true, initial: {} }),
|
||||
damages: itemDamagesSchema()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Template "list" pour les items abstraits (ability, background, trauma, evolution, rumor, target).
|
||||
* Version légère avec seulement description.
|
||||
*/
|
||||
export function listItemSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return {
|
||||
description: new fields.HTMLField({ required: true, initial: "", textSearch: true })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schéma d'apprentissage pour les abilities.
|
||||
*/
|
||||
export function learnSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return new fields.SchemaField({
|
||||
threshold: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||
hindrance: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Niveau générique (value/min/max).
|
||||
*/
|
||||
export function levelSchema(defaultVal = 1, defaultMin = 1, defaultMax = 5) {
|
||||
const fields = foundry.data.fields
|
||||
return new fields.SchemaField({
|
||||
value: new fields.NumberField({ ...reqInt, initial: defaultVal, min: defaultMin }),
|
||||
min: new fields.NumberField({ ...reqInt, initial: defaultMin, min: 0 }),
|
||||
max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { listItemSchema, learnSchema, levelSchema } from "./_shared.mjs"
|
||||
|
||||
/**
|
||||
* DataModel pour les items de type "ability" (capacités, pouvoirs).
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineAbilityData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.ability"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return {
|
||||
...listItemSchema(),
|
||||
type: new fields.StringField({ required: true, initial: "" }),
|
||||
totem: new fields.StringField({ required: true, initial: "" }),
|
||||
learn: learnSchema(),
|
||||
level: levelSchema(1, 1, 3),
|
||||
effects: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||
initial: []
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { listItemSchema } from "./_shared.mjs"
|
||||
|
||||
/**
|
||||
* DataModel pour les items de type "background" (historiques, origines).
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineBackgroundData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.background"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
const reqInt = { required: true, nullable: false, integer: true }
|
||||
return {
|
||||
...listItemSchema(),
|
||||
cost: new fields.NumberField({ ...reqInt, initial: 1, min: 0 })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* DataModel pour les acteurs de type "character" (personnage).
|
||||
* Étend foundry.abstract.TypeDataModel.
|
||||
*/
|
||||
import {
|
||||
woundSchema,
|
||||
combatStatusSchema,
|
||||
equipmentSchema,
|
||||
attributeSchema,
|
||||
abilitiesSchema,
|
||||
skillCategoriesSchema,
|
||||
skillsSchema
|
||||
} from "./_shared.mjs"
|
||||
|
||||
export default class VermineCharacterData extends foundry.abstract.TypeDataModel {
|
||||
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.character"]
|
||||
|
||||
/** @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("7"),
|
||||
|
||||
// Adaptation (totems humain/adapté)
|
||||
adaptation: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 0, max: 5 }),
|
||||
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: 5, min: 0 }),
|
||||
totems: new fields.SchemaField({
|
||||
human: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 0, max: 3 }),
|
||||
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: 3, min: 0 })
|
||||
}),
|
||||
adapted: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 0, max: 3 }),
|
||||
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: 3, min: 0 })
|
||||
})
|
||||
})
|
||||
}),
|
||||
|
||||
// Identité
|
||||
identity: new fields.SchemaField({
|
||||
height: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||
weight: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||
totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
age: new fields.StringField({ required: true, nullable: false, initial: "15" }),
|
||||
ageType: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 2 }),
|
||||
profile: 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: "" }),
|
||||
objectives: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
relations: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
biography: new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||
}),
|
||||
|
||||
// Équipement
|
||||
equipment: equipmentSchema(),
|
||||
|
||||
// Attributs (XP, réputation, sang-froid, effort)
|
||||
attributes: new fields.SchemaField({
|
||||
xp: attributeSchema(0, 0, 10),
|
||||
reputation: attributeSchema(0, 0, 10),
|
||||
self_control: attributeSchema(0, 0, 5),
|
||||
effort: attributeSchema(0, 0, 5)
|
||||
}),
|
||||
|
||||
// Rencontres
|
||||
encounters: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
|
||||
|
||||
// Caractéristiques (8)
|
||||
abilities: abilitiesSchema(),
|
||||
|
||||
// Catégories de compétences
|
||||
skill_categories: skillCategoriesSchema(),
|
||||
|
||||
// Compétences (30)
|
||||
skills: skillsSchema()
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData()
|
||||
|
||||
// 1. Déterminer la tranche d'âge
|
||||
this._setAgeType()
|
||||
|
||||
// 2. Calculer les modificateurs de caractéristiques
|
||||
this._setAbilityModifiers()
|
||||
|
||||
// 3. Calculer les réserves (sang-froid et effort)
|
||||
this._setSelfControlMax()
|
||||
this._setEffortMax()
|
||||
|
||||
// 4. Calculer les seuils de blessures
|
||||
this._setWoundThresholds()
|
||||
|
||||
// 5. Mettre à jour le statut de combat
|
||||
this._updateCombatStatus()
|
||||
}
|
||||
|
||||
/**
|
||||
* Détermine la tranche d'âge (1=jeune, 2=adulte, 3=vieux)
|
||||
* à partir de l'âge et de la config VERMINE.AgeTypes.
|
||||
*/
|
||||
_setAgeType() {
|
||||
const age = this.identity.age
|
||||
const ageTypes = CONFIG.VERMINE.AgeTypes
|
||||
for (const [type, cfg] of Object.entries(ageTypes)) {
|
||||
if (age >= parseInt(cfg.beginning, 10)) {
|
||||
this.identity.ageType = parseInt(type, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule les modificateurs de caractéristiques (règle d20).
|
||||
*/
|
||||
_setAbilityModifiers() {
|
||||
for (const ability of Object.values(this.abilities)) {
|
||||
ability.mod = Math.floor((ability.value - 10) / 2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le max de sang-froid :
|
||||
* somme des caractéristiques mentales + sociales + modificateur d'âge.
|
||||
*/
|
||||
_setSelfControlMax() {
|
||||
const abilities = Object.values(this.abilities)
|
||||
const modFromAge = this._getModFromAgeSelfControl()
|
||||
const sum = abilities
|
||||
.filter(a => a.category === "mental" || a.category === "social")
|
||||
.reduce((acc, a) => acc + a.value, 0)
|
||||
this.attributes.self_control.max = sum + modFromAge
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le max d'effort :
|
||||
* somme des caractéristiques physiques + manuelles + modificateur d'âge.
|
||||
*/
|
||||
_setEffortMax() {
|
||||
const abilities = Object.values(this.abilities)
|
||||
const modFromAge = this._getModFromAgeEffort()
|
||||
const sum = abilities
|
||||
.filter(a => a.category === "physical" || a.category === "manual")
|
||||
.reduce((acc, a) => acc + a.value, 0)
|
||||
this.attributes.effort.max = sum + modFromAge
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule les seuils de blessures à partir de la Santé.
|
||||
*/
|
||||
_setWoundThresholds() {
|
||||
const health = this.abilities.health.value
|
||||
const ageMods = this._getModFromAgeWounds()
|
||||
|
||||
this.minorWound.threshold = health
|
||||
this.majorWound.threshold = health + 3
|
||||
this.deadlyWound.threshold = (health + 7 < 11) ? health + 7 : 10
|
||||
|
||||
this.minorWound.max = 4 + ageMods.l
|
||||
this.majorWound.max = 3 + ageMods.h
|
||||
this.deadlyWound.max = 2 + ageMods.d
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
|
||||
// ── Modificateurs liés à l'âge ──────────────────────────────────────
|
||||
|
||||
/** @returns {number} Modificateur de sang-froid selon l'âge. */
|
||||
_getModFromAgeSelfControl() {
|
||||
return this.identity.ageType === 1 ? -1 : 0
|
||||
}
|
||||
|
||||
/** @returns {number} Modificateur d'effort selon l'âge. */
|
||||
_getModFromAgeEffort() {
|
||||
if (this.identity.ageType === 1) return -1
|
||||
if (this.identity.ageType === 3) return -2
|
||||
return 0
|
||||
}
|
||||
|
||||
/** @returns {{l: number, h: number, d: number}} Modificateurs de blessures selon l'âge. */
|
||||
_getModFromAgeWounds() {
|
||||
if (this.identity.ageType === 1) return { l: 0, h: 0, d: -1 }
|
||||
if (this.identity.ageType === 3) return { l: -1, h: -1, d: -1 }
|
||||
return { l: 0, h: 0, d: 0 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* DataModel pour les acteurs de type "creature" (créature).
|
||||
* Étend foundry.abstract.TypeDataModel.
|
||||
*/
|
||||
import {
|
||||
woundSchema,
|
||||
combatStatusSchema,
|
||||
equipmentSchema,
|
||||
attributeSchema
|
||||
} from "./_shared.mjs"
|
||||
|
||||
export default class VermineCreatureData extends foundry.abstract.TypeDataModel {
|
||||
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.creature"]
|
||||
|
||||
/** @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é
|
||||
identity: new fields.SchemaField({
|
||||
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: "" }),
|
||||
notes: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
biography: new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||
}),
|
||||
|
||||
// Compétences (description libre)
|
||||
skills: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
|
||||
// Modes de jeu actifs
|
||||
modes: new fields.SchemaField({
|
||||
survival: new fields.BooleanField({ required: true, initial: true }),
|
||||
nightmare: new fields.BooleanField({ required: true, initial: true }),
|
||||
apocalypse: new fields.BooleanField({ required: true, initial: false })
|
||||
}),
|
||||
|
||||
// Niveaux de créature (patron, taille, rôle, meute)
|
||||
pattern: attributeSchema(1, 1, 4),
|
||||
size: attributeSchema(1, 1, 3),
|
||||
role: attributeSchema(1, 1, 4),
|
||||
pack: attributeSchema(0, 0, 3),
|
||||
|
||||
// Valeurs calculées (dérivées de pattern/size/role/pack)
|
||||
computed: new fields.SchemaField({
|
||||
attack: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||
damage: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||
vigor: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||
reaction: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||
reactionBonus: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||
pools: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||
gear: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 9 }),
|
||||
gearHindrance: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||
protection: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1 })
|
||||
}),
|
||||
|
||||
// Équipement
|
||||
equipment: equipmentSchema()
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData()
|
||||
|
||||
// 1. Calculer les valeurs dérivées (attaque, dégâts, vigueur, etc.)
|
||||
this._calculateCreatureComputedValues()
|
||||
|
||||
// 2. Calculer les seuils de blessures
|
||||
this._calculateCreatureWoundThresholds()
|
||||
|
||||
// 3. Mettre à jour le statut de combat
|
||||
this._updateCombatStatus()
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule les valeurs dérivées à partir des niveaux de patron, taille, rôle et meute.
|
||||
* Utilise les configs CONFIG.VERMINE.creaturePatternLevels, .creatureSizeLevels,
|
||||
* .creatureRoleLevels, .creaturePackLevels.
|
||||
*
|
||||
* Règles :
|
||||
* - Attaque = pattern.attack + size.attack + pack.attack + role.reaction
|
||||
* - Dégâts = pattern.damage + size.vigor + pack.damage
|
||||
* - Vigueur = size.vigor + pack.damage
|
||||
* - Réaction = role.reaction + role.reaction_bonus
|
||||
*/
|
||||
_calculateCreatureComputedValues() {
|
||||
const patternLevel = this.pattern?.value || 1
|
||||
const sizeLevel = this.size?.value || 1
|
||||
const roleLevel = this.role?.value || 1
|
||||
const packLevel = this.pack?.value || 0
|
||||
|
||||
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
|
||||
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}
|
||||
const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel] || {}
|
||||
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}
|
||||
|
||||
// Attaque : patron + taille + meute + réaction du rôle
|
||||
this.computed.attack = (patternConfig.attack || 0)
|
||||
+ (sizeConfig.attack || 0)
|
||||
+ (packConfig.attack || 0)
|
||||
+ (roleConfig.reaction || 0)
|
||||
|
||||
// Dégâts : patron + vigueur de taille + meute
|
||||
this.computed.damage = (patternConfig.damage || 0)
|
||||
+ (sizeConfig.vigor || 0)
|
||||
+ (packConfig.damage || 0)
|
||||
|
||||
// Vigueur : taille + meute
|
||||
this.computed.vigor = (sizeConfig.vigor || 0) + (packConfig.damage || 0)
|
||||
|
||||
// Réaction : rôle
|
||||
this.computed.reaction = (roleConfig.reaction || 0) + (roleConfig.reaction_bonus || 0)
|
||||
this.computed.reactionBonus = roleConfig.reaction_bonus || 0
|
||||
|
||||
// Réserves
|
||||
this.computed.pools = roleConfig.pools || 0
|
||||
|
||||
// Équipement et handicap
|
||||
this.computed.gear = roleConfig.gear || 9
|
||||
this.computed.gearHindrance = roleConfig.gear_hindrance || 0
|
||||
|
||||
// Protection
|
||||
this.computed.protection = roleConfig.protection || 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule les seuils de blessures à partir du patron, de la taille et de la meute.
|
||||
* Les seuils sont la somme des valeurs correspondantes des trois sources.
|
||||
*/
|
||||
_calculateCreatureWoundThresholds() {
|
||||
const patternLevel = this.pattern?.value || 1
|
||||
const sizeLevel = this.size?.value || 1
|
||||
const packLevel = this.pack?.value || 0
|
||||
|
||||
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
|
||||
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}
|
||||
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}
|
||||
|
||||
this.minorWound.threshold = (patternConfig.minorWound || 0)
|
||||
+ (sizeConfig.minorWound || 0)
|
||||
+ (packConfig.minorWound || 0)
|
||||
this.majorWound.threshold = (patternConfig.majorWound || 0)
|
||||
+ (sizeConfig.majorWound || 0)
|
||||
+ (packConfig.majorWound || 0)
|
||||
this.deadlyWound.threshold = (patternConfig.deadlyWound || 0)
|
||||
+ (sizeConfig.deadlyWound || 0)
|
||||
+ (packConfig.deadlyWound || 0)
|
||||
|
||||
// Max de blessures
|
||||
this.minorWound.max = Math.min(5, this.minorWound.threshold + 2)
|
||||
this.majorWound.max = Math.min(4, this.majorWound.threshold + 1)
|
||||
this.deadlyWound.max = Math.min(2, this.deadlyWound.threshold)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { baseItemSchema } from "./_shared.mjs"
|
||||
|
||||
/**
|
||||
* DataModel pour les items de type "defense" (protections, armures, boucliers).
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineDefenseData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.defense"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
const reqInt = { required: true, nullable: false, integer: true }
|
||||
return {
|
||||
...baseItemSchema(),
|
||||
level: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
specificLevel: new fields.SchemaField({
|
||||
label: new fields.StringField({ required: true, initial: "" }),
|
||||
level: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||
}),
|
||||
mobility: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
isShield: new fields.BooleanField({ required: true, initial: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { listItemSchema, levelSchema } from './_shared.mjs'
|
||||
|
||||
/**
|
||||
* DataModel pour les items de type "evolution" (évolutions du personnage).
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineEvolutionData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.evolution"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return {
|
||||
...listItemSchema(),
|
||||
level: levelSchema(1, 1, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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.StringField({ required: true, nullable: false, initial: "" })
|
||||
}),
|
||||
|
||||
// Équipement
|
||||
equipment: equipmentSchema(),
|
||||
|
||||
// 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: "" })),
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* DataModel pour les items de type "item" (équipement générique).
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineItemData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.item"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return {
|
||||
...baseItemSchema(),
|
||||
needSkill: new fields.SchemaField({
|
||||
value: new fields.BooleanField({ required: true, initial: false }),
|
||||
skill: new fields.StringField({ required: true, initial: "" })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import partagé — après la déclaration de classe car defineSchema est statique
|
||||
import { baseItemSchema } from './_shared.mjs'
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* DataModel pour les acteurs de type "npc" (PNJ).
|
||||
* Étend foundry.abstract.TypeDataModel.
|
||||
*
|
||||
* Note : le champ libre de compétences (texte descriptif) est nommé "freeSkills"
|
||||
* pour éviter le conflit avec le SchemaField "skills" qui contient les 30 compétences.
|
||||
*/
|
||||
import {
|
||||
woundSchema,
|
||||
combatStatusSchema,
|
||||
equipmentSchema,
|
||||
attributeSchema,
|
||||
abilitiesSchema,
|
||||
skillCategoriesSchema,
|
||||
skillsSchema
|
||||
} from "./_shared.mjs"
|
||||
|
||||
export default class VermineNpcData extends foundry.abstract.TypeDataModel {
|
||||
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.npc"]
|
||||
|
||||
/**
|
||||
* Migration des données avant traitement par le schéma.
|
||||
* Avant DataModel, template.json définissait "skills" comme un champ texte libre
|
||||
* pour les PNJ. Le DataModel réserve "skills" pour les 30 compétences individuelles
|
||||
* (SchemaField) et utilise "freeSkills" pour le texte libre.
|
||||
* @param {Object} source Données brutes avant validation du schéma
|
||||
* @returns {Object} Données migrées
|
||||
*/
|
||||
static migrateData(source) {
|
||||
if (typeof source.skills === "string") {
|
||||
source.freeSkills = source.skills
|
||||
}
|
||||
return super.migrateData(source)
|
||||
}
|
||||
|
||||
/** @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, difficulté par défaut 9 pour PNJ)
|
||||
combatStatus: combatStatusSchema("9"),
|
||||
|
||||
// Identité
|
||||
identity: new fields.SchemaField({
|
||||
name: 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: "" }),
|
||||
totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
notes: new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||
}),
|
||||
|
||||
// Attributs (XP, réputation, sang-froid, effort)
|
||||
attributes: new fields.SchemaField({
|
||||
xp: attributeSchema(0, 0, 10),
|
||||
reputation: attributeSchema(0, 0, 10),
|
||||
self_control: attributeSchema(0, 0, 5),
|
||||
effort: attributeSchema(0, 0, 5)
|
||||
}),
|
||||
|
||||
// Niveaux PNJ (menace, expérience, rôle)
|
||||
threat: attributeSchema(1, 1, 4),
|
||||
experience: attributeSchema(1, 1, 4),
|
||||
role: attributeSchema(1, 1, 4),
|
||||
|
||||
// Compétences (les 30 compétences individuelles)
|
||||
skills: skillsSchema(),
|
||||
|
||||
// Description libre des compétences (champ texte PNJ)
|
||||
freeSkills: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
|
||||
// Catégories de compétences
|
||||
skill_categories: skillCategoriesSchema(),
|
||||
|
||||
// Caractéristiques (8)
|
||||
abilities: abilitiesSchema(),
|
||||
|
||||
// Équipement
|
||||
equipment: equipmentSchema()
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData()
|
||||
|
||||
// 1. Calculer les seuils de blessures selon le niveau de menace
|
||||
this._setNpcWoundThresholds()
|
||||
|
||||
// 2. Calculer les réserves selon le niveau de rôle
|
||||
this._setNpcAttributes()
|
||||
|
||||
// 3. Définir les libellés des caractéristiques
|
||||
this._setAbilityLabels()
|
||||
|
||||
// 4. Mettre à jour le statut de combat
|
||||
this._updateCombatStatus()
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule les seuils de blessures à partir du niveau de menace.
|
||||
* Utilise CONFIG.VERMINE.npcThreatLevels.
|
||||
*/
|
||||
_setNpcWoundThresholds() {
|
||||
const health = this.abilities?.health?.value || 1
|
||||
const threatLevel = this.threat?.value || 1
|
||||
const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {}
|
||||
|
||||
this.minorWound.threshold = threatConfig.minorWound || health
|
||||
this.majorWound.threshold = threatConfig.majorWound || (health + 3)
|
||||
this.deadlyWound.threshold = threatConfig.deadlyWound || (health + 7 < 11 ? health + 7 : 10)
|
||||
|
||||
this.minorWound.max = threatConfig.minorWound || 4
|
||||
this.majorWound.max = threatConfig.majorWound || 3
|
||||
this.deadlyWound.max = threatConfig.deadlyWound || 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit les attributs dérivés (effort, sang-froid) selon le niveau de rôle.
|
||||
* Utilise CONFIG.VERMINE.npcRoleLevels.
|
||||
*/
|
||||
_setNpcAttributes() {
|
||||
const roleLevel = this.role?.value || 1
|
||||
const roleConfig = CONFIG.VERMINE.npcRoleLevels[roleLevel] || {}
|
||||
|
||||
this.attributes.effort.max = roleConfig.pools || 0
|
||||
this.attributes.self_control.max = roleConfig.reaction_bonus || 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit les libellés localisés des caractéristiques.
|
||||
*/
|
||||
_setAbilityLabels() {
|
||||
for (const [k, v] of Object.entries(this.abilities)) {
|
||||
v.label = game.i18n.localize(CONFIG.VERMINE.abilities[k]) ?? k
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { baseItemSchema } from './_shared.mjs'
|
||||
|
||||
/**
|
||||
* DataModel pour les items de type "rite" (rites, rituels).
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineRiteData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.rite"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return {
|
||||
...baseItemSchema(),
|
||||
rituel: new fields.StringField({ required: true, initial: "" }),
|
||||
transe: new fields.StringField({ required: true, initial: "" }),
|
||||
ability: new fields.StringField({ required: true, initial: "" }),
|
||||
effect: new fields.StringField({ required: true, initial: "" })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { listItemSchema } from "./_shared.mjs"
|
||||
|
||||
/**
|
||||
* DataModel pour les items de type "rumor" (rumeurs).
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineRumorData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.rumor"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return {
|
||||
...listItemSchema()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* DataModel pour les items de type "specialty" (spécialités de compétence).
|
||||
* Modèle minimal sans base partagée.
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineSpecialtyData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.specialty"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return {
|
||||
name: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
skill: new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { listItemSchema } from './_shared.mjs'
|
||||
|
||||
/**
|
||||
* DataModel pour les items de type "target" (cibles, objectifs).
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineTargetData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.target"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return {
|
||||
...listItemSchema(),
|
||||
level: new fields.StringField({ required: true, initial: "minor" })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { listItemSchema } from './_shared.mjs'
|
||||
|
||||
/**
|
||||
* DataModel pour les items de type "trauma" (traumatismes, séquelles).
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineTraumaData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.trauma"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
return {
|
||||
...listItemSchema(),
|
||||
type: new fields.StringField({ required: true, initial: "" })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { baseItemSchema } from "./_shared.mjs"
|
||||
|
||||
/**
|
||||
* DataModel pour les items de type "vehicle" (véhicules).
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineVehicleData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.vehicle"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
const reqInt = { required: true, nullable: false, integer: true }
|
||||
return {
|
||||
...baseItemSchema(),
|
||||
mobility: new fields.NumberField({ ...reqInt, initial: 3, min: 0 })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { baseItemSchema } from "./_shared.mjs"
|
||||
|
||||
/**
|
||||
* DataModel pour les items de type "weapon" (armes).
|
||||
* @augments {foundry.abstract.TypeDataModel}
|
||||
*/
|
||||
export default class VermineWeaponData extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["VERMINE.item.weapon"]
|
||||
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
const reqInt = { required: true, nullable: false, integer: true }
|
||||
return {
|
||||
...baseItemSchema(),
|
||||
min_range: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
max_range: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
damage: new fields.SchemaField({
|
||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
type: new fields.StringField({ required: true, initial: "" }),
|
||||
addVigor: new fields.BooleanField({ required: true, initial: false })
|
||||
}),
|
||||
ammo: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user