feat: capacités de créature, blessures séquentielles et refonte fiche Groupe
- Nouvel item Capacité de créature (creaturecapacity) : DataModel, fiche AppV2, chat card, types Base/Survie/Cauchemar/Apocalypse, liste sur l'onglet Informations de la créature avec drag&drop et post au chat. - Blessures : coches séquentielles respectant la gravité (onClickWound), malus de blessure -1D/-2D/-3D appliqué aux jets (Règles p.42), montée en gravité automatique si les cercles sont pleins. - Fiche Groupe : objectifs majeurs/mineurs éditables, liste de membres simplifiée, notes de voyage (roadNotes), totem qui remplit instinct/interdits, refonte visuelle des onglets. - Créature : Réaction lançable, seuils/cercles de blessures corrigés (Taille cumulée, Groupe sans seuil), libellés Taille/Meute. - Corrections : icône de carte de chat plafonnée à 64px, slash orphelin d'entrave à 0, notes de matériel du Groupe éditable (formInput), bonus de Réaction non double-compté. - Divers : suppression des captures de débogage, licence README.
This commit is contained in:
+50
-10
@@ -204,20 +204,52 @@ export class VermineExchange {
|
||||
return Boolean(actor) && (game.user.isGM || actor.isOwner)
|
||||
}
|
||||
|
||||
/** Ordre de gravité des blessures (croissant). */
|
||||
static WOUND_ORDER = ['minorWound', 'majorWound', 'deadlyWound']
|
||||
|
||||
/** Malus de la pire blessure (règles p.42) : -1D Légère, -2D Grave, -3D Mortelle. */
|
||||
static WOUND_MALUS = { minorWound: 1, majorWound: 2, deadlyWound: 3 }
|
||||
|
||||
/**
|
||||
* Applique +1 dans la catégorie de blessure indiquée (plafonné au max).
|
||||
* Malus de blessure applicable à toutes les actions : seul le Malus de la
|
||||
* Blessure la plus grave compte (non cumulatif).
|
||||
* @param {Actor} actor
|
||||
* @returns {number} 0, 1, 2 ou 3
|
||||
*/
|
||||
static woundMalus(actor) {
|
||||
if (!actor?.system) return 0
|
||||
if (actor.system.deadlyWound?.value > 0) return 3
|
||||
if (actor.system.majorWound?.value > 0) return 2
|
||||
if (actor.system.minorWound?.value > 0) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique +1 cercle de blessure, en montant en gravité si les cercles du
|
||||
* niveau correspondant sont déjà pleins (règles p.42 : si tous les cercles
|
||||
* d'une Blessure sont cochés, la suivante est de gravité supérieure).
|
||||
* @param {Actor} actor
|
||||
* @param {string} woundCategory 'minorWound' | 'majorWound' | 'deadlyWound'
|
||||
* @returns {Promise<number|boolean>} nouvelle valeur, ou false si refusé
|
||||
* @returns {Promise<Object|boolean|null>}
|
||||
* - false si refusé (permissions) ou null si invalide
|
||||
* - { category, value, escalated, death } si appliqué (death si tout est plein)
|
||||
*/
|
||||
static async applyWound(actor, woundCategory) {
|
||||
if (!actor || !woundCategory) return null
|
||||
if (!this.canApplyWound(actor)) return false
|
||||
const current = actor.system?.[woundCategory]?.value || 0
|
||||
const max = actor.system?.[woundCategory]?.max ?? Number.POSITIVE_INFINITY
|
||||
const next = Math.min(current + 1, max)
|
||||
await actor.update({ [`system.${woundCategory}.value`]: next })
|
||||
return next
|
||||
const startIdx = this.WOUND_ORDER.indexOf(woundCategory)
|
||||
if (startIdx === -1) return null
|
||||
for (let i = startIdx; i < this.WOUND_ORDER.length; i++) {
|
||||
const cat = this.WOUND_ORDER[i]
|
||||
const sys = actor.system?.[cat]
|
||||
const max = sys?.max ?? Number.POSITIVE_INFINITY
|
||||
const value = sys?.value || 0
|
||||
if (value < max) {
|
||||
await actor.update({ [`system.${cat}.value`]: value + 1 })
|
||||
return { category: cat, value: value + 1, escalated: cat !== woundCategory, death: false }
|
||||
}
|
||||
}
|
||||
return { category: woundCategory, value: null, escalated: false, death: true }
|
||||
}
|
||||
|
||||
// ── Résolution ──────────────────────────────────────────────────────
|
||||
@@ -457,10 +489,18 @@ export class VermineExchange {
|
||||
ui.notifications.warn(game.i18n.localize('VERMINE.error_no_permission'))
|
||||
return
|
||||
}
|
||||
const next = await this.applyWound(defender, wound)
|
||||
if (next === false) return
|
||||
const result = await this.applyWound(defender, wound)
|
||||
if (result === false) return
|
||||
btn.disabled = true
|
||||
btn.innerHTML = `<i class="fas fa-check"></i> ${game.i18n.localize('VERMINE.wound_applied')}`
|
||||
let label = game.i18n.localize('VERMINE.wound_applied')
|
||||
if (result?.death) {
|
||||
label = game.i18n.localize('VERMINE.wound_death')
|
||||
} else if (result?.escalated) {
|
||||
const from = game.i18n.localize(this.woundLabelKey(wound))
|
||||
const to = game.i18n.localize(this.woundLabelKey(result.category))
|
||||
label = game.i18n.format('VERMINE.wound_escalated', { from, to })
|
||||
}
|
||||
btn.innerHTML = `<i class="fas fa-check"></i> ${label}`
|
||||
// Persiste l'état du bouton seulement si l'utilisateur peut modifier le message
|
||||
if (game.user.isGM || attackMessage.isOwner) {
|
||||
const msgEl = btn.closest('.vermine-roll-message')
|
||||
|
||||
Reference in New Issue
Block a user