import Advsf from "./character/advsf.mjs" import Combat from "./character/combat.mjs" import Effects from "./character/effects.mjs" import Equipment from "./character/equipment.mjs" import Liturgies from "./character/liturgies.mjs" import Meta from "./character/meta.mjs" import Skills from "./character/skills.mjs" import Social from "./character/social.mjs"; import Spells from "./character/spells.mjs" import {CombatActionDialog} from "../dialog/combatAction.mjs"; import {ActionManager} from "./actions/action-manager.mjs"; import {DefenseActionDialog} from "../dialog/defenseAction.mjs"; import {RestingDialog} from "../dialog/restingDialog.mjs"; import {LiturgyDialog} from "../dialog/liturgyDialog.mjs"; import {TalentDialog} from "../dialog/talentDialog.mjs"; import {AttributeDialog} from "../dialog/attributeDialog.mjs"; import {ItemBrowserDialog} from "../dialog/itemBrowserDialog.mjs"; import * as EquipmentDocument from "../documents/equipment.mjs"; const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api const {ActorSheetV2} = foundry.applications.sheets class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) { /** @inheritDoc */ static DEFAULT_OPTIONS = { position: {width: 1100, height: 640}, classes: ['dsa41', 'sheet', 'actor', 'character'], tag: 'form', form: { submitOnChange: true, closeOnSubmit: false, handler: CharacterSheet.#onSubmitForm }, window: { resizable: true, }, actions: { rollCombatSkill: CharacterSheet.#rollCombatSkill, rollSkill: CharacterSheet.#rollSkill, rollFlaw: CharacterSheet.#rollFlaw, rollAttribute: CharacterSheet.#rollAttribute, editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage, openEmbeddedDocument: CharacterSheet.#openEmbeddedDocument, openCultureDocument: CharacterSheet.#openCultureDocument, openSpeciesDocument: CharacterSheet.#openSpeciesDocument, openCombatAction: CharacterSheet.#openCombatAction, openLiturgyDialog: CharacterSheet.#openLiturgyDialog, progressCooldown: CharacterSheet.#progressCooldown, cancelCooldown: CharacterSheet.#cancelCooldown, activateCooldown: CharacterSheet.#activateCooldown, rest: CharacterSheet.#startResting, removeEffect: CharacterSheet.#removeEffect, rollDamage: CharacterSheet.#rollDamage, openItemBrowser: CharacterSheet.#openItemBrowser, newItem: CharacterSheet.#addNewItem } } static TABS = { sheet: { tabs: [], initial: 'meta' }, } /** @inheritDoc */ static PARTS = { form: { template: `systems/DSA_4-1/templates/actor/character/main-sheet.hbs` }, meta: { template: Meta.template }, social: { template: Social.template }, advsf: { template: Advsf.template }, combat: { template: Combat.template }, equipment: { template: Equipment.template, scrollable: ['.inventory'] }, skills: { template: Skills.template }, spells: { template: Spells.template }, liturgies: { template: Liturgies.template }, effects: { template: Effects.template }, set1: { template: "systems/DSA_4-1/templates/actor/character/tab-set.hbs" }, set2: { template: "systems/DSA_4-1/templates/actor/character/tab-set.hbs" }, set3: { template: "systems/DSA_4-1/templates/actor/character/tab-set.hbs" }, } /** * * @param {PointerEvent} event */ static #rollSkill(event) { const {id} = event.target.dataset new TalentDialog(this.document, id).render(true) } /** * * @param {PointerEvent} event */ static #rollCombatSkill(event) { const {id} = event.target.dataset const skill = this.document.items.get(id) if (skill?.system?.roll) { skill.system.roll("publicroll", event.shiftKey ? "PARRY" : "ATTACK") } } static #rollAttribute(event, target) { new AttributeDialog(this.document, { name: target.dataset.name, value: target.dataset.value, symbol: target.dataset.symbol, }).render(true) } static async #rollFlaw(event, target) { new AttributeDialog(this.document, target.dataset.itemId).render(true) } static async #progressCooldown(event, target) { const {cooldownId} = target.dataset const cooldowns = this.document.system.cooldowns const cooldown = this.document.system.cooldowns[cooldownId] cooldowns.splice(cooldownId, 1) if (cooldown) { cooldown.current = cooldown.current + 1 } cooldowns.push(cooldown) this.document.update({"system.cooldowns": cooldowns.sort((a, b) => a.current - b.current)}) ui.notifications.info(`Abklingzeit von ${cooldown.data.title} um 1 Aktion reduziert`) } static async #cancelCooldown(event, target) { const {cooldownId} = target.dataset const cooldown = this.document.system.cooldowns[cooldownId] const cooldowns = this.document.system.cooldowns cooldowns.splice(cooldownId, 1) this.document.update({"system.cooldowns": cooldowns.sort((a, b) => a.current - b.current)}) ui.notifications.info(`${cooldown.data.maneuver.name} abgebrochen`) } static async #activateCooldown(event, target) { const {cooldownId} = target.dataset const cooldowns = this.document.system.cooldowns const cooldown = this.document.system.cooldowns[cooldownId] if (cooldown && cooldown.current >= cooldown.start) { const am = new ActionManager(this.document) console.log(cooldown.data.maneuver) const action = new Function(`return ${cooldown.data.maneuver}`) if (action) { action()(this.document.system.cooldowns, {...cooldown.data, actor: this.document}) } } cooldowns.splice(cooldownId, 1) this.document.update({"system.cooldowns": cooldowns.sort((a, b) => a.current - b.current)}) ui.notifications.info(`${cooldown.data.title} ausgeführt`) } /** * * @param {MouseEvent} event */ static #openEmbeddedDocument(event) { let dataset = event.target.dataset if (!dataset.itemId && !dataset.id) { dataset = event.target.parentElement.dataset } const id = dataset.itemId ?? dataset.id this.document.items.get(id).sheet.render(true) } static #openCultureDocument() { this.document.itemTypes["Culture"]?.[0]?.sheet.render(true) } static #openSpeciesDocument() { this.document.itemTypes["Species"]?.[0]?.sheet.render(true) } static #openCombatAction(event, target) { let {weapon, skill} = target.dataset switch (target.dataset.mode) { case "attack": new CombatActionDialog(this.document, {weapon, skill}).render(true) break case "defense": new DefenseActionDialog(this.document, {weapon, skill}).render(true) break } } static #openLiturgyDialog(event, target) { const {id, lkp, deity} = target.dataset new LiturgyDialog(this.document, lkp, id, deity).render(true) } static #startResting(event, target) { const dialog = new RestingDialog(this.document) dialog.render(true) } static async #removeEffect(event, target) { const {actorId, effectId} = target.dataset if (actorId === this.document._id) { const item = this.document.items.get(effectId) if (item.type === "ActiveEffect") { this.document.deleteEmbeddedDocuments("Item", [effectId]) } } } static async #openItemBrowser(event, target) { new ItemBrowserDialog(this.document).render(true) } static async #addNewItem(event, target) { let item = new EquipmentDocument.Equipment({ name: "Neuer Gegenstand", type: "Equipment", }) const items = await this.document.createEmbeddedDocuments("Item", [item]) items[0].sheet.render(true) } _configureRenderOptions(options) { super._configureRenderOptions(options) if (options.window) { options.window.title = this.document.name } return options } /** * Handle form submission * @this {AdvantageSheet} * @param {SubmitEvent} event * @param {HTMLFormElement} form * @param {FormDataExtended} formData */ static async #onSubmitForm(event, form, formData) { event.preventDefault() await this.document.update(formData.object) } static async #rollDamage(event, target) { let {weapon, isRanged} = target.dataset isRanged = isRanged == "true" weapon = this.document.items.get(weapon) if (weapon) { const damageFormula = isRanged ? weapon.system.rangedAttackDamage : weapon.system.meleeAttackDamage const calculation = await foundry.applications.api.DialogV2.prompt({ window: {title: game.i18n.format("COMBAT_DIALOG_TP.windowTitle")}, content: `
`, ok: { label: game.i18n.format("COMBAT_DIALOG_TP.buttonText"), callback: (event, button, dialog) => { return { formula: button.form.elements.formula.value, bonusDamage: button.form.elements.bonusDamage.value } } } }); const sanitisedFormula = calculation.formula.replace(/wW/g, "d") const suffix = calculation.bonusDamage >= 0 ? "+" + calculation.bonusDamage : calculation.bonusDamage let r = new Roll(sanitisedFormula + suffix, this.document.getRollData()); const label = `Schadenswurf` await r.toMessage({ speaker: ChatMessage.getSpeaker({actor: this.document}), flavor: label, rollMode: game.settings.get('core', 'rollMode'), }) } } _getTabsConfig(group) { const tabs = foundry.utils.deepClone(super._getTabsConfig(group)) Meta._getTabConfig(tabs, this) Social._getTabConfig(tabs, this) Advsf._getTabConfig(tabs, this) Combat._getTabConfig(tabs, this) Equipment._getTabConfig(tabs, this) Skills._getTabConfig(tabs, this) Spells._getTabConfig(tabs, this) Liturgies._getTabConfig(tabs, this) Effects._getTabConfig(tabs, this) return tabs } async _preparePartContext(partId, context) { switch (partId) { case "form": const actorData = context.document context.system = actorData.system context.actorId = actorData._id context.isOwner = actorData.isOwner context.flags = actorData.flags context.derived = context.document.system context.professions = actorData.items.filter(p => p.type === 'Profession').map(p => { // is tarnidentitaet revealed? if (p.system.revealed) { return { id: p.id, name: p.name, alias: p.system.alias, } } else { return { id: p.id, name: p.system.alias ?? p.name, alias: p.name, } } }) context.spezies = "" if (actorData.itemTypes["Species"]?.[0]) { const speciesData = actorData.itemTypes["Species"][0] if (actorData.system.meta.geschlecht === "männlich") { context.spezies = speciesData.system.masculineDemonym } else { context.spezies = speciesData.system.feminineDemonym } } context.kultur = "" if (actorData.itemTypes["Culture"]?.[0]) { const cultureData = actorData.itemTypes["Culture"][0] if (actorData.system.geschlecht === "männlich") { context.kultur = cultureData.system.masculineDemonym } else { context.kultur = cultureData.system.feminineDemonym } } context.originalName = actorData.name context.name = context.derived.name ?? actorData.name context.img = actorData.img context.effects = actorData.effects ?? [] context.maxWounds = actorData.system.wunden.max ?? 3 context.wounds = actorData.system.wunden.gesamt ?? 0 context.woundsFilled = [] for (let i = 1; i <= context.maxWounds; i++) { context.woundsFilled[i] = i <= context.wounds } context.zonenruestung = game.settings.get("DSA_4-1", "optional_ruestungzonen") context.trefferzonen = game.settings.get("DSA_4-1", "optional_trefferzonen") context.ausdauer = game.settings.get("DSA_4-1", "optional_ausdauer") context.colorfulDice = game.settings.get('DSA_4-1', 'optional_colorfuldice') context.inidice = actorData.system.ini.wuerfel context.inivalue = actorData.system.ini.aktuell context.inimod = actorData.system.ini.mod context.aupper = Math.min((actorData.system.aup.aktuell / actorData.system.aup.max) * 100, 100) context.lepper = Math.min((actorData.system.lep.aktuell / actorData.system.lep.max) * 100, 100) context.keper = Math.min((actorData.system.kap.aktuell / actorData.system.kap.max) * 100, 100) context.aspper = Math.min((actorData.system.asp.aktuell / actorData.system.asp.max) * 100, 100) context.lepcurrent = actorData.system.lep.aktuell ?? 0 context.aupcurrent = actorData.system.aup.aktuell ?? 0 context.aspcurrent = actorData.system.asp.aktuell ?? 0 context.kapcurrent = actorData.system.kap.aktuell ?? 0 const fernkampf = actorData.findEquipmentOnSlot("fernkampf", actorData.system.setEquipped, actorData) const links = actorData.findEquipmentOnSlot("links", actorData.system.setEquipped, actorData) const rechts = actorData.findEquipmentOnSlot("rechts", actorData.system.setEquipped, actorData) context.attacks = []; if (fernkampf) { const fkitems = fernkampf.system.rangedSkills.map((skillInQuestion) => actorData.items.find(p => p.name === skillInQuestion)) fkitems.forEach(skill => { context.attacks.push({ name: skill.name, id: fernkampf._id, skillId: skill._id, using: fernkampf.name, isRanged: true, at: `${this.document.system.fk.aktuell + skill.system.at}`, tp: `${fernkampf.system.rangedAttackDamage}`, ini: `${context.inidice}w6 + ${context.inivalue + fernkampf.system.iniModifier ?? 0}`, }) }) } if (links) { const meitems = [] links.system.meleeSkills.forEach((skillInQuestion) => { const item = actorData.items.find(p => p.name === skillInQuestion) if (item) { meitems.push(item) } }) meitems.forEach(skill => { const obj = skill context.attacks.push({ name: obj.name, id: links._id, skillId: skill._id, using: links.name, isRanged: false, at: `${this.document.system.at.links.aktuell + obj.system.at + links.system.attackModifier}`, pa: `${this.document.system.pa.links.aktuell + obj.system.pa + links.system.parryModifier}`, tp: `${links.system.meleeAttackDamage}`, ini: `${context.inidice}w6 + ${context.inivalue + links.system.iniModifier ?? 0}`, }) }) } if (rechts) { const meitems = [] rechts.system.meleeSkills.forEach((skillInQuestion) => { const item = actorData.items.find(p => p.name === skillInQuestion) if (item) { meitems.push(item) } }) meitems.forEach(skill => { const obj = skill context.attacks.push({ name: obj.name, id: rechts._id, skillId: skill._id, using: rechts.name, isRanged: false, at: `${this.document.system.at.rechts.aktuell + obj.system.at + rechts.system.attackModifier}`, pa: `${this.document.system.pa.rechts.aktuell + obj.system.pa + rechts.system.parryModifier}`, tp: `${rechts.system.meleeAttackDamage}`, ini: `${context.inidice}w6 + ${context.inivalue + rechts.system.iniModifier ?? 0}`, }) }) } context.cooldowns = actorData.system.cooldowns ?? [] context.cooldowns.forEach(cooldown => { let weapon = null let target = null let tooltip = cooldown.data.title if (cooldown.data.weapon) { weapon = this.document.itemTypes["Equipment"].find(p => p._id === cooldown.data.weapon) tooltip += `