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 {Character} from "../documents/character.mjs"; import {LiturgyDialog} from "../dialog/liturgyDialog.mjs"; import {TalentDialog} from "../dialog/talentDialog.mjs"; import {AttributeDialog} from "../dialog/attributeDialog.mjs"; const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api const {ActorSheetV2} = foundry.applications.sheets const {ContextMenu} = foundry.applications.ux 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, } } 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: [''] }, skills: { template: Skills.template }, spells: { template: Spells.template }, liturgies: { template: Liturgies.template }, effects: { template: Effects.template } } /** * * @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.maneuver.name} 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 <= 0) { const am = new ActionManager(this.document) const action = am.evaluate().find(action => action.name === cooldown.data.maneuver.id) if (action) { action.activate(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.maneuver.name} 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) { switch (target.dataset.mode) { case "attack": new CombatActionDialog(this.document).render(true) break case "defense": new DefenseActionDialog(this.document).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]) } } } _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) // Note: formData.object } _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 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(async skill => { const obj = await skill context.attacks.push({ name: obj.name, using: fernkampf.name, atroll: `1d20cs<${this.document.system.fk.aktuell + obj.system.at}`, at: `${this.document.system.fk.aktuell + obj.system.at}`, tproll: `${fernkampf.system.rangedAttackDamage}`, // TODO consider adding TP/KK mod and Range mod tp: `${fernkampf.system.rangedAttackDamage}`, iniroll: `(${context.inidice})d6 + ${context.inivalue + fernkampf.system.iniModifier ?? 0}`, 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, using: links.name, atroll: `1d20cs<${this.document.system.at.links.aktuell + obj.system.at + links.system.attackModifier}`, // TODO consider adding W/M at: `${this.document.system.at.links.aktuell + obj.system.at + links.system.attackModifier}`, paroll: `1d20cs<${this.document.system.pa.links.aktuell + obj.system.pa + links.system.parryModifier}`, // TODO consider adding W/M pa: `${this.document.system.pa.links.aktuell + obj.system.pa + links.system.parryModifier}`, tproll: `${links.system.meleeAttackDamage}`, // TODO consider adding TP/KK mod tp: `${links.system.meleeAttackDamage}`, iniroll: `(${context.inidice})d6 + ${context.inivalue + links.system.iniModifier ?? 0}`, 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, using: rechts.name, atroll: `1d20cs<${this.document.system.at.rechts.aktuell + obj.system.at + rechts.system.attackModifier}`, // TODO consider adding W/M at: `${this.document.system.at.rechts.aktuell + obj.system.at + rechts.system.attackModifier}`, paroll: `1d20cs<${this.document.system.pa.rechts.aktuell + obj.system.pa + rechts.system.parryModifier}`, // TODO consider adding W/M pa: `${this.document.system.pa.rechts.aktuell + obj.system.pa + rechts.system.parryModifier}`, tproll: `${rechts.system.meleeAttackDamage}`, // TODO consider adding TP/KK mod tp: `${rechts.system.meleeAttackDamage}`, iniroll: `(${context.inidice})d6 + ${context.inivalue + rechts.system.iniModifier ?? 0}`, ini: `${context.inidice}w6 + ${context.inivalue + rechts.system.iniModifier ?? 0}`, }) }) } context.cooldowns = actorData.system.cooldowns ?? [] context.cooldowns.forEach(cooldown => { const weapon = this.document.itemTypes["Equipment"].find(p => p._id === cooldown.data.weapon) const skill = this.document.itemTypes["Skill"].find(p => p._id === cooldown.data.skillId) const target = game.actors.get(game.scenes.current.tokens.find(p => p._id === cooldown.data.target).actorId) cooldown.progress = ((cooldown.current / cooldown.start) * 100) + "%" cooldown.tooltip = `${cooldown.data.maneuver.name}
Waffe:${weapon.name}
Ziel: ${target.name}
Wurfziel: ${cooldown.data.targetNumber}
Aktionen verbleibend: ${cooldown.current}` }) context.hasSpells = actorData.itemTypes["Spell"].length > 0 context.hasLiturgies = actorData.itemTypes["Liturgy"].length > 0 context.attributes = [ { eigenschaft: "mu", name: "MU", tooltip: "Mut", wert: context.derived.attribute.mu.aktuell ?? 0, }, { eigenschaft: "kl", name: "KL", tooltip: "Klugheit", wert: context.derived.attribute.kl.aktuell ?? 0, }, { eigenschaft: "in", name: "IN", tooltip: "Intuition", wert: context.derived.attribute.in.aktuell ?? 0, }, { eigenschaft: "ch", name: "CH", tooltip: "Charisma", wert: context.derived.attribute.ch.aktuell ?? 0, }, { eigenschaft: "ff", name: "FF", tooltip: "Fingerfertigkeit", wert: context.derived.attribute.ff.aktuell ?? 0, }, { eigenschaft: "ge", name: "GE", tooltip: "Gewandtheit", wert: context.derived.attribute.ge.aktuell ?? 0, }, { eigenschaft: "ko", name: "KO", tooltip: "Konstitution", wert: context.derived.attribute.ko.aktuell ?? 0, }, { eigenschaft: "kk", name: "KK", tooltip: "Körperkraft", wert: context.derived.attribute.kk.aktuell ?? 0, }, ] break; case "meta": await Meta._prepareContext(context, this.document) break case "social": await Social._prepareContext(context, this.document) break case "advsf": await Advsf._prepareContext(context, this.document) break case "combat": await Combat._prepareContext(context, this.document) break case "equipment": await Equipment._prepareContext(context, this.document) break case "skills": await Skills._prepareContext(context, this.document) break case "spells": await Spells._prepareContext(context, this.document) break case "liturgies": await Liturgies._prepareContext(context, this.document) break case "effects": await Effects._prepareContext(context, this.document) break } return context } _onRender(context, options) { Meta._onRender(context, options, this.element) Social._onRender(context, options, this.element) Advsf._onRender(context, options, this) Combat._onRender(context, options, this.element) Effects._onRender(context, options, this.element) Equipment._onRender(context, options, this) Liturgies._onRender(context, options, this.element) Skills._onRender(context, options, this.element) Spells._onRender(context, options, this.element) } async _canDragDrop() { return true } async _onDrop(event) { const data = TextEditor.implementation.getDragEventData(event); const actor = this.actor; const targetDocument = this.actor.itemTypes["Equipment"].find(p => p._id === event.target.dataset['itemId']) //const allowed = Hooks.call("dropActorSheetData", actor, this, data); //if (allowed === false) return; // Dropped Documents const documentClass = foundry.utils.getDocumentClass(data.type); if (documentClass) { const document = await documentClass.fromDropData(data); if (document.type === "Equipment" || document.type === "Advantage" || document.type === "Spell" || document.type === "Liturgy" || document.type === "ActiveEffect" || document.type === "SpecialAbility") { // No duplication by moving items from one actor to another if ((targetDocument?.name ?? false) === document.name && targetDocument._id !== document._id && await foundry.applications.api.DialogV2.confirm({ content: `Gegenstände der Art ${document.name} (Neue Anzahl: ${targetDocument.system.quantity + document.system.quantity}) zusammenlegen?`, rejectClose: false, modal: true, window: { title: `Gegenstände zusammenlegen` } })) { // combine await targetDocument.update({"system.quantity": targetDocument.system.quantity + document.system.quantity}) await this.actor.deleteEmbeddedDocuments('Item', [document._id]) return false } else { if (document.parent && document.parent !== this.actor) { document.parent.items.get(document._id).delete() } await this._onDropDocument(event, document) } } } } } export default CharacterSheet