- Dialogue d'apprentissage personnage : 4 nouveaux types d'achat.
- Capacité de Totem : options filtrées (mode de jeu, niveau de
groupe, totem, non-apprises), difficulté = valeur d'Apprentissage,
limites 3×N1 / 1×N2-N3 / max 5, marque system.learned de l'item
(convention type character) et learned.ability du personnage.
- Élément d'Historique : difficulté 9, handicap 1 si astérisque
(background.asterisk), marque learned.background + item.
- Test de Réputation (réduire/augmenter) : réduire = difficulté
10 − score visé (−1 pt), augmenter = difficulté = réputation
actuelle (+1 par succès), bornes 0-10, marque learned.reputation.
- Évolution : option Dé de Totem Adapté ×2 quand Adapté > Humain
(1D remplacé par un dé adapté compté ×2 via VermineUtils.roll).
- Correction règles : les Dés d'Expérience sont dépensés même en cas
d'échec du jet d'apprentissage (décompte hors #applySuccess).
- Expérience de Groupe : nouveau dialogue d'apprentissage de Groupe
(niveau, réserve, capacité de totem), réserve d'expérience et phase
dans system.experience, boutons sur la fiche Groupe, reset des
limites par phase (level/reserve/ability).
- Phase d'expérience : filtre par groupe sélectionné, associe chaque
personnage à son premier groupe, reset des limites de tous les
nouveaux types d'apprentissage.
- Corrections : l'attaque de créature n'ajoute plus la Réaction du Rôle
(regles_creatures.txt), suppression de la case XP des en-têtes PJ
(legacy et AppV2).
- Divers : .eslintrc.js (règles inexistantes/obsolètes retirées),
libellé jaune de dialogue en couleur chitine, i18n FR/EN, chips
« appris » sur l'onglet expérience du personnage.
176 lines
6.9 KiB
JavaScript
176 lines
6.9 KiB
JavaScript
import VermineBaseActorSheet from "./base-actor-sheet.mjs"
|
|
|
|
export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
|
|
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["group"],
|
|
position: { width: 700, height: 600 },
|
|
window: { contentClasses: ["group-content"] },
|
|
actions: {
|
|
chooseTotem: VermineGroupSheetV2.#onChooseTotem,
|
|
chooseActor: VermineGroupSheetV2.#onChooseActor,
|
|
deleteMember: VermineGroupSheetV2.#onDeleteMember,
|
|
deleteEncounter: VermineGroupSheetV2.#onDeleteEncounter,
|
|
deleteObjective: VermineGroupSheetV2.#onDeleteObjective,
|
|
addObjective: VermineGroupSheetV2.#onAddObjective,
|
|
createTotemAbility: VermineGroupSheetV2.#onCreateTotemAbility,
|
|
openLearning: VermineGroupSheetV2.#onOpenLearning,
|
|
openExperiencePhase: VermineGroupSheetV2.#onOpenExperiencePhase
|
|
}
|
|
}
|
|
|
|
static PARTS = {
|
|
main: { template: "systems/vermine2047/templates/actor/appv2/group-main.hbs", scrollable: [""] },
|
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
|
info: { template: "systems/vermine2047/templates/actor/appv2/group-info.hbs", scrollable: [""] },
|
|
gear: { template: "systems/vermine2047/templates/actor/appv2/group-gear.hbs", scrollable: [""] },
|
|
road: { template: "systems/vermine2047/templates/actor/appv2/group-road.hbs", scrollable: [""] },
|
|
reserve: { template: "systems/vermine2047/templates/actor/appv2/group-reserve.hbs", scrollable: [""] }
|
|
}
|
|
|
|
tabGroups = { sheet: "info" }
|
|
|
|
#getTabs() {
|
|
const tabs = {
|
|
info: { id: "info", group: "sheet", icon: "fas fa-star", label: "VERMINE.information" },
|
|
gear: { id: "gear", group: "sheet", icon: "fas fa-gear", label: "VERMINE.gear" },
|
|
road: { id: "road", group: "sheet", icon: "fas fa-route", label: "VERMINE.road" },
|
|
reserve: { id: "reserve", group: "sheet", icon: "fas fa-users", label: "VERMINE.reserve" }
|
|
}
|
|
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()
|
|
// Résoudre les IDs des membres/encounters en données acteur
|
|
context.resolvedMembers = {}
|
|
if (this.document.system.members?.length > 0) {
|
|
for (const memberId of this.document.system.members) {
|
|
const a = game.actors.get(memberId)
|
|
if (a) context.resolvedMembers[memberId] = { name: a.name, id: a.id }
|
|
}
|
|
}
|
|
context.resolvedEncounters = {}
|
|
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,
|
|
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
|
|
}
|
|
|
|
async _preparePartContext(partId, context) {
|
|
const doc = this.document
|
|
switch (partId) {
|
|
case "main": break
|
|
case "info":
|
|
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")
|
|
break
|
|
case "gear":
|
|
context.tab = context.tabs.gear
|
|
context.gear = doc.itemTypes.item
|
|
context.weapons = doc.itemTypes.weapon
|
|
context.defenses = doc.itemTypes.defense
|
|
context.vehicles = this.#getOwnedVehicles()
|
|
break
|
|
case "road":
|
|
context.tab = context.tabs.road
|
|
break
|
|
case "reserve":
|
|
context.tab = context.tabs.reserve
|
|
break
|
|
}
|
|
return context
|
|
}
|
|
|
|
/** Véhicules (acteurs) appartenant à ce groupe via system.ownerId. */
|
|
#getOwnedVehicles() {
|
|
return game.actors.filter(a => a.type === "vehicle" && a.system.ownerId === this.document.id)
|
|
}
|
|
|
|
// Actions : délégation aux applications AppV1 existantes pour TotemPicker/ActorPicker
|
|
static async #onChooseTotem(event, target) {
|
|
const { TotemPicker } = await import("../../system/applications.mjs")
|
|
// 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")
|
|
new ActorPicker(target, this.document).render(true)
|
|
}
|
|
static #onDeleteMember(event, target) {
|
|
const li = target.closest("li.actor")
|
|
if (!li) return
|
|
const actorId = li.dataset.actorId
|
|
const idx = this.document.system.members.indexOf(actorId)
|
|
if (idx !== -1) {
|
|
const members = [...this.document.system.members]
|
|
members.splice(idx, 1)
|
|
this.document.update({ "system.members": members })
|
|
}
|
|
}
|
|
static #onDeleteEncounter(event, target) {
|
|
const li = target.closest("li.actor")
|
|
if (!li) return
|
|
const actorId = li.dataset.actorId
|
|
const idx = this.document.system.encounters.indexOf(actorId)
|
|
if (idx !== -1) {
|
|
const encounters = [...this.document.system.encounters]
|
|
encounters.splice(idx, 1)
|
|
this.document.update({ "system.encounters": encounters })
|
|
}
|
|
}
|
|
static #onDeleteObjective(event, target) {
|
|
const type = target.dataset.type
|
|
const index = parseInt(target.dataset.index)
|
|
if (isNaN(index)) return
|
|
const objectives = foundry.utils.duplicate(this.document.system.objectives || { major: [], minor: [] })
|
|
objectives[type].splice(index, 1)
|
|
this.document.update({ "system.objectives": objectives })
|
|
}
|
|
static #onAddObjective(event, target) {
|
|
const type = target.dataset.type === "major_objective" ? "major" : "minor"
|
|
const objectives = foundry.utils.duplicate(this.document.system.objectives || { major: [], minor: [] })
|
|
objectives[type].push("")
|
|
this.document.update({ "system.objectives": objectives })
|
|
}
|
|
|
|
static async #onCreateTotemAbility(event, target) {
|
|
const name = game.i18n.localize("ITEMS.new_ability")
|
|
await this.document.createEmbeddedDocuments("Item", [{ name, type: "ability", system: { type: "totem" } }])
|
|
}
|
|
|
|
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: GroupLearningDialog } = await import("../../system/dialogs/groupLearningDialog.mjs")
|
|
const dialog = await GroupLearningDialog.create({ actorId: this.document.id })
|
|
if (dialog) dialog.render(true)
|
|
}
|
|
}
|