import {LiturgyData} from "../data/miracle/liturgyData.mjs"; import {Zonenruestung, Zonenwunde, Wunde} from "../data/trefferzone.mjs"; import {PlayerCharacterDataModel} from "../data/character.mjs"; export class Character extends Actor { /** import() { let input = document.createElement('input') input.type = 'file' input.accept = '.xml' input.onchange = e => { importCharacter(this.id, e.target.files[0]) } input.click() }*/ /** * @override * Augment the actor source data with additional dynamic data. Typically, * you'll want to handle most of your calculated/derived data in this step. * Data calculated in this step should generally not exist in template.json * (such as ability modifiers rather than ability scores) and should be * available both inside and outside of character sheets (such as if an actor * is queried and has a roll executed directly from it). */ prepareDerivedData() { if (this.type === "Character") { const actorData = this; const systemData = actorData.system; systemData.attribute.mu.aktuell = systemData.attribute.mu.start systemData.attribute.kl.aktuell = systemData.attribute.kl.start systemData.attribute.in.aktuell = systemData.attribute.in.start systemData.attribute.ch.aktuell = systemData.attribute.ch.start systemData.attribute.ff.aktuell = systemData.attribute.ff.start systemData.attribute.ge.aktuell = systemData.attribute.ge.start systemData.attribute.ko.aktuell = systemData.attribute.ko.start systemData.attribute.kk.aktuell = systemData.attribute.kk.start const mu = systemData.attribute.mu.aktuell const kl = systemData.attribute.kl.aktuell const _in = systemData.attribute.in.aktuell const ch = systemData.attribute.ch.aktuell const ff = systemData.attribute.ff.aktuell const ge = systemData.attribute.ge.aktuell const ko = systemData.attribute.ko.aktuell const kk = systemData.attribute.kk.aktuell systemData.lep.max = Math.round((ko + ko + kk) / 2) + systemData.lep.mod systemData.aup.max = Math.round((mu + ko + ge) / 2) + systemData.aup.mod systemData.asp.max = Math.round((mu + _in + ch) / 2) + systemData.asp.mod systemData.regeneration = systemData.regeneration ?? { lep: "1d6", asp: "1d6", kap: "1", ko: 0, in: 0 } systemData.at = systemData.at ?? {links: {}, rechts: {}} systemData.at.links = systemData.at.links ?? { aktuell: 0, mods: 0 } systemData.at.rechts = systemData.at.rechts ?? { aktuell: 0, mods: 0 } systemData.at.basis = Math.round((mu + ge + kk) / 5) systemData.at.aktuell = systemData.at.basis systemData.at.links.aktuell = systemData.at.basis systemData.at.rechts.aktuell = systemData.at.basis systemData.pa = systemData.pa ?? {links: {}, rechts: {}} systemData.pa.links = systemData.pa.links ?? { aktuell: 0, mods: 0 } systemData.pa.rechts = systemData.pa.rechts ?? { aktuell: 0, mods: 0 } systemData.pa.basis = Math.round((_in + ge + kk) / 5) systemData.pa.aktuell = systemData.pa.basis systemData.pa.links.aktuell = systemData.pa.basis systemData.pa.rechts.aktuell = systemData.pa.basis systemData.fk = systemData.fk ?? { aktuell: 0, mods: 0 } systemData.fk.basis = Math.round((_in + ff + kk) / 5); systemData.fk.aktuell = systemData.fk.basis systemData.ini.basis = Math.round((mu + mu + _in + ge) / 5) systemData.ini.aktuell = systemData.ini.basis systemData.mr.basis = Math.round((mu + kl + ko) / 5) systemData.mr.aktuell = systemData.mr.basis systemData.gs.basis = 6; systemData.gs.aktuell = systemData.gs.basis // TOOD: get GS from spezien systemData.ausweichen = {} systemData.ausweichen.basis = systemData.pa.basis systemData.ausweichen.aktuell = systemData.ausweichen.basis if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) { systemData.rs = { brust: 0, bauch: 0, armlinks: 0, armrechts: 0, beinlinks: 0, beinrechts: 0, kopf: 0, }; // only with DSA_4-1.optional_trefferzonen = true } else { systemData.rs = 0; // only with DSA_4-1.optional_trefferzonen = false } systemData.be = 0; // half KO is the maximum a character can sustain wounds before collapsing systemData.wunden.max = ko / 2; if (game.settings.get("DSA_4-1", "optional_trefferzonen")) { systemData.wunden.kopf = 0 systemData.wunden.brust = 0 systemData.wunden.bauch = 0 systemData.wunden.ruecken = 0 systemData.wunden.armlinks = 0 systemData.wunden.armrechts = 0 systemData.wunden.beinlinks = 0 systemData.wunden.beinrechts = 0 } else { systemData.wunden.gesamt = 0 } systemData.ws = ko / 2; // map current set to RS and BE const ausruestung = systemData.heldenausruestung[systemData.setEquipped] const zonesToCheck = [ "brust", "bauch", "ruecken", "kopf", "armlinks", "armrechts", "beinlinks", "beinrechts"] if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) { systemData.rs.gesamt = 0 systemData.rs.brust = 0 systemData.rs.bauch = 0 systemData.rs.ruecken = 0 systemData.rs.kopf = 0 systemData.rs.armlinks = 0 systemData.rs.armrechts = 0 systemData.rs.beinlinks = 0 systemData.rs.beinrechts = 0 } else { systemData.rs = 0 } zonesToCheck.forEach((zone) => { systemData.be += systemData.parent.items.get(ausruestung[zone])?.system.armorHandicap ?? 0 if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) { zonesToCheck.forEach((itemZone) => { systemData.rs[itemZone] += systemData.parent.items.get(ausruestung[zone])?.system.armorValue[itemZone] ?? 0 }) } else { systemData.rs += systemData.parent.items.get(ausruestung[zone])?.system.armorValue.total ?? 0 } }) systemData.kap.max = 0; // evaluate deities for KaP const deities = systemData.parent.items.filter(p => p.type === "Blessing") deities?.forEach((deity) => { if (LiturgyData.alverans.includes(deity.system.gottheit)) { systemData.kap.max = 24; } else if (systemData.kap.max === 0) { systemData.kap.max += 12; } }, 0) // evaluate SFs const propSetter = (mod) => { // we don't apply to .mod as that field is only applicable for temporary modifier from ActiveEffects, Spells, Liturgies, Combat Effects const property = foundry.utils.getProperty(systemData, mod.name) if (property && property.aktuell) { // apply to aktuell const aktuellProperty = foundry.utils.getProperty(systemData, mod.name + ".aktuell") foundry.utils.setProperty(systemData, mod.name + ".aktuell", aktuellProperty + mod.value) } else { // apply directly const aktuellProperty = foundry.utils.getProperty(systemData, mod.name) foundry.utils.setProperty(systemData, mod.name, aktuellProperty + mod.value) } } this.itemTypes["SpecialAbility"].forEach(sf => { if (sf.system.isActive()) { sf.system.getActiveMod()?.forEach(propSetter) } }) this.itemTypes["Advantage"].forEach(adv => { if (adv.system.isActive()) { adv.system.getActiveMod()?.forEach(propSetter) } }) } } getModificationsOn(fieldName) { const flatActor = foundry.utils.flattenObject(this.system) const grabbag = {} // mods via SF this.itemTypes["SpecialAbility"].forEach(sf => { if (sf.system.isActive()) { sf.system.getActiveMod()?.forEach(mod => { // we don't apply to .mod as that field is only applicable for temporary modifier from ActiveEffects, Spells, Liturgies, Combat Effects if (mod.name === fieldName || mod.talent === fieldName) { grabbag[sf.name] = mod.value } }) } }) // mods via adv this.itemTypes["Advantage"].forEach(adv => { if (adv.system.isActive()) { adv.system.getActiveMod()?.forEach(mod => { // we don't apply to .mod as that field is only applicable for temporary modifier from ActiveEffects, Spells, Liturgies, Combat Effects if (mod.name === fieldName || mod.talent === fieldName) { grabbag[adv.name] = mod.value } }) } }) // TODO mods via ActiveEffects grabbag["Ergebnis"] = flatActor[fieldName] return grabbag } /** * Augment the basic Item data model with additional dynamic data. */ prepareData() { super.prepareData(); this.prepareEmbeddedDocuments(); } getRollData() { const data = super.getRollData(); this.prepareDerivedData() if (this.type !== 'character' && this.type !== 'creature') return; // move sonderfertigkeiten into data, if it isn't in data the actor doesn't have that sonderfertigkeit data.sf = {} if (data.sonderfertigkeiten) { data.sonderfertigkeiten.forEach(sf => { data.sf[sf.name] = sf.auswahl; }) delete data.sonderfertigkeiten; } return data; } findEquipmentOnSlot(slot, setNumber) { return this.items.get(this.system.heldenausruestung[setNumber ?? this.system.setEquipped]?.[slot]) } getEquipmentSetUpdateObject() { const updateObject = {} Array.from(this.system.heldenausruestung).forEach((equipmentSet, index) => { updateObject[`system.heldenausruestung.${index}.links`] = equipmentSet.links; updateObject[`system.heldenausruestung.${index}.rechts`] = equipmentSet.rechts; updateObject[`system.heldenausruestung.${index}.brust`] = equipmentSet.brust; updateObject[`system.heldenausruestung.${index}.bauch`] = equipmentSet.bauch; updateObject[`system.heldenausruestung.${index}.ruecken`] = equipmentSet.ruecken; updateObject[`system.heldenausruestung.${index}.kopf`] = equipmentSet.kopf; updateObject[`system.heldenausruestung.${index}.fernkampf`] = equipmentSet.fernkampf; updateObject[`system.heldenausruestung.${index}.munition`] = equipmentSet.munition; updateObject[`system.heldenausruestung.${index}.armlinks`] = equipmentSet.armlinks; updateObject[`system.heldenausruestung.${index}.armrechts`] = equipmentSet.armrechts; updateObject[`system.heldenausruestung.${index}.beinlinks`] = equipmentSet.beinlinks; updateObject[`system.heldenausruestung.${index}.beinrechts`] = equipmentSet.beinrechts; }) return updateObject; } /** * reduce the wealth owned by this character by the given amount in Silver * @param by amount of Silver to reduce the wealth by the character * @returns {boolean} */ async reduceWealth(by) { const ducats = this.itemTypes["Equipment"].filter(p => p.name === "Dukate") const silver = this.itemTypes["Equipment"].filter(p => p.name === "Silbertaler") const kreutzer = this.itemTypes["Equipment"].filter(p => p.name === "Kreuzer") const heller = this.itemTypes["Equipment"].filter(p => p.name === "Heller") let ducatsAmount = 0 let silverAmount = 0 let kreutzerAmount = 0 let hellerAmount = 0 ducats.forEach(d => { ducatsAmount += d.system.quantity ?? 1 }) silver.forEach(s => { silverAmount += s.system.quantity ?? 1 }) kreutzer.forEach(k => { kreutzerAmount += k.system.quantity ?? 1 }) heller.forEach(h => { hellerAmount += h.system.quantity ?? 1 }) // Convert total wealth to silver let totalSilver = ducatsAmount * 10 + silverAmount + kreutzerAmount * 0.1 + hellerAmount * 0.01; if (totalSilver < by) { return false; // Indicate that the reduction can't be performed } // Subtract the given sum from total silver totalSilver -= by; // Handle if the total goes below zero if (totalSilver < 0) totalSilver = 0; // Convert back to coinages let newDucats = Math.floor(totalSilver / 10); totalSilver %= 10; let newSilver = Math.floor(totalSilver); totalSilver %= 1; let newKreutzer = Math.floor(totalSilver / 0.1); totalSilver %= 0.1; let newHeller = Math.round(totalSilver / 0.01); // remove all coinage items let deleteDocuments = [] ducats.forEach(d => deleteDocuments.push(d._id)) silver.forEach(s => deleteDocuments.push(s._id)) kreutzer.forEach(k => deleteDocuments.push(k._id)) heller.forEach(h => deleteDocuments.push(h._id)) await this.deleteEmbeddedDocuments('Item', deleteDocuments) // rebuild coinage documents const compendiumOfCoins = game.packs.get('DSA_4-1.Currency'); if (newDucats > 0) { let coin = compendiumOfCoins.index.find(coin => coin.name === "Dukate") const ducatDocument = await compendiumOfCoins.getDocument(coin._id); try { this.createEmbeddedDocuments('Item', [ducatDocument]).then( embeddedDocuments => { embeddedDocuments[0].update({"system.quantity": newDucats}).then(_ => { console.log("created new Ducats with qty of", newDucats) }) }) } catch (err) { } } if (newSilver > 0) { let coin = compendiumOfCoins.index.find(coin => coin.name === "Silbertaler") const silverDocument = await compendiumOfCoins.getDocument(coin._id); try { this.createEmbeddedDocuments('Item', [silverDocument]).then( embeddedDocuments => { embeddedDocuments[0].update({"system.quantity": newSilver}).then(_ => { console.log("created new Silver with qty of", newSilver) }) }) } catch (err) { } } if (newKreutzer > 0) { let coin = compendiumOfCoins.index.find(coin => coin.name === "Kreuzer") const kreutzerDocument = await compendiumOfCoins.getDocument(coin._id); try { this.createEmbeddedDocuments('Item', [kreutzerDocument]).then( embeddedDocuments => { embeddedDocuments[0].update({"system.quantity": newKreutzer}).then(_ => { console.log("created new Kreutzer with qty of", newKreutzer) }) }) } catch (err) { } } if (newHeller > 0) { let coin = compendiumOfCoins.index.find(coin => coin.name === "Heller") const hellerDocument = await compendiumOfCoins.getDocument(coin._id); try { this.createEmbeddedDocuments('Item', [hellerDocument]).then( embeddedDocuments => { embeddedDocuments[0].update({"system.quantity": newHeller}).then(_ => { console.log("created new Heller with qty of", newHeller) }) }) } catch (err) { } } return true } isWorn(itemId) { const slots = PlayerCharacterDataModel.getSlots() const set = this.system.heldenausruestung[this.system.setEquipped] if (set) { for (const slot of slots) { const equipmentSlotId = set[slot] if (equipmentSlotId === itemId) { return slot } } } return false } async rollDefense(data) { const maneuver = data.manuever const weapon = this.itemTypes["Equipment"].find(p => p._id === data.weapon) const skill = data.skill !== "Ausweichen" ? this.itemTypes["Skill"].find(p => p._id === data.skill) : "Ausweichen" //const target = game.actors.get(game.scenes.current.tokens.find(p => p._id === data.target).actorId) const roll = new Roll("1d20cs<" + data.targetNumber) const evaluated1 = (await roll.evaluate()) let flavor = '' if (skill === "Ausweichen") { flavor = `Versucht auszuweichen
${data.modDescription}` } else { flavor = `Verteidigt sich gegen einen Angriff mit ${weapon.name} (${skill.name})
${data.modDescription}` } await evaluated1.toMessage({ speaker: ChatMessage.getSpeaker({actor: this}), flavor, rollMode: "publicroll", }) } async rollAttack(data) { const maneuver = data.manuever const weapon = this.itemTypes["Equipment"].find(p => p._id === data.weapon) const skill = this.itemTypes["Skill"].find(p => p._id === data.skill) const target = game.actors.get(game.scenes.current.tokens.find(p => p._id === data.target).actorId) const roll = new Roll("1d20cs<" + data.targetNumber) const evaluated1 = (await roll.evaluate()) await evaluated1.toMessage({ speaker: ChatMessage.getSpeaker({actor: this}), flavor: `Attackiert ${target.name} mit ${weapon.name} (${skill.name})
${data.modDescription}`, rollMode: "publicroll", }) } /** * * @param amount * @param zone either null or one of * @returns {Promise} */ async takeDamage(amount = null, zone = null) { this.prepareDerivedData() const playWithZoneArmor = game.settings.get("DSA_4-1", "optional_ruestungzonen") const playWithWoundZones = game.settings.get("DSA_4-1", "optional_trefferzonen") const previousLeP = this.system.lep.aktuell; if (amount == null) { // TODO show Dialog for entering damage amount (TP) } let armorReduction = 0 let setEquipped = this.system.setEquipped let woundThreshold = this.system.ws if (playWithZoneArmor) { const armorId = this.system.heldenausruestung[setEquipped][Zonenruestung[zone]] const zoneArmor = await this.items.find(p => p._id === armorId) if (!zoneArmor) { return console.error(`zone "${zone}" is not a valid value`) } armorReduction = zoneArmor.system.armorValue ?? 0 } else { armorReduction = this.system.rs } let damage = amount - armorReduction let wounds = damage / woundThreshold let wound = null if (playWithWoundZones) { wound = await game.packs.get("DSA_4-1.Wounds").index.find(p => p.name === Zonenwunde[zone]) if (!wound) { return console.error(`Wunden Dokument zu "${zone}" konnten nicht gefunden werden`) } } else { wound = await game.packs.get("DSA_4-1.Wounds").index.find(p => p.name === Wunde) if (!wound) { return console.error(`Wunden Dokument zu "${Wunde}" konnten nicht gefunden werden`) } } // TODO this doesnt work yet, wound documents wont get expanded for (let i = 0; i < wounds; i++) { await this.createEmbeddedDocuments('Item', [wound]) } await this.update({system: {lep: {aktuell: previousLeP - damage}}}) } }