import OathHammerActorSheet from "./base-actor-sheet.mjs" const ALLOWED_ITEM_TYPES = new Set(["building", "equipment", "weapon", "armor"]) export default class OathHammerSettlementSheet extends OathHammerActorSheet { /** @override */ static DEFAULT_OPTIONS = { classes: ["settlement"], position: { width: 840, height: "auto", }, window: { contentClasses: ["settlement-content"], }, actions: { adjustCurrency: OathHammerSettlementSheet.#onAdjustCurrency, adjustQty: OathHammerSettlementSheet.#onAdjustQty, toggleConstructed: OathHammerSettlementSheet.#onToggleConstructed, collectTaxes: OathHammerSettlementSheet.#onCollectTaxes, openRegiment: OathHammerSettlementSheet.#onOpenRegiment, removeRegiment: OathHammerSettlementSheet.#onRemoveRegiment, }, } /** @override */ static PARTS = { main: { template: "systems/fvtt-oath-hammer/templates/actor/settlement-sheet.hbs", }, tabs: { template: "templates/generic/tab-navigation.hbs", }, overview: { template: "systems/fvtt-oath-hammer/templates/actor/settlement-overview.hbs", }, buildings: { template: "systems/fvtt-oath-hammer/templates/actor/settlement-buildings.hbs", }, inventory: { template: "systems/fvtt-oath-hammer/templates/actor/settlement-inventory.hbs", }, garrison: { template: "systems/fvtt-oath-hammer/templates/actor/settlement-garrison.hbs", }, } /** @override */ tabGroups = { sheet: "overview", } #getTabs() { const tabs = { overview: { id: "overview", group: "sheet", icon: "fa-solid fa-city", label: "OATHHAMMER.Tab.Overview" }, buildings: { id: "buildings", group: "sheet", icon: "fa-solid fa-building", label: "OATHHAMMER.Tab.Buildings" }, inventory: { id: "inventory", group: "sheet", icon: "fa-solid fa-boxes-stacked", label: "OATHHAMMER.Tab.Inventory" }, garrison: { id: "garrison", group: "sheet", icon: "fa-solid fa-shield-halved", label: "OATHHAMMER.Tab.Garrison" }, } for (const v of Object.values(tabs)) { v.active = this.tabGroups[v.group] === v.id v.cssClass = v.active ? "active" : "" } return tabs } /** @override */ async _prepareContext() { const context = await super._prepareContext() context.tabs = this.#getTabs() context.archetypeChoices = Object.fromEntries( Object.entries(SYSTEM.SETTLEMENT_ARCHETYPES).map(([k, v]) => [k, game.i18n.localize(v)]) ) return context } /** @override */ async _preparePartContext(partId, context) { const doc = this.document switch (partId) { case "main": break case "overview": context.tab = context.tabs.overview context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML( doc.system.description, { async: true } ) break case "buildings": context.tab = context.tabs.buildings context.buildings = doc.itemTypes.building.map(b => ({ id: b.id, uuid: b.uuid, img: b.img, name: b.name, system: b.system, _descTooltip: b.system.description?.replace(/<[^>]+>/g, "").trim().slice(0, 400) ?? "" })) context.hasTaxBuildings = doc.itemTypes.building.some(b => b.system.constructed && b.system.taxRevenue?.trim()) break case "inventory": { context.tab = context.tabs.inventory context.weapons = doc.itemTypes.weapon context.armors = doc.itemTypes.armor context.equipments = doc.itemTypes.equipment break } case "garrison": context.tab = context.tabs.garrison context.regiments = (doc.system.garrisonRefs ?? []) .map(id => game.actors?.get(id)) .filter(Boolean) break } return context } /** @override */ async _onDrop(event) { if (!this.isEditable || !this.isEditMode) return const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event) // Regiment actors dropped onto garrison tab if (data.type === "Actor") { const actor = await fromUuid(data.uuid) if (!actor || actor.type !== "regiment") return const refs = foundry.utils.deepClone(this.document.system.garrisonRefs ?? []) if (refs.includes(actor.id)) return // already linked refs.push(actor.id) return this.document.update({ "system.garrisonRefs": refs }) } if (data.type !== "Item") return const item = await fromUuid(data.uuid) if (!item || !ALLOWED_ITEM_TYPES.has(item.type)) return return this._onDropItem(item) } static async #onAdjustCurrency(event, target) { const field = target.dataset.field const delta = parseInt(target.dataset.delta, 10) if (!field || isNaN(delta)) return const current = foundry.utils.getProperty(this.document, field) ?? 0 await this.document.update({ [field]: Math.max(0, current + delta) }) } static async #onAdjustQty(event, target) { const itemId = target.dataset.itemId const delta = parseInt(target.dataset.delta, 10) if (!itemId || isNaN(delta)) return const item = this.document.items.get(itemId) if (!item) return const current = item.system.quantity ?? 0 await item.update({ "system.quantity": Math.max(0, current + delta) }) } static async #onToggleConstructed(event, target) { const itemId = target.dataset.itemId if (!itemId) return const item = this.document.items.get(itemId) if (!item) return await item.update({ "system.constructed": !item.system.constructed }) } static async #onOpenRegiment(event, target) { const actor = game.actors?.get(target.dataset.actorId) if (actor) actor.sheet.render(true) } static async #onRemoveRegiment(event, target) { const actorId = target.dataset.actorId const refs = (this.document.system.garrisonRefs ?? []).filter(id => id !== actorId) await this.document.update({ "system.garrisonRefs": refs }) } static async #onCollectTaxes() { const actor = this.document // Only constructed buildings with a non-empty taxRevenue formula const taxBuildings = actor.itemTypes.building.filter( b => b.system.constructed && b.system.taxRevenue?.trim() ) if (!taxBuildings.length) { ui.notifications.warn(game.i18n.localize("OATHHAMMER.Settlement.NoTaxRevenue")) return } // Roll each building's formula individually, sum totals const rolls = [] const lines = [] let total = 0 for (const b of taxBuildings) { const r = new Roll(b.system.taxRevenue.trim()) await r.evaluate() rolls.push(r) total += r.total lines.push(`