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}}})
}
}