import EcrymeBaseActorSheet from "./base-actor-sheet.js" import { EcrymeUtility } from "../../common/ecryme-utility.js" /** * Actor sheet for PC and NPC types using Application V2. */ export default class EcrymeActorSheet extends EcrymeBaseActorSheet { /** @override */ static DEFAULT_OPTIONS = { classes: ["pc-npc"], position: { width: 860, height: 680 }, actions: { openAnnency: EcrymeActorSheet.#onOpenAnnency, itemEdit: EcrymeActorSheet.#onItemEdit, itemDelete: EcrymeActorSheet.#onItemDelete, itemCreate: EcrymeActorSheet.#onItemCreate, subactorEdit: EcrymeActorSheet.#onSubactorEdit, subactorDelete: EcrymeActorSheet.#onSubactorDelete, rollSkill: EcrymeActorSheet.#onRollSkill, rollSpec: EcrymeActorSheet.#onRollSpec, rollSkillConfront: EcrymeActorSheet.#onRollSkillConfront, rollCephaly: EcrymeActorSheet.#onRollCephaly, rollWeaponConfront:EcrymeActorSheet.#onRollWeaponConfront, impactModify: EcrymeActorSheet.#onImpactModify, rollWeapon: EcrymeActorSheet.#onRollWeapon, lockUnlock: EcrymeActorSheet.#onLockUnlock, equipItem: EcrymeActorSheet.#onEquipItem, quantityMinus: EcrymeActorSheet.#onQuantityMinus, quantityPlus: EcrymeActorSheet.#onQuantityPlus, }, } /** @override */ static PARTS = { header: { template: "systems/fvtt-ecryme/templates/actors/partials/actor-header.hbs" }, tabs: { template: "templates/generic/tab-navigation.hbs" }, skills: { template: "systems/fvtt-ecryme/templates/actors/actor-skills.hbs" }, traits: { template: "systems/fvtt-ecryme/templates/actors/actor-traits.hbs" }, combat: { template: "systems/fvtt-ecryme/templates/actors/actor-combat.hbs" }, cephaly: { template: "systems/fvtt-ecryme/templates/actors/actor-cephaly.hbs" }, equipements:{ template: "systems/fvtt-ecryme/templates/actors/actor-equipements.hbs" }, biodata: { template: "systems/fvtt-ecryme/templates/actors/actor-biodata.hbs" }, } /** @override */ tabGroups = { primary: "skills" } /** Build tabs, conditionally adding cephaly if the module is active */ _getTabs() { const hasCephaly = EcrymeUtility.hasCephaly() const tabs = { skills: { id: "skills", group: "primary", label: "ECRY.ui.skills" }, traits: { id: "traits", group: "primary", label: "ECRY.ui.traits" }, combat: { id: "combat", group: "primary", label: "ECRY.ui.healthcombat" }, equipements:{ id: "equipements", group: "primary", label: "ECRY.ui.equipment" }, biodata: { id: "biodata", group: "primary", label: "ECRY.ui.bionotes" }, } if (hasCephaly) { // Insert cephaly after combat, rebuilding the object to preserve insertion order const ordered = {} for (const [k, v] of Object.entries(tabs)) { ordered[k] = v if (k === "combat") ordered.cephaly = { id: "cephaly", group: "primary", label: "ECRY.ui.cephaly" } } for (const tab of Object.values(ordered)) { tab.active = this.tabGroups[tab.group] === tab.id tab.cssClass = tab.active ? "active" : "" } return ordered } for (const tab of Object.values(tabs)) { tab.active = this.tabGroups[tab.group] === tab.id tab.cssClass = tab.active ? "active" : "" } return tabs } /** @override */ async _prepareContext() { const actor = this.document return { actor, system: actor.system, source: actor.toObject(), fields: actor.schema.fields, systemFields: actor.system.schema.fields, type: actor.type, img: actor.img, name: actor.name, isEditable: this.isEditable, config: game.system.ecryme.config, hasCephaly: EcrymeUtility.hasCephaly(), hasBoheme: EcrymeUtility.hasBoheme(), hasAmertume: EcrymeUtility.hasAmertume(), skills: actor.prepareSkills(), traits: actor.getRollTraits(), ideal: actor.getIdeal(), spleen: actor.getSpleen(), weapons: actor.getWeapons(), maneuvers: actor.getManeuvers(), impactsMalus: actor.getImpactsMalus(), equipments: actor.getEquipments(), cephalySkills:actor.getCephalySkills(), confrontations: actor.getConfrontations(), subActors: actor.getSubActors(), annency: actor.getAnnency(), owner: this.document.isOwner, isGM: game.user.isGM, editScore: this.options.editScore ?? true, tabs: this._getTabs(), } } /** @override */ async _preparePartContext(partId, context) { context = await super._preparePartContext(partId, context) switch (partId) { case "skills": case "traits": case "combat": case "cephaly": case "equipements": context.tab = context.tabs[partId] ?? { cssClass: "" } break case "biodata": context.tab = context.tabs.biodata context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML( this.document.system.biodata.description, { async: true } ) context.enrichedGmnotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML( this.document.system.biodata.gmnotes, { async: true } ) break } return context } // #region Drag and Drop /** Handle incoming drops: Items from sidebar/compendium, Actors as subactors */ async _onDrop(event) { const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event) if (!data?.type) return if (data.type === "Item") { const item = await fromUuid(data.uuid) if (!item) return await this.document.createEmbeddedDocuments("Item", [item.toObject()]) } else if (data.type === "Actor") { const actor = fromUuidSync(data.uuid) if (actor) await this.actor.addSubActor(actor.id) } } /** Handle outgoing drag from embedded item rows */ _onDragStart(event) { const li = event.currentTarget.closest("[data-item-id]") if (!li) return const item = this.document.items.get(li.dataset.itemId) if (!item) return event.dataTransfer.setData("text/plain", JSON.stringify(item.toDragData())) } // #endregion // #region Static Action Handlers static #onOpenAnnency(event, target) { const actorId = target.dataset.annencyId game.actors.get(actorId)?.sheet.render(true) } static #onItemEdit(event, target) { const li = target.closest("[data-item-id]") const itemId = li?.dataset.itemId ?? target.dataset.itemId this.document.items.get(itemId)?.sheet.render(true) } static async #onItemDelete(event, target) { const li = target.closest("[data-item-id]") EcrymeUtility.confirmDelete(this, $(li)).catch(() => {}) } static #onItemCreate(event, target) { const dataType = target.dataset.type this.document.createEmbeddedDocuments("Item", [{ name: "NewItem", type: dataType }], { renderSheet: true }) } static #onSubactorEdit(event, target) { const li = target.closest("[data-actor-id]") game.actors.get(li?.dataset.actorId)?.sheet.render(true) } static #onSubactorDelete(event, target) { const li = target.closest("[data-actor-id]") this.actor.delSubActor(li?.dataset.actorId) } static #onRollSkill(event, target) { this.actor.rollSkill(target.dataset.categoryKey, target.dataset.skillKey) } static #onRollSpec(event, target) { this.actor.rollSpec(target.dataset.categoryKey, target.dataset.skillKey, target.dataset.specId) } static #onRollSkillConfront(event, target) { this.actor.rollSkillConfront(target.dataset.categoryKey, target.dataset.skillKey) } static #onRollCephaly(event, target) { this.actor.rollCephalySkillConfront(target.dataset.skillKey) } static #onRollWeaponConfront(event, target) { const li = target.closest("[data-item-id]") this.actor.rollWeaponConfront(li?.dataset.itemId) } static #onImpactModify(event, target) { this.actor.modifyImpact( target.dataset.impactType, target.dataset.impactLevel, Number(target.dataset.impactModifier) ) } static #onRollWeapon(event, target) { this.actor.rollArme(target.dataset.armeId) } static #onLockUnlock(event, target) { this.options.editScore = !this.options.editScore this.render(true) } static #onEquipItem(event, target) { const li = target.closest("[data-item-id]") this.actor.equipItem(li?.dataset.itemId) this.render(true) } static #onQuantityMinus(event, target) { const li = target.closest("[data-item-id]") this.actor.incDecQuantity(li?.dataset.itemId, -1) } static #onQuantityPlus(event, target) { const li = target.closest("[data-item-id]") this.actor.incDecQuantity(li?.dataset.itemId, +1) } // #endregion }