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:
2026-08-05 09:38:36 +02:00
parent cf905cdcfb
commit d0373fab5a
59 changed files with 984 additions and 1211 deletions
+1
View File
@@ -11,6 +11,7 @@ export {
VermineWeaponSheetV2,
VermineDefenseSheetV2,
VermineAbilitySheetV2,
VermineCreatureCapacitySheetV2,
VermineSpecialtySheetV2,
VermineBackgroundSheetV2,
VermineTraumaSheetV2,
@@ -38,6 +38,7 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo
roll: VermineBaseActorSheet.#onRollItem,
attack: VermineBaseActorSheet.#onAttack,
clickRadio: VermineBaseActorSheet.#onClickRadioHexa,
clickWound: VermineBaseActorSheet.#onClickWound,
effectControl: VermineBaseActorSheet.#onEffectControl,
chooseTotem: VermineBaseActorSheet.#onChooseTotem
}
@@ -264,9 +265,10 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo
if (!id) return
const item = this.document.items.get(id)
if (!item) return
// Armes : PNJ → dialogue de combat (valeur d'Attaque) ; personnage →
// dialogue de compétence. Les PNJ n'ont ni caractéristiques ni compétences.
if (item.type === "weapon" && this.document.type === 'npc') {
// Armes : PNJ et créatures → dialogue de combat (valeur d'Attaque) ;
// personnage → dialogue de compétence. PNJ/créatures n'ont ni
// caractéristiques ni compétences.
if (item.type === "weapon" && (this.document.type === 'npc' || this.document.type === 'creature')) {
const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs")
const dialog = await CombatDialog.create({ actorId: this.document.id, weapon: item })
if (dialog) dialog.render(true)
@@ -297,6 +299,8 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo
}
static async #onToggleEquip(event, target) {
event.preventDefault()
event.stopPropagation()
const id = target.closest("[data-item-id]")?.dataset?.itemId
if (!id) return
const item = this.document.items.get(id)
@@ -322,6 +326,36 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo
this.document.update(update)
}
/**
* Coche/décoche les cases de Blessure en respectant l'ordre de gravité
* (règles p.39) : on ne peut cocher que la case suivante dans l'ordre
* (une blessure s'applique toujours au Seuil égal ou inférieur le plus
* élevé), et on ne peut pas en sauter. Décocher une case retire toutes
* celles situées après elle.
*/
static #onClickWound(event, target) {
event.preventDefault()
event.stopPropagation()
const input = target
const box = parseInt(input.value, 10) || 0 // numéro de case (1-based)
let current = this.document
const propTree = input.name.split(".")
for (const prop of propTree) {
current = current[prop]
}
const currentValue = parseInt(current, 10) || 0
let next = currentValue
if (box === currentValue + 1) {
// Cocher la case suivante dans l'ordre.
next = box
} else if (box <= currentValue) {
// Décocher : retire la case cliquée et toutes celles après.
next = box - 1
}
// Si box > currentValue + 1 : on ne permet pas de sauter des cases.
this.document.update({ [input.name]: next })
}
static #onEffectControl(event, target) {
onManageActiveEffect(event, this.document)
}
@@ -4,7 +4,7 @@ export default class VermineCharacterSheetV2 extends VermineBaseActorSheet {
static DEFAULT_OPTIONS = {
classes: ["character"],
position: { width: 860, height: 720 },
position: { width: 920, height: 720 },
window: { contentClasses: ["character-content"] },
actions: {
addSpecialty: VermineCharacterSheetV2.#onAddSpecialty,
+17 -2
View File
@@ -59,16 +59,25 @@ export default class VermineCreatureSheetV2 extends VermineBaseActorSheet {
context.roleOptions = CONFIG.VERMINE.creatureRoleLevels
context.sizeOptions = CONFIG.VERMINE.creatureSizeLevels
context.packOptions = CONFIG.VERMINE.creaturePackLevels
context.sizeLabel = doc.system.size?.value !== undefined
? game.i18n.localize(CONFIG.VERMINE.creatureSizeLevels[doc.system.size.value]?.label ?? "")
: ""
context.packLabel = doc.system.pack?.value
? game.i18n.localize(CONFIG.VERMINE.creaturePackLevels[doc.system.pack.value]?.label ?? "")
: ""
break
case "info":
context.tab = context.tabs.info
context.capacities = doc.itemTypes.creaturecapacity
break
case "stats":
context.tab = context.tabs.stats
context.patternLabel = doc.system.pattern?.value ? game.i18n.localize(CONFIG.VERMINE.creaturePatternLevels[doc.system.pattern.value]?.label ?? "") : ""
context.sizeLabel = doc.system.size?.value !== undefined ? game.i18n.localize(CONFIG.VERMINE.creatureSizeLevels[doc.system.size.value]?.label ?? "") : ""
context.roleLabel = doc.system.role?.value ? game.i18n.localize(CONFIG.VERMINE.creatureRoleLevels[doc.system.role.value]?.label ?? "") : ""
context.packLabel = doc.system.pack?.value || game.i18n.localize("VERMINE.none")
context.packLabel = doc.system.pack?.value
? `${doc.system.pack.value} (${game.i18n.localize(CONFIG.VERMINE.creaturePackLevels[doc.system.pack.value]?.label ?? "")})`
: game.i18n.localize("VERMINE.none")
// Contribution cumulée de la Taille (les modificateurs se cumulent : Taille N = somme 1..N)
const sizeLevels = CONFIG.VERMINE.creatureSizeLevels || {}
const sizeLevel = doc.system.size?.value ?? 0
@@ -97,11 +106,17 @@ export default class VermineCreatureSheetV2 extends VermineBaseActorSheet {
return context
}
/** @override - Les créatures n'ont pas de caractéristiques/compétences : on ouvre le dialogue d'attaque (pool calculé). */
/** @override - Les créatures n'ont pas de caractéristiques/compétences : on ouvre le dialogue d'attaque (pool calculé) ou de Réaction. */
async _onRoll(event) {
event.preventDefault()
const el = event.currentTarget
const type = el.dataset.type
if (type === "creature-reaction") {
const { default: NpcRollDialog } = await import("../../system/dialogs/npcRollDialog.mjs")
const dialog = await NpcRollDialog.create({ actorId: this.document.id, rolltype: "reaction" })
if (dialog) dialog.render(true)
return
}
if (type !== "creature-attack") return super._onRoll(event)
const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs")
const dialog = await CombatDialog.create({ actorId: this.document.id })
+17 -7
View File
@@ -7,6 +7,7 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
position: { width: 700, height: 600 },
window: { contentClasses: ["group-content"] },
actions: {
chooseTotem: VermineGroupSheetV2.#onChooseTotem,
chooseActor: VermineGroupSheetV2.#onChooseActor,
deleteMember: VermineGroupSheetV2.#onDeleteMember,
deleteEncounter: VermineGroupSheetV2.#onDeleteEncounter,
@@ -56,9 +57,22 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
if (this.document.system.encounters?.length > 0) {
for (const encId of this.document.system.encounters) {
const a = game.actors.get(encId)
if (a) context.resolvedEncounters[encId] = { name: a.name, id: a.id }
if (a) {
context.resolvedEncounters[encId] = {
name: a.name,
id: a.id,
type: a.type,
profile: a.system?.identity?.profile || "",
totem: a.system?.identity?.totem || ""
}
}
}
}
// Notes de voyage (riche texte)
const roadNotes = this.document.system?.roadNotes
context.enrichedRoadNotes = roadNotes
? await foundry.applications.ux.TextEditor.implementation.enrichHTML(roadNotes, { async: true })
: ""
return context
}
@@ -70,10 +84,6 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
context.tab = context.tabs.info
context.abilities = doc.itemTypes.ability.filter(i => i.system.type !== "totem")
context.totem_abilities = doc.itemTypes.ability.filter(i => i.system.type === "totem")
context.specialties = doc.itemTypes.specialty
context.backgrounds = doc.itemTypes.background
context.traumas = doc.itemTypes.trauma
context.evolutions = doc.itemTypes.evolution
break
case "gear":
context.tab = context.tabs.gear
@@ -84,7 +94,6 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
break
case "road":
context.tab = context.tabs.road
context.vehicles = this.#getOwnedVehicles()
break
case "reserve":
context.tab = context.tabs.reserve
@@ -101,7 +110,8 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
// Actions : délégation aux applications AppV1 existantes pour TotemPicker/ActorPicker
static async #onChooseTotem(event, target) {
const { TotemPicker } = await import("../../system/applications.mjs")
new TotemPicker(target, this.document).render(true)
// Le Totem du Groupe remplit automatiquement Instincts/Interdits.
new TotemPicker(target, this.document, { fillInstincts: true }).render(true)
}
static async #onChooseActor(event, target) {
const { ActorPicker } = await import("../../system/applications.mjs")
@@ -24,6 +24,12 @@ export class VermineAbilitySheetV2 extends VermineBaseItemSheet {
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-ability-sheet.hbs", scrollable: [""] } }
}
// ── Capacité de créature ────────────────────────────────────────────────
export class VermineCreatureCapacitySheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["creaturecapacity"], position: { width: 560 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-creaturecapacity-sheet.hbs", scrollable: [""] } }
}
// ── Spécialité ────────────────────────────────────────────────────────
export class VermineSpecialtySheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["specialty"], position: { width: 400 } }