581 lines
23 KiB
JavaScript
581 lines
23 KiB
JavaScript
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}<br/>Waffe:${weapon.name}<br/>Ziel: ${target.name}<br/>Wurfziel: ${cooldown.data.targetNumber}<br/>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: `<span>Gegenstände der Art <strong>${document.name}</strong> (Neue Anzahl: ${targetDocument.system.quantity + document.system.quantity}) zusammenlegen?</span>`,
|
|
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
|