Tâche 0 : - item.mjs : champ equipped sur items génériques + toggle fiche - nouveau module/system/traits.mjs (helpers partagés : equippedItems, lourdMalus, mobilityHandicap, feticheItem…) Phase 2 — Combat : - Lourd : malus cumulés auto (Vigueur < n) dans le dialogue + pool - Rapide : handicap (n) pré-rempli dans la défense (character + PNJ) + note carte - Rafale : case tir en rafale (+2 difficulté), +n réussites si réussi (bonusOnSuccess) - Portée : portée effective n × Vigueur affichée - Maniable/Mobilité : note de rappel (dialogue + carte) - Incapacitant : bouton « Jet de Santé (n) » sur la carte d'échange - getLabel() : libellés caractéristiques/compétences localisés Phase 3 — Jets d'action : - Ponctuel : annule jusqu'à n Handicaps, consommé au jet (Traits supprimé à 0) - Pratique : outil sélectionné +2D au lieu de +1D - Intimidant : case « Brandit » → -1D Psychologie ou Relance 1D - Malus : champ « Malus temporaires » manuel déduit du pool - Durée : note « n tours » sur la carte de jet d'arme - roll() : transmission effectiveRange/mobilityHandicap/ponctuelUsed/intimidant (corrige l'affichage Portée/Mobilité) Phase 4 — Acteur : - Fétiche : +1D Effort ET Sang-Froid (max dérivé, config corrigé), alerte 2+ fétiches, action « Perdre le fétiche » (-2D, max -1D persistant feticheLoss) - Zone : note + bouton « Appliquer aux cibles de la zone » (#onApplyZone) CSS : styles LESS pour les nouvelles classes (fetiche-block, exchange-reminder/zone, traits-reminder, tool-bonus/ponctuel, duree-note) i18n : 29 clés fr/en TRAITS_PLAN.md : plan complet cochée
193 lines
8.5 KiB
JavaScript
193 lines
8.5 KiB
JavaScript
import VermineBaseActorSheet from "./base-actor-sheet.mjs"
|
|
|
|
export default class VermineCharacterSheetV2 extends VermineBaseActorSheet {
|
|
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["character"],
|
|
position: { width: 920, height: 720 },
|
|
window: { contentClasses: ["character-content"] },
|
|
actions: {
|
|
addSpecialty: VermineCharacterSheetV2.#onAddSpecialty,
|
|
openExperiencePhase: VermineCharacterSheetV2.#onOpenExperiencePhase,
|
|
openLearning: VermineCharacterSheetV2.#onOpenLearning,
|
|
validatePhase: VermineCharacterSheetV2.#onValidatePhase,
|
|
buyEvolution: VermineCharacterSheetV2.#onBuyEvolution,
|
|
openTotemDice: VermineCharacterSheetV2.#onOpenTotemDice,
|
|
loseFetiche: VermineCharacterSheetV2.#onLoseFetiche
|
|
}
|
|
}
|
|
|
|
static PARTS = {
|
|
main: { template: "systems/vermine2047/templates/actor/appv2/character-main.hbs", scrollable: [""] },
|
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
|
abilities: { template: "systems/vermine2047/templates/actor/appv2/character-abilities.hbs", scrollable: [""] },
|
|
totem: { template: "systems/vermine2047/templates/actor/appv2/character-totem.hbs", scrollable: [""] },
|
|
equipment: { template: "systems/vermine2047/templates/actor/appv2/character-equipment.hbs", scrollable: [""] },
|
|
stories: { template: "systems/vermine2047/templates/actor/appv2/character-stories.hbs", scrollable: [""] },
|
|
combat: { template: "systems/vermine2047/templates/actor/appv2/character-combat.hbs", scrollable: [""] },
|
|
experience: { template: "systems/vermine2047/templates/actor/appv2/character-experience.hbs", scrollable: [""] }
|
|
}
|
|
|
|
tabGroups = { sheet: "abilities" }
|
|
|
|
#getTabs() {
|
|
const tabs = {
|
|
abilities: { id: "abilities", group: "sheet", icon: "fas fa-address-card", label: "VERMINE.tabs.abilities" },
|
|
totem: { id: "totem", group: "sheet", icon: "fas fa-star", label: "VERMINE.tabs.totem" },
|
|
equipment: { id: "equipment", group: "sheet", icon: "fas fa-hammer", label: "VERMINE.tabs.equipment" },
|
|
stories: { id: "stories", group: "sheet", icon: "fas fa-book-open-reader", label: "VERMINE.tabs.stories" },
|
|
combat: { id: "combat", group: "sheet", icon: "fas fa-medal", label: "VERMINE.tabs.combat" },
|
|
experience: { id: "experience", group: "sheet", icon: "fas fa-graduation-cap", label: "VERMINE.tabs.experience" }
|
|
}
|
|
for (const v of Object.values(tabs)) {
|
|
v.active = this.tabGroups[v.group] === v.id
|
|
v.cssClass = v.active ? "active" : ""
|
|
}
|
|
return tabs
|
|
}
|
|
|
|
async _prepareContext() {
|
|
const context = await super._prepareContext()
|
|
context.tabs = this.#getTabs()
|
|
return context
|
|
}
|
|
|
|
async _preparePartContext(partId, context) {
|
|
const doc = this.document
|
|
context.systemFields = doc.system.schema.fields
|
|
switch (partId) {
|
|
case "main": break
|
|
case "abilities":
|
|
context.tab = context.tabs.abilities
|
|
context.specialties = doc.itemTypes.specialty
|
|
break
|
|
case "totem":
|
|
context.tab = context.tabs.totem
|
|
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
|
|
context.rites = doc.itemTypes.rite
|
|
break
|
|
case "equipment":
|
|
context.tab = context.tabs.equipment
|
|
context.gear = doc.itemTypes.item
|
|
context.weapons = doc.itemTypes.weapon
|
|
context.defenses = doc.itemTypes.defense
|
|
context.vehicles = game.actors.filter(a => a.type === "vehicle" && a.system.ownerId === doc.id)
|
|
break
|
|
case "stories":
|
|
context.tab = context.tabs.stories
|
|
break
|
|
case "combat": {
|
|
context.tab = context.tabs.combat
|
|
context.equippedWeapons = doc.itemTypes.weapon.filter(i => i.system.equipped)
|
|
context.equippedDefenses = doc.itemTypes.defense.filter(i => i.system.equipped)
|
|
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
|
|
context.effects = prepareActiveEffectCategories(doc.effects)
|
|
const { VermineTraits } = await import("../../system/traits.mjs")
|
|
const feticheItems = VermineTraits.equippedItems(doc, 'fetiche')
|
|
context.feticheItems = feticheItems
|
|
context.fetiche = feticheItems[0] ?? null
|
|
context.feticheMultiple = feticheItems.length > 1
|
|
const loss = doc.system.experience?.feticheLoss
|
|
context.feticheLost = Boolean(loss?.self_control || loss?.effort)
|
|
break
|
|
}
|
|
case "experience":
|
|
context.tab = context.tabs.experience
|
|
context.specialtyCount = doc.itemTypes.specialty.length
|
|
const ex = doc.system.experience
|
|
context.experience = ex
|
|
context.collectiveTotal = (ex.collective.danger + ex.collective.discoveries + ex.collective.duration)
|
|
context.individualTotal = (ex.individual.implication + ex.individual.influence + ex.individual.interpretation)
|
|
context.currentPhase = ex.phase
|
|
context.learnedCurrent = ex.learned.phase === ex.phase
|
|
break
|
|
}
|
|
return context
|
|
}
|
|
|
|
changeTab(tab, group, options = {}) {
|
|
super.changeTab(tab, group, options)
|
|
if (group === "sheet") {
|
|
const main = this.element?.querySelector('[data-group="sheet"][data-tab="main"]')
|
|
if (main) main.classList.add("active")
|
|
}
|
|
}
|
|
|
|
static async #onAddSpecialty(event, target) {
|
|
const skillKey = target.dataset.skill
|
|
const name = game.i18n.localize("ITEMS.new_specialty")
|
|
const itemData = { name, type: "specialty" }
|
|
if (skillKey) itemData.system = { skill: skillKey }
|
|
await this.document.createEmbeddedDocuments("Item", [itemData])
|
|
}
|
|
|
|
static async #onOpenExperiencePhase(event, target) {
|
|
const { default: ExperiencePhaseDialog } = await import("../../system/dialogs/experiencePhaseDialog.mjs")
|
|
const dialog = await ExperiencePhaseDialog.create()
|
|
if (dialog) dialog.render(true)
|
|
}
|
|
|
|
static async #onOpenLearning(event, target) {
|
|
const { default: LearningDialog } = await import("../../system/dialogs/learningDialog.mjs")
|
|
const dialog = await LearningDialog.create({ actorId: this.document.id })
|
|
if (dialog) dialog.render(true)
|
|
}
|
|
|
|
/** Valide la phase d'Expérience courante et réinitialise les limites. */
|
|
static async #onValidatePhase() {
|
|
const { VermineExperience } = await import("../../system/experience.mjs")
|
|
await VermineExperience.resetPhaseLimits(this.document)
|
|
ui.notifications.info(game.i18n.localize("VERMINE.validate_phase_done"))
|
|
}
|
|
|
|
/** Ouvre le dialogue d'achat d'une Adaptation/Mutation (Dés d'Évolution). */
|
|
static async #onBuyEvolution(event, target) {
|
|
const { default: BuyEvolutionDialog } = await import("../../system/dialogs/buyEvolutionDialog.mjs")
|
|
const dialog = await BuyEvolutionDialog.create({ actorId: this.document.id })
|
|
if (dialog) dialog.render(true)
|
|
}
|
|
|
|
/** Ouvre le dialogue de gestion des Dés de Totems. */
|
|
static async #onOpenTotemDice(event, target) {
|
|
const { default: TotemDiceDialog } = await import("../../system/dialogs/totemDiceDialog.mjs")
|
|
const dialog = await TotemDiceDialog.create({ actorId: this.document.id })
|
|
if (dialog) dialog.render(true)
|
|
}
|
|
|
|
/**
|
|
* Action « Perdre le fétiche » : applique -2D dans chaque Réserve et
|
|
* -1D de max persistant (expérience.feticheLoss), puis retire le Trait
|
|
* Fétiche de l'objet équipé.
|
|
*/
|
|
static async #onLoseFetiche() {
|
|
const doc = this.document
|
|
if (!game.user.isGM && !doc.isOwner) {
|
|
ui.notifications.warn(game.i18n.localize("VERMINE.error_no_permission"))
|
|
return
|
|
}
|
|
const { VermineTraits } = await import("../../system/traits.mjs")
|
|
const items = VermineTraits.equippedItems(doc, 'fetiche')
|
|
if (!items.length) {
|
|
ui.notifications.warn(game.i18n.localize("VERMINE.fetiche_none"))
|
|
return
|
|
}
|
|
const loss = doc.system.experience?.feticheLoss
|
|
const sc = Math.max(0, (doc.system.attributes.self_control.value || 0) - 2)
|
|
const ef = Math.max(0, (doc.system.attributes.effort.value || 0) - 2)
|
|
await doc.update({
|
|
"system.attributes.self_control.value": sc,
|
|
"system.attributes.effort.value": ef,
|
|
"system.experience.feticheLoss.self_control": (loss?.self_control || 0) + 1,
|
|
"system.experience.feticheLoss.effort": (loss?.effort || 0) + 1
|
|
})
|
|
await items[0].update({ "system.traits.fetiche": null })
|
|
ui.notifications.info(game.i18n.localize("VERMINE.fetiche_lost"))
|
|
this.render()
|
|
}
|
|
}
|