Fix apv2, WIP

This commit is contained in:
2026-06-06 10:21:24 +02:00
parent 6cec1da910
commit 9b77a0c552
130 changed files with 12850 additions and 2830 deletions
+20
View File
@@ -0,0 +1,20 @@
export { VermineBaseActorSheet } from "./base-actor-sheet.mjs"
export { VermineBaseItemSheet } from "./base-item-sheet.mjs"
export { VermineCharacterSheetV2 } from "./character-sheet.mjs"
export { VermineNpcSheetV2 } from "./npc-sheet.mjs"
export { VermineGroupSheetV2 } from "./group-sheet.mjs"
export { VermineCreatureSheetV2 } from "./creature-sheet.mjs"
export {
VermineItemSheetV2,
VermineWeaponSheetV2,
VermineDefenseSheetV2,
VermineVehicleSheetV2,
VermineAbilitySheetV2,
VermineSpecialtySheetV2,
VermineBackgroundSheetV2,
VermineTraumaSheetV2,
VermineEvolutionSheetV2,
VermineRumorSheetV2,
VermineTargetSheetV2,
VermineRiteSheetV2
} from "./item-sheets.mjs"
@@ -0,0 +1,207 @@
import { onManageActiveEffect } from "../../system/effects.mjs"
const { HandlebarsApplicationMixin } = foundry.applications.api
/**
* Fiche de base pour tous les acteurs Vermine 2047 (ApplicationV2).
* Remplace VermineActorSheet (AppV1).
*/
export class VermineBaseActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
// ── Mode édition / jeu ──────────────────────────────────────────────
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
_sheetMode = this.constructor.SHEET_MODES.PLAY
get isPlayMode() { return this._sheetMode === this.constructor.SHEET_MODES.PLAY }
get isEditMode() { return this._sheetMode === this.constructor.SHEET_MODES.EDIT }
// ── Options par défaut ──────────────────────────────────────────────
static DEFAULT_OPTIONS = {
classes: ["vermine2047", "actor"],
position: { width: 800, height: "auto" },
form: { submitOnChange: true },
window: { resizable: true },
dragDrop: [{ dragSelector: ".item", dropSelector: null }],
actions: {
editImage: VermineBaseActorSheet.#onEditImage,
toggleSheet: VermineBaseActorSheet.#onToggleSheet,
edit: VermineBaseActorSheet.#onItemEdit,
delete: VermineBaseActorSheet.#onItemDelete,
create: VermineBaseActorSheet.#onItemCreate,
roll: VermineBaseActorSheet.#onRollItem,
clickRadio: VermineBaseActorSheet.#onClickRadioHexa,
effectControl: VermineBaseActorSheet.#onEffectControl,
chooseTotem: VermineBaseActorSheet.#onChooseTotem
}
}
// ── Drag & Drop ─────────────────────────────────────────────────────
#dragDrop
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#createDragDropHandlers() {
return this.options.dragDrop.map(d => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this)
}
d.callbacks = {
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this)
}
return new foundry.applications.ux.DragDrop.implementation(d)
})
}
_canDragStart() { return this.isEditable }
_canDragDrop() { return this.isEditable }
// ── Contexte commun ─────────────────────────────────────────────────
async _prepareContext() {
return {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
actor: this.document,
system: this.document.system,
source: this.document.toObject(),
config: CONFIG.VERMINE,
rollData: this.document.getRollData(),
isGM: game.user.isGM,
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isEditable: this.isEditable
}
}
// ── Rendu ───────────────────────────────────────────────────────────
async _onRoll(event) {
event.preventDefault()
const el = event.currentTarget
const type = el.dataset.type
const label = el.dataset.label
if (!type || !label) return
const { default: RollDialog } = await import("../../system/dialogs/rollDialog.mjs")
const dialog = await RollDialog.create({
actorId: this.document.id,
rolltype: type,
label
})
if (dialog) dialog.render(true)
}
// ── Actions ─────────────────────────────────────────────────────────
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach(d => d.bind(this.element))
this.element.querySelectorAll(".rollable").forEach(el => {
el.addEventListener("click", this._onRoll.bind(this))
})
// Auto-fill empty number inputs on change to prevent validation errors
this.element.addEventListener("change", e => {
const input = e.target
if (input?.type === "number" && !input.value && input.name && input !== document.activeElement) {
input.value = "0"
}
}, { capture: true })
}
/** @override */
async _onDropItem(item) {
const doc = item instanceof foundry.abstract.Document ? item : await fromUuid(item.uuid)
if (!doc) return
const itemData = doc.toObject()
await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
}
static #onToggleSheet() {
const modes = this.constructor.SHEET_MODES
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
this.render()
}
static async #onEditImage(event, target) {
const attr = target.dataset.edit ?? "img"
const current = foundry.utils.getProperty(this.document, attr)
const fp = new FilePicker({
current,
type: "image",
callback: (path) => this.document.update({ [attr]: path }),
top: this.position.top + 40,
left: this.position.left + 10
})
return fp.browse()
}
static async #onItemEdit(event, target) {
const id = target.closest("[data-item-id]")?.dataset?.itemId
const uuid = target.closest("[data-item-uuid]")?.dataset?.itemUuid
let item
if (uuid) item = await fromUuid(uuid)
if (!item) item = this.document.items.get(id)
item?.sheet.render(true)
}
static async #onItemDelete(event, target) {
const itemUuid = target.closest("[data-item-uuid]")?.dataset?.itemUuid
if (itemUuid) {
const item = await fromUuid(itemUuid)
await item?.deleteDialog()
return
}
const id = target.closest("[data-item-id]")?.dataset?.itemId
const item = this.document.items.get(id)
await item?.deleteDialog()
}
static async #onItemCreate(event, target) {
const type = target.dataset.type
if (!type) return
const name = game.i18n.localize("ITEMS.new_" + type)
await this.document.createEmbeddedDocuments("Item", [{ name, type }])
}
static async #onRollItem(event, target) {
const id = target.closest("[data-item-id]")?.dataset?.itemId
if (!id) return
const item = this.document.items.get(id)
item?.roll()
}
static #onClickRadioHexa(event, target) {
event.preventDefault()
event.stopPropagation()
const input = target
const update = {}
let current = this.document
const propTree = input.name.split(".")
for (const prop of propTree) {
current = current[prop]
}
if (current != input.value) {
update[input.name] = parseInt(input.value)
} else {
update[input.name] = parseInt(input.value) - 1
}
this.document.update(update)
}
static #onEffectControl(event, target) {
onManageActiveEffect(event, this.document)
}
static async #onChooseTotem(event, target) {
const { TotemPicker } = await import("../../system/applications.mjs")
new TotemPicker(target, this.document).render(true)
}
}
@@ -0,0 +1,114 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
/**
* Fiche de base pour tous les items Vermine 2047 (ApplicationV2).
*/
export class VermineBaseItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
// ── Mode édition ────────────────────────────────────────────────────
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
_sheetMode = this.constructor.SHEET_MODES.PLAY
get isPlayMode() { return this._sheetMode === this.constructor.SHEET_MODES.PLAY }
get isEditMode() { return this._sheetMode === this.constructor.SHEET_MODES.EDIT }
// ── Options par défaut ──────────────────────────────────────────────
static DEFAULT_OPTIONS = {
classes: ["vermine2047", "item"],
position: { width: 560, height: "auto" },
form: { submitOnChange: true },
window: { resizable: true },
actions: {
editImage: VermineBaseItemSheet.#onEditImage,
toggleSheet: VermineBaseItemSheet.#onToggleSheet,
clickDamage: VermineBaseItemSheet.#onClickDamage,
openTraits: VermineBaseItemSheet.#onOpenTraits
}
}
// ── Drag & Drop ─────────────────────────────────────────────────────
#dragDrop
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#createDragDropHandlers() {
if (!this.options.dragDrop) return []
return this.options.dragDrop.map(d => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this)
}
d.callbacks = {
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this)
}
return new foundry.applications.ux.DragDrop.implementation(d)
})
}
_canDragStart() { return this.isEditable }
_canDragDrop() { return this.isEditable }
// ── Contexte commun ─────────────────────────────────────────────────
async _prepareContext() {
return {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
source: this.document.toObject(),
config: CONFIG.VERMINE,
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isEditable: this.isEditable
}
}
// ── Rendu ───────────────────────────────────────────────────────────
_onRender(context, options) {
this.#dragDrop.forEach(d => d.bind(this.element))
}
// ── Actions ─────────────────────────────────────────────────────────
static #onToggleSheet() {
const modes = this.constructor.SHEET_MODES
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
this.render()
}
static async #onEditImage(event, target) {
const attr = target.dataset.edit ?? "img"
const current = foundry.utils.getProperty(this.document, attr)
const fp = new FilePicker({
current,
type: "image",
callback: (path) => this.document.update({ [attr]: path }),
top: this.position.top + 40,
left: this.position.left + 10
})
return fp.browse()
}
static #onClickDamage(event, target) {
// Les radios de dégâts sont 1-based dans le template (value="{{@index}}" avec index 1..max)
// mais le stockage est 0-based. On soustrait 1 avant de sauvegarder.
const prop = target.name
const value = parseInt(target.value) - 1
this.document.update({ [prop]: value })
}
static async #onOpenTraits(event, target) {
const { TraitSelector } = await import("../../system/applications.mjs")
new TraitSelector(this.document).render(true)
}
}
@@ -0,0 +1,98 @@
import { VermineBaseActorSheet } from "./base-actor-sheet.mjs"
export class VermineCharacterSheetV2 extends VermineBaseActorSheet {
static DEFAULT_OPTIONS = {
classes: ["character"],
position: { width: 860, height: 720 },
window: { contentClasses: ["character-content"] },
actions: {
addSpecialty: VermineCharacterSheetV2.#onAddSpecialty
}
}
static PARTS = {
main: { template: "systems/vermine2047/templates/actor/appv2/character-main.hbs" },
tabs: { template: "templates/generic/tab-navigation.hbs" },
abilities: { template: "systems/vermine2047/templates/actor/appv2/character-abilities.hbs" },
totem: { template: "systems/vermine2047/templates/actor/appv2/character-totem.hbs" },
equipment: { template: "systems/vermine2047/templates/actor/appv2/character-equipment.hbs" },
stories: { template: "systems/vermine2047/templates/actor/appv2/character-stories.hbs" },
combat: { template: "systems/vermine2047/templates/actor/appv2/character-combat.hbs" }
}
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" }
}
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
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
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 = doc.itemTypes.vehicle
break
case "stories":
context.tab = context.tabs.stories
break
case "combat":
context.tab = context.tabs.combat
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
context.effects = prepareActiveEffectCategories(doc.effects)
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])
}
}
@@ -0,0 +1,67 @@
import { VermineBaseActorSheet } from "./base-actor-sheet.mjs"
export class VermineCreatureSheetV2 extends VermineBaseActorSheet {
static DEFAULT_OPTIONS = {
classes: ["creature"],
position: { width: 700, height: 650 },
window: { contentClasses: ["creature-content"] }
}
static PARTS = {
main: { template: "systems/vermine2047/templates/actor/appv2/creature-main.hbs" },
tabs: { template: "templates/generic/tab-navigation.hbs" },
info: { template: "systems/vermine2047/templates/actor/appv2/creature-info.hbs" },
stats: { template: "systems/vermine2047/templates/actor/appv2/creature-stats.hbs" },
combat: { template: "systems/vermine2047/templates/actor/appv2/creature-combat.hbs" },
effects: { template: "systems/vermine2047/templates/actor/appv2/creature-effects.hbs" }
}
tabGroups = { sheet: "info" }
#getTabs() {
const tabs = {
info: { id: "info", group: "sheet", icon: "fas fa-info-circle", label: "VERMINE.information" },
stats: { id: "stats", group: "sheet", icon: "fas fa-chart-bar", label: "VERMINE.stats" },
combat: { id: "combat", group: "sheet", icon: "fas fa-sword", label: "VERMINE.combat" },
effects: { id: "effects", group: "sheet", icon: "fas fa-magic", label: "UI.effects.name" }
}
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
switch (partId) {
case "main": break
case "info":
context.tab = context.tabs.info
break
case "stats":
context.tab = context.tabs.stats
context.patternLabel = doc.system.pattern?.value ? CONFIG.VERMINE.creaturePatternLevels[doc.system.pattern.value]?.label : ""
context.sizeLabel = doc.system.size?.value || ""
context.roleLabel = doc.system.role?.value ? CONFIG.VERMINE.creatureRoleLevels[doc.system.role.value]?.label : ""
context.packLabel = doc.system.pack?.value || game.i18n.localize("VERMINE.none")
break
case "combat":
context.tab = context.tabs.combat
break
case "effects":
context.tab = context.tabs.effects
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
context.effects = prepareActiveEffectCategories(doc.effects)
break
}
return context
}
}
+139
View File
@@ -0,0 +1,139 @@
import { VermineBaseActorSheet } from "./base-actor-sheet.mjs"
export 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
}
}
static PARTS = {
main: { template: "systems/vermine2047/templates/actor/appv2/group-main.hbs" },
tabs: { template: "templates/generic/tab-navigation.hbs" },
info: { template: "systems/vermine2047/templates/actor/appv2/group-info.hbs" },
gear: { template: "systems/vermine2047/templates/actor/appv2/group-gear.hbs" },
road: { template: "systems/vermine2047/templates/actor/appv2/group-road.hbs" },
reserve: { template: "systems/vermine2047/templates/actor/appv2/group-reserve.hbs" }
}
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 }
}
}
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
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
context.gear = doc.itemTypes.item
context.weapons = doc.itemTypes.weapon
context.defenses = doc.itemTypes.defense
break
case "road":
context.tab = context.tabs.road
context.vehicles = doc.itemTypes.vehicle
break
case "reserve":
context.tab = context.tabs.reserve
break
}
return context
}
// 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)
}
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 })
}
}
@@ -0,0 +1,73 @@
import { VermineBaseItemSheet } from "./base-item-sheet.mjs"
// ── Item générique ────────────────────────────────────────────────────
export class VermineItemSheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["item-gear"], position: { width: 520 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-item-sheet.hbs" } }
}
// ── Arme ──────────────────────────────────────────────────────────────
export class VermineWeaponSheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["weapon"], position: { width: 520 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-weapon-sheet.hbs" } }
}
// ── Défense ───────────────────────────────────────────────────────────
export class VermineDefenseSheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["defense"], position: { width: 520 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-defense-sheet.hbs" } }
}
// ── Véhicule ──────────────────────────────────────────────────────────
export class VermineVehicleSheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["vehicle"], position: { width: 520 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-vehicle-sheet.hbs" } }
}
// ── Capacité ──────────────────────────────────────────────────────────
export class VermineAbilitySheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["ability"], position: { width: 560 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-ability-sheet.hbs" } }
}
// ── Spécialité ────────────────────────────────────────────────────────
export class VermineSpecialtySheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["specialty"], position: { width: 400 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-specialty-sheet.hbs" } }
}
// ── Historique ────────────────────────────────────────────────────────
export class VermineBackgroundSheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["background"], position: { width: 520 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-background-sheet.hbs" } }
}
// ── Traumatisme ───────────────────────────────────────────────────────
export class VermineTraumaSheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["trauma"], position: { width: 520 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-trauma-sheet.hbs" } }
}
// ── Évolution ─────────────────────────────────────────────────────────
export class VermineEvolutionSheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["evolution"], position: { width: 520 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-evolution-sheet.hbs" } }
}
// ── Rumeur ────────────────────────────────────────────────────────────
export class VermineRumorSheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["rumor"], position: { width: 520 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-rumor-sheet.hbs" } }
}
// ── Cible ─────────────────────────────────────────────────────────────
export class VermineTargetSheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["target"], position: { width: 520 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-target-sheet.hbs" } }
}
// ── Rite ──────────────────────────────────────────────────────────────
export class VermineRiteSheetV2 extends VermineBaseItemSheet {
static DEFAULT_OPTIONS = { classes: ["rite"], position: { width: 520 } }
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-rite-sheet.hbs" } }
}
+76
View File
@@ -0,0 +1,76 @@
import { VermineBaseActorSheet } from "./base-actor-sheet.mjs"
export class VermineNpcSheetV2 extends VermineBaseActorSheet {
static DEFAULT_OPTIONS = {
classes: ["npc"],
position: { width: 750, height: 680 },
window: { contentClasses: ["npc-content"] }
}
static PARTS = {
main: { template: "systems/vermine2047/templates/actor/appv2/npc-main.hbs" },
tabs: { template: "templates/generic/tab-navigation.hbs" },
characteristics: { template: "systems/vermine2047/templates/actor/appv2/npc-characteristics.hbs" },
skills: { template: "systems/vermine2047/templates/actor/appv2/npc-skills.hbs" },
threat: { template: "systems/vermine2047/templates/actor/appv2/npc-threat.hbs" },
combat: { template: "systems/vermine2047/templates/actor/appv2/npc-combat.hbs" },
notes: { template: "systems/vermine2047/templates/actor/appv2/npc-notes.hbs" }
}
tabGroups = { sheet: "characteristics" }
#getTabs() {
const tabs = {
characteristics: { id: "characteristics", group: "sheet", icon: "fas fa-dice", label: "VERMINE.abilities" },
skills: { id: "skills", group: "sheet", icon: "fas fa-brain", label: "VERMINE.skills" },
threat: { id: "threat", group: "sheet", icon: "fas fa-exclamation-triangle", label: "ADVERSITY.threat" },
combat: { id: "combat", group: "sheet", icon: "fas fa-sword", label: "VERMINE.combat" },
notes: { id: "notes", group: "sheet", icon: "fas fa-sticky-note", label: "IDENTITY.notes" }
}
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
}
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")
}
}
async _preparePartContext(partId, context) {
const doc = this.document
switch (partId) {
case "main": break
case "characteristics":
context.tab = context.tabs.characteristics
break
case "skills":
context.tab = context.tabs.skills
break
case "threat":
context.tab = context.tabs.threat
break
case "combat":
context.tab = context.tabs.combat
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
context.effects = prepareActiveEffectCategories(doc.effects)
break
case "notes":
context.tab = context.tabs.notes
break
}
return context
}
}