dsa-4th-edition/src/main.mjs
2025-11-09 12:44:37 +01:00

1733 lines
79 KiB
JavaScript

const { TypeDataModel } = foundry.abstract;
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
const { DocumentSheetConfig } = foundry.applications.apps;
const { loadTemplates, renderTemplate } = foundry.applications.handlebars;
const { ActorSheetV2, ItemSheetV2 } = foundry.applications.sheets;
const { Notifications } = foundry.applications.ui;
const { SetField, SchemaField, NumberField, StringField, BooleanField } = foundry.data.fields;
const { Roll } = foundry.dice;
const { OperatorTerm, NumericTerm } = foundry.dice.terms;
const { ChatMessage } = foundry.documents;
Hooks.once("i18nInit", async function() {
const lang = game.i18n.lang;
await game.i18n.setLanguage("de");
const fallback = game.i18n.translations;
await game.i18n.setLanguage(lang);
game.i18n._fallback = foundry.utils.mergeObject(fallback, game.i18n._fallback);
});
class DSA41_Notifications extends Notifications {
notify(message, type="info", {localize=false, permanent=false, console=true}={}) {
if (permanent && message === "ERROR.RESOLUTION.Window")
return;
return super.notify(message, type, { localize, permanent, console });
}
}
Hooks.once("init", async function() {
// CONFIG.debug.applications = true;
// CONFIG.debug.combat = true;
// CONFIG.debug.dice = true;
// CONFIG.debug.documents = true;
// CONFIG.debug.hooks = true;
// CONFIG.debug.rollParsing = true;
// CONFIG.ActiveEffect.documentClass = ActiveEffect;
// CONFIG.ActiveEffect.legacyTransferral = false;
CONFIG.ui.notifications = DSA41_Notifications
CONFIG.Actor.dataModels.Player = DSA41_CharacterData;
CONFIG.Actor.documentClass = DSA41_CharacterDocument;
CONFIG.ChatMessage.documentClass = DSA41_ChatMessage;
CONFIG.Combat.initiative.formula = "1d6 + @computed.initiative.wert";
CONFIG.Item.dataModels.Gegenstand = DSA41_GegenstandData;
CONFIG.Item.dataModels.Ruestung = DSA41_RuestungData;
CONFIG.Item.dataModels.Bewaffnung = DSA41_BewaffnungData;
CONFIG.Item.dataModels.Talent = DSA41_TalentData;
CONFIG.Item.dataModels.Kampftalent = DSA41_KampftalentData;
CONFIG.Item.dataModels.Sonderfertigkeit = DSA41_SonderfertigkeitData;
CONFIG.Item.dataModels.VorNachteil = DSA41_VorNachteilData;
CONFIG.Item.dataModels.Zauber = DSA41_ZauberData;
CONFIG.statusEffects = [];
DocumentSheetConfig.registerSheet(Actor, "dsa41", DSA41_ActorSheet, {
makeDefault: true,
types: [
"Player",
]
});
DocumentSheetConfig.registerSheet(Item, "dsa41", DSA41_ItemSheetV2, {
makeDefault: true,
types: [
"Gegenstand",
"Ruestung",
"Bewaffnung",
"Talent",
"Kampftalent",
"Sonderfertigkeit",
"VorNachteil",
"Zauber",
]
});
Handlebars.registerHelper({
"string-length": (value, options) => {
return value.toString().length;
}
});
Handlebars.registerHelper({
"mul": (a, b, options) => {
return a * b;
}
});
Handlebars.registerHelper({
maybeLocalize: (value, options) => {
const prefix = options.hash.prefix ? options.hash.prefix.string : null;
if (prefix) {
const id = prefix + "." + value;
const localized = game.i18n.localize(id);
return id === localized ? value : localized;
}
return value;
}
});
Handlebars.registerHelper({
sorted: (array, options) => {
return array.toSorted((a, b) => a.sort - b.sort);
}
});
Handlebars.registerHelper({
DSA41_input: (field_name, options) => {
field_name = field_name.toString();
let fields = field_name.startsWith("system.") ? options.data.root.document.system.schema.fields : options.data.root.document.schema.fields;
let field = foundry.utils.getProperty(fields, field_name.replace(/^(system\.)/, "").replaceAll(".", ".fields."));
if (!field) {
console.error("Non-existent data field provided to {{DSA4_input}} handlebars helper.");
return Handlebars.SafeString("");
}
options.hash.placeholder ??= game.i18n.localize(options.hash.subtitle);
const value = foundry.utils.getProperty(options.data.root.document, field_name);
const input = field.toInput({ localize: true, value: value, ...options.hash });
if (options.hash.subtitle) {
let subtitle_div = document.createElement("div");
subtitle_div.className = "subtitle";
subtitle_div.innerText = game.i18n.localize(options.hash.subtitle);
let outer_div = document.createElement("div");
outer_div.replaceChildren(input, subtitle_div);
return new Handlebars.SafeString(outer_div.outerHTML);
}
return new Handlebars.SafeString(input.outerHTML);
}
});
await loadTemplates({
"editable-input": "systems/dsa-4th-edition/src/EditableInput.hbs",
"attribute_tooltip": "systems/dsa-4th-edition/src/Tooltips/Attribute.hbs",
"attacke_tooltip": "systems/dsa-4th-edition/src/Tooltips/Attacke.hbs",
"parade_tooltip": "systems/dsa-4th-edition/src/Tooltips/Parade.hbs",
"trefferpunkte_tooltip": "systems/dsa-4th-edition/src/Tooltips/Trefferpunkte.hbs",
"fernkampf_attacke_tooltip": "systems/dsa-4th-edition/src/Tooltips/FernkampfAttacke.hbs",
"fernkampf_trefferpunkte_tooltip": "systems/dsa-4th-edition/src/Tooltips/FernkampfTrefferpunkte.hbs",
"CHAT_HEADER": "systems/dsa-4th-edition/src/Chat/Header.hbs",
"TrefferpunkteTargets": "systems/dsa-4th-edition/src/Chat/TrefferpunkteTargets.hbs",
"talent_chat": "systems/dsa-4th-edition/src/Chat/Talent.hbs",
"zauber_chat": "systems/dsa-4th-edition/src/Chat/Zauber.hbs",
});
});
function string_compare(a, b) {
return a > b ? 1 : a < b ? -1 : 0;
}
function get_targeted_actors() {
const targets = [];
for (const token of game.user.targets) {
if (token.actor) {
targets.push(token.actor.uuid);
}
}
return targets;
}
async function instantiateTemplate(template, context) {
const html = await renderTemplate(template, context);
const template_element = document.createElement("template");
template_element.innerHTML = html;
return template_element.content.firstChild;
}
class DSA41_ChatMessage extends ChatMessage {
get actor() {
if (this.speaker.scene && this.speaker.token) {
const scene = game.scenes.get(this.speaker.scene);
const token = scene?.tokens.get(this.speaker.token);
if (token) return token.actor;
}
return game.actors.get(this.speaker.actor);
}
async renderHTML({ canDelete, canClose=false, ...rest }={}) {
const html = await super.renderHTML({ canDelete, canClose, ...rest });
const img = this.actor?.img ?? this.author.avatar;
const name = this.alias;
const header = await instantiateTemplate("CHAT_HEADER", { img: img, name: name, author: this.author?.name ?? "" });
const sender = html.querySelector(".message-sender");
sender?.replaceChildren(header);
if (this.flags.dsa41?.type === "trefferpunkte" && this.flags.dsa41?.targets?.length != 0) {
const targets = this.flags.dsa41?.targets.map(x => fromUuidSync(x, { strict: false })).filter(x => x !== null);
const targets_list = await instantiateTemplate("TrefferpunkteTargets", targets);
html.querySelector(".message-content")?.appendChild(targets_list);
}
for (const element of html.querySelectorAll("[data-action]")) {
element.addEventListener("click", async event => {
const target = event.target;
const action = target.dataset.action;
if (action === null) return;
if (action === "apply_damage") {
const target_actor_id = target.closest("[data-actor-id]")?.dataset.actorId;
if (!target_actor_id) return;
const target_actor = fromUuidSync(target_actor_id);
if (!target_actor) return;
const dialog_data = await DSA41_Dialog.wait("Trefferzone", { window: {title: "Trefferzone"} });
const trefferzone = dialog_data.trefferzone;
const rolled_damage = this.rolls[0].total;
const target_hp = target_actor.system.lebenspunkte.aktuell;
const target_rs = target_actor.system.computed.kampf.ruestungen_gesamt[trefferzone];
const damage = Math.max(rolled_damage - target_rs, 0);
const new_hp = target_hp - damage;
target_actor.update({ "system.lebenspunkte.aktuell": new_hp });
}
});
}
return html;
}
}
function get_minified_formula(formula, data) {
const terms = Roll.simplifyTerms(Roll.parse(formula, data));
let output = [];
let is_combinable = true;
for (let i = 0; i < terms.length; i++) {
const term = terms[i];
if (is_combinable && term instanceof NumericTerm) {
for (let j = i + 1; j < terms.length - 1; j += 2) {
let operator = terms[j];
let next = terms[j + 1];
if (!(operator instanceof OperatorTerm && next instanceof NumericTerm))
break;
switch (operator.operator) {
case "+": {
term.number += next.number;
i += 2;
} break;
case "-": {
term.number -= next.number;
i += 2;
} break;
default:
break;
}
}
is_combinable = false;
} else {
if (term instanceof OperatorTerm && (term.operator == "+" || term.operator == "-")) {
is_combinable = true;
} else {
is_combinable = false;
}
}
output.push(term);
}
output = Roll.simplifyTerms(output);
return Roll.getFormula(output);
}
class PreisUnitField extends StringField {
constructor(options={}, context={}) {
return super({
required: true,
choices: {
"kreuzer": "DSA41.currency.kreuzer",
"heller": "DSA41.currency.heller",
"silbertaler": "DSA41.currency.silbertaler",
"dukaten": "DSA41.currency.dukaten",
},
initial: "silbertaler",
...options
}, context);
}
}
class PreisField extends SchemaField {
constructor() {
return super({
value: new NumberField({ initial: 0, min: 0 }),
unit: new PreisUnitField(),
});
}
_toInput(config) {
const value_input = this.fields["value"].toInput({ value: config.value.value, });
const unit_input = this.fields["unit"] .toInput({ value: config.value.unit, localize: true });
let outer_div = document.createElement("div");
outer_div.className = "price-input";
outer_div.replaceChildren(value_input, unit_input);
return outer_div;
}
}
class GewichtUnitField extends StringField {
constructor(options={}, context={}) {
return super({
required: true,
choices: {
"gran": "DSA41.weight.gran",
"karat": "DSA41.weight.karat",
"skrupel": "DSA41.weight.skrupel",
"unze": "DSA41.weight.unze",
"stein": "DSA41.weight.stein",
"sack": "DSA41.weight.sack",
"quader": "DSA41.weight.quader",
},
initial: "stein",
...options
}, context);
}
}
class GewichtField extends SchemaField {
constructor() {
return super({
value: new NumberField({ initial: 0, min: 0 }),
unit: new GewichtUnitField(),
});
}
_toInput(config) {
const value_input = this.fields["value"].toInput({ value: config.value.value, });
const unit_input = this.fields["unit"] .toInput({ value: config.value.unit, localize: true });
let outer_div = document.createElement("div");
outer_div.className = "weight-input";
outer_div.replaceChildren(value_input, unit_input);
return outer_div;
}
}
class LaengeUnitField extends StringField {
constructor(options={}, context={}) {
return super({
required: true,
choices: {
"halbfinger": "DSA41.laenge.halbfinger",
"finger": "DSA41.laenge.finger",
"spann": "DSA41.laenge.spann",
"schritt": "DSA41.laenge.schritt",
"faden": "DSA41.laenge.faden",
"lot": "DSA41.laenge.lot",
"meile": "DSA41.laenge.meile",
"tagesreise": "DSA41.laenge.tagesreise",
"baryd": "DSA41.laenge.baryd",
},
initial: "schritt",
...options
}, context);
}
}
class LaengeField extends SchemaField {
constructor() {
return super({
value: new NumberField({ initial: 0, min: 0 }),
unit: new LaengeUnitField(),
});
}
_toInput(config) {
const value_input = this.fields["value"].toInput({ value: config.value.value, });
const unit_input = this.fields["unit"] .toInput({ value: config.value.unit, localize: true });
let outer_div = document.createElement("div");
outer_div.className = "length-input";
outer_div.replaceChildren(value_input, unit_input);
return outer_div;
}
}
class AttributeField extends SchemaField {
constructor() {
return super({
initial: new NumberField({ integer: true, initial: 8, min: 0 }),
advancement: new NumberField({ integer: true, initial: 0, min: 0 }),
modifier: new NumberField({ integer: true, initial: 0 }),
});
}
}
class AttributeChoiceField extends StringField {
constructor(options={}, context={}) {
return super({
required: true,
choices: {
"courage": "DSA41.attributes.long.courage",
"cleverness": "DSA41.attributes.long.cleverness",
"intuition": "DSA41.attributes.long.intuition",
"charisma": "DSA41.attributes.long.charisma",
"dexterity": "DSA41.attributes.long.dexterity",
"agility": "DSA41.attributes.long.agility",
"constitution": "DSA41.attributes.long.constitution",
"strength": "DSA41.attributes.long.strength",
},
initial: "courage",
...options,
}, context);
}
}
class SteigerungsKategorieField extends StringField {
constructor(options={}, context={}) {
return super({
required: true,
choices: {
"A_Star": "DSA41.steigerungskategorie.A_Star",
"A": "DSA41.steigerungskategorie.A",
"B": "DSA41.steigerungskategorie.B",
"C": "DSA41.steigerungskategorie.C",
"D": "DSA41.steigerungskategorie.D",
"E": "DSA41.steigerungskategorie.E",
"F": "DSA41.steigerungskategorie.F",
"G": "DSA41.steigerungskategorie.G",
"H": "DSA41.steigerungskategorie.H",
},
initial: "A_Star",
...options,
}, context);
}
}
class DSA41_CharacterDocument extends Actor {
static async create(data, operation) {
const actor = await super.create(data, operation);
if (data.type === "Player") {
actor.prototypeToken.update({ actorLink: true });
const talente_compendium = game.packs.get("dsa-4th-edition.talente");
const talente = await talente_compendium.getDocuments({ name__in: [
// Basis Körperliche Talente
"Athletik",
"Klettern",
"Körperbeherrschung",
"Schleichen",
"Schwimmen",
"Selbstbeherrschung",
"Sich Verstecken",
"Singen",
"Sinnenschärfe",
"Tanzen",
"Zechen",
// Basis Gesellschaftliche Talente
"Menschenkenntnis",
"Überreden",
// Basis Natur Talente
"Fährtensuchen",
"Orientierung",
"Wildnisleben",
// Basis Wissens Talente
"Götter/Kulte",
"Rechnen",
"Sagen/Legenden",
// Basis Handwerks Talente
"Heilkunde Wunden",
"Holzbearbeitung",
"Kochen",
"Lederarbeiten",
"Malen/Zeichnen",
"Schneidern",
// Basis Kampftalente
"Dolche",
"Hiebwaffen",
"Raufen",
"Ringen",
"Säbel",
"Wurfmesser",
]});
await actor.createEmbeddedDocuments("Item", talente);
}
return actor;
}
get_hp_roll_modifier() {
if (this.system.lebenspunkte.aktuell < Math.round(this.system.computed.lebenspunkte.max / 4)) return 3;
if (this.system.lebenspunkte.aktuell < Math.round(this.system.computed.lebenspunkte.max / 3)) return 2;
if (this.system.lebenspunkte.aktuell < Math.round(this.system.computed.lebenspunkte.max / 2)) return 1;
return 0;
}
}
class DSA41_CharacterData extends TypeDataModel {
static defineSchema() {
return {
race: new StringField(),
culture: new StringField(),
profession: new StringField(),
sozialstatus: new NumberField({ integer: true, inital: 1 }),
allgemein: new SchemaField({
geschlecht: new StringField(),
alter: new StringField(),
groesse: new LaengeField(),
gewicht: new GewichtField(),
haarfarbe: new StringField(),
augenfarbe: new StringField(),
stand: new StringField(),
titel: new StringField(),
aussehen: new StringField(),
hintergrund: new StringField(),
biografie: new StringField(),
}),
abenteuerpunkte: new SchemaField({
ausgegeben: new NumberField({ integer: true, initial: 0 }),
gesamt: new NumberField({ integer: true, initial: 0 }),
}),
currency: new SchemaField({
dukaten: new NumberField({ integer: true, initial: 0 }),
silbertaler: new NumberField({ integer: true, initial: 0 }),
heller: new NumberField({ integer: true, initial: 0 }),
kreuzer: new NumberField({ integer: true, initial: 0 }),
}),
attributes: new SchemaField({
courage: new AttributeField(),
cleverness: new AttributeField(),
intuition: new AttributeField(),
charisma: new AttributeField(),
dexterity: new AttributeField(),
agility: new AttributeField(),
constitution: new AttributeField(),
strength: new AttributeField(),
}),
lebenspunkte: new SchemaField({
modifikator: new NumberField({integer: true, initial: 0}),
zukauf: new NumberField({integer: true, initial: 0}),
verlust: new NumberField({integer: true, initial: 0}),
aktuell: new NumberField({integer: true, initial: 0}),
}),
ausdauer: new SchemaField({
modifikator: new NumberField({integer: true, initial: 0}),
zukauf: new NumberField({integer: true, initial: 0}),
verlust: new NumberField({integer: true, initial: 0}),
aktuell: new NumberField({integer: true, initial: 0}),
}),
astralenergie: new SchemaField({
modifikator: new NumberField({integer: true, initial: 0}),
zukauf: new NumberField({integer: true, initial: 0}),
verlust: new NumberField({integer: true, initial: 0}),
aktuell: new NumberField({integer: true, initial: 0}),
}),
karmalenergie: new NumberField({integer: true, inital: 0}),
magieresistenz: new SchemaField({
modifikator: new NumberField({integer: true, initial: 0}),
zukauf: new NumberField({integer: true, initial: 0}),
aktuell: new NumberField({integer: true, initial: 0}),
}),
modifikator_initiative: new NumberField({integer: true, initial: 0}),
modifikator_attacke: new NumberField({integer: true, initial: 0}),
modifikator_parade: new NumberField({integer: true, initial: 0}),
modifikator_fernkampf: new NumberField({integer: true, initial: 0}),
wunden: new SchemaField({
kopf: new NumberField({integer: true, initial: 0, min: 0, max: 3 }),
brust: new NumberField({integer: true, initial: 0, min: 0, max: 3 }),
bauch: new NumberField({integer: true, initial: 0, min: 0, max: 3 }),
linker_arm: new NumberField({integer: true, initial: 0, min: 0, max: 3 }),
rechter_arm: new NumberField({integer: true, initial: 0, min: 0, max: 3 }),
linkes_bein: new NumberField({integer: true, initial: 0, min: 0, max: 3 }),
rechtes_bein: new NumberField({integer: true, initial: 0, min: 0, max: 3 }),
}),
}
}
async prepareDerivedData() {
super.prepareDerivedData();
this.computed = {
abenteuerpunkte: {},
attributes: {},
attributes_without_modifiers: {},
num_vorteile: 0,
num_nachteile: 0,
num_allgemeine_sonderfertigkeiten: 0,
num_kampf_sonderfertigkeiten: 0,
num_magische_sonderfertigkeiten: 0,
num_waffen: 0,
num_fernkampf_waffen: 0,
kampf: {
talente: {},
waffen: {},
fernkampf_waffen: {},
ruestungen: {},
ruestungen_gesamt: {
kopf: 0,
brust: 0,
ruecken: 0,
bauch: 0,
linker_arm: 0,
rechter_arm: 0,
linkes_bein: 0,
rechtes_bein: 0,
gesamt_ruestungsschutz: 0.0,
gesamt_behinderung: 0.0,
},
},
wunden_modifiers: {
courage: 0,
cleverness: 0,
intuition: 0,
dexterity: 0,
agility: 0,
constitution: 0,
strength: 0,
initiative_basis: 0,
initiative: "",
attacke: 0,
parade: 0,
schaden: "",
},
};
if (this.wunden.kopf >= 1) {
this.computed.wunden_modifiers.courage += -2 * this.wunden.kopf;
this.computed.wunden_modifiers.cleverness += -2 * this.wunden.kopf;
this.computed.wunden_modifiers.intuition += -2 * this.wunden.kopf;
this.computed.wunden_modifiers.initiative_basis += -2 * this.wunden.kopf;
this.computed.wunden_modifiers.initiative += (-2 * this.wunden.kopf) + "d6";
if (this.wunden.kopf >= 3) this.computed.wunden_modifiers.schaden += "+ 2d6";
}
if (this.wunden.brust >= 1) {
this.computed.wunden_modifiers.attacke += -this.wunden.brust;
this.computed.wunden_modifiers.parade += -this.wunden.brust;
this.computed.wunden_modifiers.constitution += -this.wunden.brust;
this.computed.wunden_modifiers.strength += -this.wunden.brust;
this.computed.wunden_modifiers.schaden += "+" + this.wunden.brust + "d6";
}
if (this.wunden.bauch >= 1) {
this.computed.wunden_modifiers.attacke += -this.wunden.bauch;
this.computed.wunden_modifiers.parade += -this.wunden.bauch;
this.computed.wunden_modifiers.constitution += -this.wunden.bauch;
this.computed.wunden_modifiers.strength += -this.wunden.bauch;
this.computed.wunden_modifiers.initiative_basis += -this.wunden.bauch;
this.computed.wunden_modifiers.schaden += "+" + this.wunden.bauch + "d6";
}
if (this.wunden.linker_arm >= 1) {
this.computed.wunden_modifiers.attacke += -2 * this.wunden.linker_arm;
this.computed.wunden_modifiers.parade += -2 * this.wunden.linker_arm;
this.computed.wunden_modifiers.strength += -2 * this.wunden.linker_arm;
this.computed.wunden_modifiers.dexterity += -2 * this.wunden.linker_arm;
}
if (this.wunden.rechter_arm >= 1) {
this.computed.wunden_modifiers.attacke += -2 * this.wunden.rechter_arm;
this.computed.wunden_modifiers.parade += -2 * this.wunden.rechter_arm;
this.computed.wunden_modifiers.strength += -2 * this.wunden.rechter_arm;
this.computed.wunden_modifiers.dexterity += -2 * this.wunden.rechter_arm;
}
if (this.wunden.linkes_bein >= 1) {
this.computed.wunden_modifiers.attacke += -2 * this.wunden.linkes_bein;
this.computed.wunden_modifiers.parade += -2 * this.wunden.linkes_bein;
this.computed.wunden_modifiers.agility += -2 * this.wunden.linkes_bein;
this.computed.wunden_modifiers.initiative_basis += -2 * this.wunden.linkes_bein;
}
if (this.wunden.rechtes_bein >= 1) {
this.computed.wunden_modifiers.attacke += -2 * this.wunden.rechtes_bein;
this.computed.wunden_modifiers.parade += -2 * this.wunden.rechtes_bein;
this.computed.wunden_modifiers.agility += -2 * this.wunden.rechtes_bein;
this.computed.wunden_modifiers.initiative_basis += -2 * this.wunden.rechtes_bein;
}
this.computed.abenteuerpunkte.uebrig = this.abenteuerpunkte.gesamt - this.abenteuerpunkte.ausgegeben;
this.computed.attributes_without_modifiers.courage = this.attributes.courage.initial + this.attributes.courage.advancement;
this.computed.attributes_without_modifiers.cleverness = this.attributes.cleverness.initial + this.attributes.cleverness.advancement;
this.computed.attributes_without_modifiers.intuition = this.attributes.intuition.initial + this.attributes.intuition.advancement;
this.computed.attributes_without_modifiers.charisma = this.attributes.charisma.initial + this.attributes.charisma.advancement;
this.computed.attributes_without_modifiers.dexterity = this.attributes.dexterity.initial + this.attributes.dexterity.advancement;
this.computed.attributes_without_modifiers.agility = this.attributes.agility.initial + this.attributes.agility.advancement;
this.computed.attributes_without_modifiers.constitution = this.attributes.constitution.initial + this.attributes.constitution.advancement;
this.computed.attributes_without_modifiers.strength = this.attributes.strength.initial + this.attributes.strength.advancement;
this.computed.attributes.courage = this.computed.attributes_without_modifiers.courage + this.attributes.courage.modifier + this.computed.wunden_modifiers.courage;
this.computed.attributes.cleverness = this.computed.attributes_without_modifiers.cleverness + this.attributes.cleverness.modifier + this.computed.wunden_modifiers.cleverness;
this.computed.attributes.intuition = this.computed.attributes_without_modifiers.intuition + this.attributes.intuition.modifier + this.computed.wunden_modifiers.intuition;
this.computed.attributes.charisma = this.computed.attributes_without_modifiers.charisma + this.attributes.charisma.modifier;
this.computed.attributes.dexterity = this.computed.attributes_without_modifiers.dexterity + this.attributes.dexterity.modifier + this.computed.wunden_modifiers.dexterity;
this.computed.attributes.agility = this.computed.attributes_without_modifiers.agility + this.attributes.agility.modifier + this.computed.wunden_modifiers.agility;
this.computed.attributes.constitution = this.computed.attributes_without_modifiers.constitution + this.attributes.constitution.modifier + this.computed.wunden_modifiers.constitution;
this.computed.attributes.strength = this.computed.attributes_without_modifiers.strength + this.attributes.strength.modifier + this.computed.wunden_modifiers.strength;
this.computed.lebenspunkte = {};
this.computed.ausdauer = {};
this.computed.astralenergie = {};
this.computed.magieresistenz = {};
this.computed.lebenspunkte.basiswert = Math.round((this.computed.attributes_without_modifiers.constitution + this.computed.attributes_without_modifiers.constitution + this.computed.attributes_without_modifiers.strength) / 2);
this.computed.ausdauer.basiswert = Math.round((this.computed.attributes_without_modifiers.courage + this.computed.attributes_without_modifiers.constitution + this.computed.attributes_without_modifiers.agility) / 2);
this.computed.astralenergie.basiswert = Math.round((this.computed.attributes_without_modifiers.courage + this.computed.attributes_without_modifiers.intuition + this.computed.attributes_without_modifiers.charisma) / 2);
this.computed.magieresistenz.basiswert = Math.round((this.computed.attributes_without_modifiers.courage + this.computed.attributes_without_modifiers.cleverness + this.computed.attributes_without_modifiers.constitution) / 5);
this.computed.lebenspunkte.max = this.computed.lebenspunkte.basiswert + this.lebenspunkte.modifikator + this.lebenspunkte.zukauf - this.lebenspunkte.verlust;
this.computed.ausdauer.max = this.computed.ausdauer.basiswert + this.ausdauer.modifikator + this.ausdauer.zukauf - this.ausdauer.verlust;
this.computed.astralenergie.max = this.computed.astralenergie.basiswert + this.astralenergie.modifikator + this.astralenergie.zukauf - this.astralenergie.verlust;
this.computed.magieresistenz.max = this.computed.magieresistenz.basiswert + this.magieresistenz.modifikator + this.magieresistenz.zukauf;
this.computed.lebenspunkte.prozent = this.lebenspunkte.aktuell / this.computed.lebenspunkte.max * 100;
this.computed.ausdauer.prozent = this.ausdauer.aktuell / this.computed.ausdauer.max * 100;
this.computed.astralenergie.prozent = this.astralenergie.aktuell / this.computed.astralenergie.max * 100;
this.computed.magieresistenz.prozent = this.magieresistenz.aktuell / this.computed.magieresistenz.max * 100;
this.computed.initiative = {};
this.computed.attacke = {};
this.computed.parade = {};
this.computed.fernkampf = {};
this.computed.initiative.basiswert = Math.round((this.computed.attributes_without_modifiers.courage + this.computed.attributes_without_modifiers.courage + this.computed.attributes_without_modifiers.intuition + this.computed.attributes_without_modifiers.agility) / 5);
this.computed.attacke.basiswert = Math.round((this.computed.attributes_without_modifiers.courage + this.computed.attributes_without_modifiers.agility + this.computed.attributes_without_modifiers.strength) / 5);
this.computed.parade.basiswert = Math.round((this.computed.attributes_without_modifiers.intuition + this.computed.attributes_without_modifiers.agility + this.computed.attributes_without_modifiers.strength) / 5);
this.computed.fernkampf.basiswert = Math.round((this.computed.attributes_without_modifiers.intuition + this.computed.attributes_without_modifiers.dexterity + this.computed.attributes_without_modifiers.strength) / 5);
const equipped_ruestungen = this.parent.items.filter((x) => x.type === "Ruestung" && x.system.angelegt === true);
for (const item of equipped_ruestungen) {
this.computed.kampf.ruestungen[item._id] = { item: item };
this.computed.kampf.ruestungen_gesamt.kopf += item.system.kopf;
this.computed.kampf.ruestungen_gesamt.brust += item.system.brust;
this.computed.kampf.ruestungen_gesamt.ruecken += item.system.ruecken;
this.computed.kampf.ruestungen_gesamt.bauch += item.system.bauch;
this.computed.kampf.ruestungen_gesamt.linker_arm += item.system.linker_arm;
this.computed.kampf.ruestungen_gesamt.rechter_arm += item.system.rechter_arm;
this.computed.kampf.ruestungen_gesamt.linkes_bein += item.system.linkes_bein;
this.computed.kampf.ruestungen_gesamt.rechtes_bein += item.system.rechtes_bein;
this.computed.kampf.ruestungen_gesamt.gesamt_ruestungsschutz += item.system.gesamt_ruestungsschutz;
this.computed.kampf.ruestungen_gesamt.gesamt_behinderung += item.system.gesamt_behinderung;
}
for (let [key, value] of Object.entries(this.computed.kampf.ruestungen_gesamt)) {
this.computed.kampf.ruestungen_gesamt[key] = Math.round(value);
}
this.computed.initiative.wert = this.computed.initiative.basiswert + this.modifikator_initiative - this.computed.kampf.ruestungen_gesamt.gesamt_behinderung + this.computed.wunden_modifiers.initiative_basis;
this.computed.attacke.wert = this.computed.attacke.basiswert + this.modifikator_attacke + this.computed.wunden_modifiers.attacke;
this.computed.parade.wert = this.computed.parade.basiswert + this.modifikator_parade + this.computed.wunden_modifiers.parade;
this.computed.fernkampf.wert = this.computed.fernkampf.basiswert + this.modifikator_fernkampf;
this.kampftalente = this.parent.items.filter((x) => x.type === "Kampftalent").sort((a, b) => string_compare(a.name, b.name));
for(const talent of this.kampftalente) {
this.computed.kampf.talente[talent.name] = {};
this.computed.kampf.talente[talent.name].attacke = (talent.system.kategorie === "fernkampf" ? this.computed.fernkampf.wert : this.computed.attacke.wert) + talent.system.attacke;
this.computed.kampf.talente[talent.name].parade = this.computed.parade.wert + talent.system.parade;
this.computed.kampf.talente[talent.name].talent_attacke = talent.system.attacke;
this.computed.kampf.talente[talent.name].talent_parade = talent.system.parade;
}
const equipped_bewaffnung = this.parent.items.filter((x) => x.type === "Bewaffnung" && x.system.angelegt === true);
const equipped_nahkampfwaffen = equipped_bewaffnung.filter((x) => x.system.nahkampfwaffe.aktiv);
const equipped_parierwaffen = equipped_bewaffnung.filter((x) => x.system.parierwaffe.aktiv);
const equipped_schilde = equipped_bewaffnung.filter((x) => x.system.schild.aktiv);
const equipped_fernkampfwaffen = equipped_bewaffnung.filter((x) => x.system.fernkampfwaffe.aktiv);
for(const item of equipped_nahkampfwaffen) {
if (item.system.nahkampfwaffe.aktiv) {
this.computed.num_waffen += 1;
let computed = this.computed.kampf.waffen[item._id] = {
item: item,
attacke: 0,
parade: 0,
parade_crit: 0,
trefferpunkte: "",
trefferpunkte_display: "",
basis_attacke: 0,
basis_parade: 0,
talent_attacke: 0,
talent_parade: 0,
modifikator_attacke: 0,
modifikator_parade: 0,
tp_kk: 0,
parierwaffe_attacke: 0,
parierwaffe_parade: 0,
schild_attacke: 0,
schild_parade: 0,
};
computed.basis_attacke = this.computed.attacke.wert;
computed.basis_parade = this.computed.parade.wert;
const talent = item.system.nahkampfwaffe.kampftalente;
computed.talent_attacke = this.computed.kampf.talente[talent]?.talent_attacke ?? 0;
computed.talent_parade = this.computed.kampf.talente[talent]?.talent_parade ?? 0;
computed.modifikator_attacke = item.system.nahkampfwaffe.modifikator_attacke;
computed.modifikator_parade = item.system.nahkampfwaffe.modifikator_parade;
if (item.system.nahkampfwaffe.schadensschritte != 0) {
computed.tp_kk = Math.trunc((this.computed.attributes.strength - item.system.nahkampfwaffe.schwellenwert) / item.system.nahkampfwaffe.schadensschritte);
}
for (const parierwaffe of equipped_parierwaffen) {
if (parierwaffe._id === item._id) continue;
computed.parierwaffe_attacke += parierwaffe.system.parierwaffe.modifikator_attacke;
computed.parierwaffe_parade += parierwaffe.system.parierwaffe.modifikator_parade;
}
for (const schild of equipped_schilde) {
if (schild._id === item._id) continue;
computed.schild_attacke += schild.system.schild.modifikator_attacke;
computed.schild_parade += schild.system.schild.modifikator_parade;
}
computed.attacke = computed.basis_attacke + computed.talent_attacke + computed.modifikator_attacke + computed.parierwaffe_attacke + computed.schild_attacke + Math.min(computed.tp_kk, 0);
computed.parade = computed.basis_parade + computed.talent_parade + computed.modifikator_parade + computed.parierwaffe_parade + computed.schild_parade + Math.min(computed.tp_kk, 0);
computed.parade_crit = Math.round(computed.parade / 2);
computed.trefferpunkte = get_minified_formula(item.system.nahkampfwaffe.basis + (computed.tp_kk != 0 ? " + " + computed.tp_kk : ""));
computed.trefferpunkte_display = computed.trefferpunkte.replace(/[\+\-]/, (op) => "<br>" + op);
}
}
for (const item of equipped_fernkampfwaffen) {
this.computed.num_fernkampf_waffen += 1;
let computed = this.computed.kampf.fernkampf_waffen[item._id] = {
item: item,
attacke: 0,
trefferpunkte: "",
trefferpunkte_display: "",
basis_attacke: 0,
talent_attacke: 0,
};
computed.basis_attacke = this.computed.fernkampf.wert;
const talent = item.system.nahkampfwaffe.kampftalente;
computed.talent_attacke = this.computed.kampf.talente[talent]?.talent_attacke ?? 0;
computed.attacke = computed.basis_attacke + computed.talent_attacke;
computed.trefferpunkte = get_minified_formula(item.system.fernkampfwaffe.basis);
computed.trefferpunkte_display = computed.trefferpunkte.replace(/[\+\-]/, (op) => "<br>" + op);
}
const talente = this.parent.items.filter((x) => x.type === "Talent").sort((a, b) => string_compare(a.name, b.name));
this.talente = {
koerperliche: talente.filter((x) => x.system.kategorie === "koerperliche"),
gesellschaftliche: talente.filter((x) => x.system.kategorie === "gesellschaftliche"),
natur: talente.filter((x) => x.system.kategorie === "natur"),
wissens: talente.filter((x) => x.system.kategorie === "wissens"),
handwerks: talente.filter((x) => x.system.kategorie === "handwerks"),
};
this.computed.num_allgemeine_sonderfertigkeiten = this.parent.items.filter((x) => x.type === "Sonderfertigkeit" && x.system.kategorie === "allgemein").length;
this.computed.num_kampf_sonderfertigkeiten = this.parent.items.filter((x) => x.type === "Sonderfertigkeit" && x.system.kategorie === "kampf").length;
this.computed.num_magische_sonderfertigkeiten = this.parent.items.filter((x) => x.type === "Sonderfertigkeit" && x.system.kategorie === "magisch").length;
this.computed.num_vorteile = this.parent.items.filter((x) => x.type === "VorNachteil" && x.system.kategorie === "vorteil").length;
this.computed.num_nachteile = this.parent.items.filter((x) => x.type === "VorNachteil" && x.system.kategorie === "nachteil").length;
}
}
class DSA41_GegenstandData extends TypeDataModel {
static defineSchema() {
return {
gewicht: new GewichtField(),
preis: new PreisField(),
anzahl: new NumberField({ integer: true, initial: 1, min: 0 }),
beschreibung: new StringField({ initial: "" }),
};
}
}
class DSA41_RuestungData extends TypeDataModel {
static defineSchema() {
return {
angelegt: new BooleanField({ initial: false }),
gewicht: new GewichtField(),
preis: new PreisField(),
kopf: new NumberField({ integer: true, initial: 0, min: 0 }),
brust: new NumberField({ integer: true, initial: 0, min: 0 }),
ruecken: new NumberField({ integer: true, initial: 0, min: 0 }),
bauch: new NumberField({ integer: true, initial: 0, min: 0 }),
linker_arm: new NumberField({ integer: true, initial: 0, min: 0 }),
rechter_arm: new NumberField({ integer: true, initial: 0, min: 0 }),
linkes_bein: new NumberField({ integer: true, initial: 0, min: 0 }),
rechtes_bein: new NumberField({ integer: true, initial: 0, min: 0 }),
gesamt_ruestungsschutz: new NumberField({ integer: false, initial: 0, min: 0 }),
gesamt_behinderung: new NumberField({ integer: false, initial: 0, min: 0 }),
beschreibung: new StringField({ initial: "" }),
};
}
}
class DSA41_BewaffnungData extends TypeDataModel {
static defineSchema() {
return {
angelegt: new BooleanField({ initial: false }),
gewicht: new GewichtField(),
preis: new PreisField(),
beschreibung: new StringField({ initial: "" }),
nahkampfwaffe: new SchemaField({
aktiv: new BooleanField({ initial: false }),
basis: new StringField({ initial: "1d4" }),
schwellenwert: new NumberField({ integer: true, initial: 0, min: 0 }),
schadensschritte: new NumberField({ integer: true, initial: 0, min: 0 }),
modifikator_attacke: new NumberField({ integer: true, initial: 0 }),
modifikator_parade: new NumberField({ integer: true, initial: 0 }),
initiative: new NumberField({ integer: true, initial: 0 }),
bruchfaktor: new NumberField({ integer: true, initial: 0 }),
distanzklasse: new StringField({ initial: "" }),
kampftalente: new StringField({ initial: "" }),
laenge: new LaengeField(),
zweihaendig: new BooleanField({ initial: false }),
werfbar: new BooleanField({ initial: false }),
improvisiert: new BooleanField({ initial: false }),
priviligiert: new BooleanField({ initial: false }),
}),
parierwaffe: new SchemaField({
aktiv: new BooleanField({ initial: false }),
modifikator_attacke: new NumberField({ integer: true, initial: 0 }),
modifikator_parade: new NumberField({ integer: true, initial: 0 }),
initiative: new NumberField({ integer: true, initial: 0 }),
bruchfaktor: new NumberField({ integer: true, initial: 0 }),
}),
schild: new SchemaField({
aktiv: new BooleanField({ initial: false }),
groesse: new StringField({
required: true,
choices: {
"klein": "DSA41.bewaffnung.schild.groesse.klein",
"gross": "DSA41.bewaffnung.schild.groesse.gross",
"sehr_gross": "DSA41.bewaffnung.schild.groesse.sehr_gross",
},
initial: "klein",
}),
modifikator_attacke: new NumberField({ integer: true, initial: 0 }),
modifikator_parade: new NumberField({ integer: true, initial: 0 }),
initiative: new NumberField({ integer: true, initial: 0 }),
bruchfaktor: new NumberField({ integer: true, initial: 0 }),
}),
fernkampfwaffe: new SchemaField({
aktiv: new BooleanField({ initial: false }),
basis: new StringField({ initial: "1d6 + 1" }),
laden: new NumberField({ integer: true, initial: 0 }),
reichweite1: new NumberField({ integer: true, initial: 0 }),
reichweite2: new NumberField({ integer: true, initial: 0 }),
reichweite3: new NumberField({ integer: true, initial: 0 }),
reichweite4: new NumberField({ integer: true, initial: 0 }),
reichweite5: new NumberField({ integer: true, initial: 0 }),
modifikator1: new NumberField({ integer: true, initial: 0, nullable: true }),
modifikator2: new NumberField({ integer: true, initial: 0, nullable: true }),
modifikator3: new NumberField({ integer: true, initial: 0, nullable: true }),
modifikator4: new NumberField({ integer: true, initial: 0, nullable: true }),
modifikator5: new NumberField({ integer: true, initial: 0, nullable: true }),
munitionskosten: new NumberField({ integer: true, initial: 0 }),
munitionsgewicht: new NumberField({ integer: true, initial: 0 }),
}),
};
}
}
class DSA41_TalentData extends TypeDataModel {
static defineSchema() {
return {
kategorie: new StringField({
required: true,
choices: {
"koerperliche": "DSA41.talente.koerperliche.label",
"gesellschaftliche": "DSA41.talente.gesellschaftliche.label",
"natur": "DSA41.talente.natur.label",
"wissens": "DSA41.talente.wissens.label",
"handwerks": "DSA41.talente.handwerks.label",
},
initial: "koerperliche"
}),
behinderung: new StringField({ initial: "" }),
attribute1: new AttributeChoiceField(),
attribute2: new AttributeChoiceField(),
attribute3: new AttributeChoiceField(),
talentwert: new NumberField({ integer: true, initial: 0 }),
beschreibung: new StringField({ initial: "" }),
};
}
}
class DSA41_KampftalentData extends TypeDataModel {
static defineSchema() {
return {
kategorie: new StringField({
required: true,
choices: {
"waffenlos": "DSA41.kampftalent.kategorie.waffenlos",
"nahkampf": "DSA41.kampftalent.kategorie.nahkampf",
"fernkampf": "DSA41.kampftalent.kategorie.fernkampf",
},
initial: "waffenlos"
}),
behinderung: new StringField({ initial: "" }),
steigern: new SteigerungsKategorieField(),
beschreibung: new StringField({ initial: "" }),
talentwert: new NumberField({ integer: true, initial: 0 }),
attacke: new NumberField({ integer: true, initial: 0 }),
parade: new NumberField({ integer: true, initial: 0 }),
};
}
}
class DSA41_SonderfertigkeitData extends TypeDataModel {
static defineSchema() {
return {
kategorie: new StringField({
required: true,
choices: {
"allgemein": "DSA41.sonderfertigkeiten.kategorie.allgemein",
"kampf": "DSA41.sonderfertigkeiten.kategorie.kampf",
"magisch": "DSA41.sonderfertigkeiten.kategorie.magisch",
"klerikal": "DSA41.sonderfertigkeiten.kategorie.klerikal",
},
initial: "allgemein",
}),
kosten: new NumberField({ integer: true, initial: 0 }),
verbreitung: new NumberField({ integer: true, initial: 0 }),
beschreibung: new StringField({ initial: "" }),
};
}
}
class DSA41_VorNachteilData extends TypeDataModel {
static defineSchema() {
return {
kategorie: new StringField({
required: true,
choices: {
"vorteil": "DSA41.vornachteil.kategorie.vorteil",
"nachteil": "DSA41.vornachteil.kategorie.nachteil",
},
initial: "vorteil",
}),
kosten: new NumberField({ integer: true, initial: 0 }),
beschreibung: new StringField({ initial: "" }),
};
}
}
class DSA41_ZauberData extends TypeDataModel {
static defineSchema() {
return {
attribute1: new AttributeChoiceField(),
attribute2: new AttributeChoiceField(),
attribute3: new AttributeChoiceField(),
zauberdauer: new StringField(),
wirkungsdauer: new StringField(),
kosten: new StringField(),
komplexitaet: new SteigerungsKategorieField(),
repraesentation: new StringField({
required: true,
choices: {
"borbaradianisch": "DSA41.zauber.repraesentation.borbaradianisch",
"druidisch": "DSA41.zauber.repraesentation.druidisch",
"elfisch": "DSA41.zauber.repraesentation.elfisch",
"geodisch": "DSA41.zauber.repraesentation.geodisch",
"satuarisch": "DSA41.zauber.repraesentation.satuarisch",
"kristallomantisch": "DSA41.zauber.repraesentation.kristallomantisch",
"gildenmagisch": "DSA41.zauber.repraesentation.gildenmagisch",
"scharlatanisch": "DSA41.zauber.repraesentation.scharlatanisch",
"schelmisch": "DSA41.zauber.repraesentation.schelmisch",
},
initial: "borbaradianisch",
}),
merkmale: new SetField(new StringField({
choices: {
"anitmagie": "DSA41.zauber.merkmale.anitmagie",
"beschwoerung": "DSA41.zauber.merkmale.beschwoerung",
"daemonisch_allgemein": "DSA41.zauber.merkmale.daemonisch_allgemein",
"daemonisch_agrimoth_widharcal": "DSA41.zauber.merkmale.daemonisch_agrimoth_widharcal",
"daemonisch_amazeroth_iribaar": "DSA41.zauber.merkmale.daemonisch_amazeroth_iribaar",
"daemonisch_asfaloth_calijnaar": "DSA41.zauber.merkmale.daemonisch_asfaloth_calijnaar",
"daemonisch_belhalhar_xarfai": "DSA41.zauber.merkmale.daemonisch_belhalhar_xarfai",
"daemonisch_blakharaz_tyakraman": "DSA41.zauber.merkmale.daemonisch_blakharaz_tyakraman",
"daemonisch_lolgramoth_thezzphai": "DSA41.zauber.merkmale.daemonisch_lolgramoth_thezzphai",
"daemonisch_belzhorash_mishkara": "DSA41.zauber.merkmale.daemonisch_belzhorash_mishkara",
"daemonisch_thargunitoth_tijakool": "DSA41.zauber.merkmale.daemonisch_thargunitoth_tijakool",
"eigenschaften": "DSA41.zauber.merkmale.eigenschaften",
"einfluss": "DSA41.zauber.merkmale.einfluss",
"elementar_allgemein": "DSA41.zauber.merkmale.elementar_allgemein",
"elementar_eis": "DSA41.zauber.merkmale.elementar_eis",
"elementar_erz": "DSA41.zauber.merkmale.elementar_erz",
"elementar_feuer": "DSA41.zauber.merkmale.elementar_feuer",
"elementar_humus": "DSA41.zauber.merkmale.elementar_humus",
"elementar_luft": "DSA41.zauber.merkmale.elementar_luft",
"elementar_wasser": "DSA41.zauber.merkmale.elementar_wasser",
"form": "DSA41.zauber.merkmale.form",
"geisterwesen": "DSA41.zauber.merkmale.geisterwesen",
"heilung": "DSA41.zauber.merkmale.heilung",
"hellsicht": "DSA41.zauber.merkmale.hellsicht",
"herbeirufung": "DSA41.zauber.merkmale.herbeirufung",
"herrschaft": "DSA41.zauber.merkmale.herrschaft",
"illusion": "DSA41.zauber.merkmale.illusion",
"kraft": "DSA41.zauber.merkmale.kraft",
"limbus": "DSA41.zauber.merkmale.limbus",
"metamagie": "DSA41.zauber.merkmale.metamagie",
"objekt": "DSA41.zauber.merkmale.objekt",
"schaden": "DSA41.zauber.merkmale.schaden",
"telekinese": "DSA41.zauber.merkmale.telekinese",
"temporal": "DSA41.zauber.merkmale.temporal",
"umwelt": "DSA41.zauber.merkmale.umwelt",
"verständigung": "DSA41.zauber.merkmale.verständigung",
},
})),
modifikationen: new SetField(new StringField({
choices: {
"zauberdauer": "DSA41.zauber.modifikationen.zauberdauer",
"erzwingen": "DSA41.zauber.modifikationen.erzwingen",
"kosten": "DSA41.zauber.modifikationen.kosten",
"mehrere_ziele": "DSA41.zauber.modifikationen.mehrere_ziele",
"mehrere_ziele_freiwillig": "DSA41.zauber.modifikationen.mehrere_ziele_freiwillig",
"reichweite": "DSA41.zauber.modifikationen.reichweite",
"reichweite_beruehrung": "DSA41.zauber.modifikationen.reichweite_beruehrung",
"reichweite_selbst": "DSA41.zauber.modifikationen.reichweite_selbst",
"wirkungsdauer": "DSA41.zauber.modifikationen.wirkungsdauer",
"ziel_objekt": "DSA41.zauber.modifikationen.ziel_objekt",
},
})),
magieresistenz: new BooleanField({ initial: false }),
haus: new BooleanField({ initial: false }),
zauberfertigkeitswert: new NumberField({ integer: true, initial: 0 }),
beschreibung: new StringField({ initial: "" }),
};
}
}
function DSA41_ApplicationMixin(BaseApplication) {
class DSA41_Application extends HandlebarsApplicationMixin(BaseApplication) {
static DEFAULT_OPTIONS= {
classes: [ "DSA41" ],
window: { resizable: true },
form: { submitOnChange: true },
};
async _prepareContext(options) {
return this;
}
}
return DSA41_Application;
}
class DSA41_Dialog extends DSA41_ApplicationMixin(ApplicationV2) {
static PARTS = {
Eigenschaft: { template: "systems/dsa-4th-edition/src/Dialogs/Attribute.hbs" },
Talent: { template: "systems/dsa-4th-edition/src/Dialogs/Talent.hbs" },
Zauber: { template: "systems/dsa-4th-edition/src/Dialogs/Zauber.hbs" },
Attacke: { template: "systems/dsa-4th-edition/src/Dialogs/Attacke.hbs" },
Parade: { template: "systems/dsa-4th-edition/src/Dialogs/Parade.hbs" },
Trefferpunkte: { template: "systems/dsa-4th-edition/src/Dialogs/Trefferpunkte.hbs" },
FernkampfAttacke: { template: "systems/dsa-4th-edition/src/Dialogs/FernkampfAttacke.hbs" },
FernkampfTrefferpunkte: { template: "systems/dsa-4th-edition/src/Dialogs/FernkampfTrefferpunkte.hbs" },
Trefferzone: { template: "systems/dsa-4th-edition/src/Dialogs/Trefferzone.hbs" },
footer: { template: "templates/generic/form-footer.hbs" },
};
static DEFAULT_OPTIONS = {
classes: [ "DSA41", "Dialog" ],
window: {
title: "Dialog",
minimizable: false,
resizable: false
},
tag: "form",
form: {
closeOnSubmit: false,
submitOnChange: true,
}
};
get formData() {
const inputs = this.element.querySelector("[data-application-part]")?.querySelectorAll("input") ?? [];
const types = Object.fromEntries(Array.from(inputs, x => [x.name, x.type]));
const data = Object.fromEntries(
new FormData(this.element).entries().map(([key, value])=>{
if (types[key] == "number") return [key, Number(value)];
return [key, value];
})
);
return data;
}
buttons = [{
type: "submit",
label: "Confirm"
}];
static async wait(dialog_type, options) {
return new Promise((resolve, reject) => {
const dialog = new this({
dialog_type: dialog_type,
form: {
handler: (event, form, formData) => {
if (event.type != "submit") {
dialog.render({ force: false });
return;
}
dialog.close();
resolve(dialog.formData);
}
},
...options,
});
dialog.render({ force: true });
});
}
_configureRenderOptions(options) {
super._configureRenderOptions(options);
options.parts = [ this.options.dialog_type, "footer" ];
}
_onFirstRender(context, options) {
this.render(options)
}
}
class DSA41_ActorSheet extends DSA41_ApplicationMixin(ActorSheetV2) {
static PARTS = {
ActorSheet: { template: "systems/dsa-4th-edition/src/ActorSheet.hbs", scrollable: [".scroll-container"] },
};
static DEFAULT_OPTIONS = {
classes: [ "DSA41", "ActorSheet" ],
position: { width: "800", height: "650" },
actions: {
"roll": async function(event, target) {
var roll_formula = event.target.closest("[data-roll]")?.dataset.roll;
const roll_type = event.target.closest("[data-roll-type]")?.dataset.rollType;
const success_value = event.target.closest("[data-success-value]")?.dataset.successValue;
const item_id = event.target.closest("[data-item-id]")?.dataset.itemId;
const item = this.document.items.get(item_id);
const hp_roll_modifier = this.document.get_hp_roll_modifier();
let flavor = game.i18n.localize("DSA41.roll_types." + roll_type);
if (typeof success_value !== 'undefined') {
flavor += " <= " + success_value;
}
if (roll_type == "courage" || roll_type == "cleverness" || roll_type == "intuition" || roll_type == "charisma" ||
roll_type == "dexterity" || roll_type == "agility" || roll_type == "constitution" || roll_type == "strength") {
const title = game.i18n.localize("DSA41.roll_types." + roll_type) + ": " + this.document.name;
const data = await DSA41_Dialog.wait("Eigenschaft", { window: { title: title }, attribute: this.document.system.attributes[roll_type] });
let flavor = game.i18n.localize("DSA41.roll_types." + roll_type);
if (typeof success_value !== 'undefined') {
flavor += " <= " + (Number(success_value) + data.modifikator);
}
if (hp_roll_modifier !== 0)
roll_formula = roll_formula + " + " + hp_roll_modifier;
let roll = new Roll(roll_formula, this.document.system);
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.document }),
flavor: flavor,
flags: {
dsa41: {
type: roll_type,
targets: get_targeted_actors(),
}
},
});
return;
}
if (roll_type == "talent") {
const title = game.i18n.localize("DSA41.roll_types." + roll_type) + ": " + item.name;
const data = await DSA41_Dialog.wait("Talent", { window: {title: title}, item: item });
const eBE = (await new Roll(item.system.behinderung || "0", { BE: this.document.system.computed.kampf.ruestungen_gesamt.gesamt_behinderung }).evaluate()).total;
data.modifikator -= eBE;
const talentwert = item.system.talentwert + data.modifikator;
const roll_modifier = talentwert < 0 ? -talentwert: 0;
if (hp_roll_modifier !== 0)
roll_modifier += hp_roll_modifier;
const roll1 = (await new Roll("1d20").evaluate()).total + roll_modifier;
const roll2 = (await new Roll("1d20").evaluate()).total + roll_modifier;
const roll3 = (await new Roll("1d20").evaluate()).total + roll_modifier;
const attribute1 = this.document.system.computed.attributes[item.system.attribute1];
const attribute2 = this.document.system.computed.attributes[item.system.attribute2];
const attribute3 = this.document.system.computed.attributes[item.system.attribute3];
const needed_taw_roll1 = Math.max(roll1 - attribute1, 0);
const needed_taw_roll2 = Math.max(roll2 - attribute2, 0);
const needed_taw_roll3 = Math.max(roll3 - attribute3, 0);
const leftover_taw = Math.max(talentwert, 0) - needed_taw_roll1 - needed_taw_roll2 - needed_taw_roll3;
const context = {
talent: item,
modifikator: data.modifikator,
attribute1: { type: item.system.attribute1, value: attribute1 },
attribute2: { type: item.system.attribute2, value: attribute2 },
attribute3: { type: item.system.attribute3, value: attribute3 },
roll1: roll1,
roll2: roll2,
roll3: roll3,
needed_taw_roll1: -needed_taw_roll1,
needed_taw_roll2: -needed_taw_roll2,
needed_taw_roll3: -needed_taw_roll3,
leftover_taw: Math.min(leftover_taw, item.system.talentwert),
};
const message = await ChatMessage.create(
{
content: await renderTemplate("talent_chat", context),
speaker: { actor: this.actor },
sound: CONFIG.sounds.dice,
},
);
return;
}
if (roll_type == "attacke") {
const item = this.document.system.computed.kampf.waffen[item_id];
const title = game.i18n.localize("DSA41.roll_types." + roll_type) + ": " + item.name;
const data = await DSA41_Dialog.wait("Attacke", { window: { title: title }, item: item });
let flavor = game.i18n.localize("DSA41.roll_types." + roll_type);
if (typeof success_value !== 'undefined') {
flavor += " <= " + (Number(success_value) + data.modifikator);
}
if (data.anmerkung) {
flavor += "<br>" + data.anmerkung;
}
if (hp_roll_modifier !== 0)
roll_formula = roll_formula + " + " + hp_roll_modifier;
let roll = new Roll(roll_formula, this.document.system);
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.document }),
flavor: flavor,
flags: {
dsa41: {
type: roll_type,
targets: get_targeted_actors(),
}
},
});
return;
}
if (roll_type == "parade") {
const item = this.document.system.computed.kampf.waffen[item_id];
const title = game.i18n.localize("DSA41.roll_types." + roll_type) + ": " + item.name;
const data = await DSA41_Dialog.wait("Parade", { window: { title: title }, item: item });
let flavor = game.i18n.localize("DSA41.roll_types." + roll_type);
flavor += " <= " + (Number((data.crit == "on") ? item.parade_crit : item.parade) + data.modifikator);
if (data.anmerkung) {
flavor += "<br>" + data.anmerkung;
}
if (hp_roll_modifier !== 0)
roll_formula = roll_formula + " + " + hp_roll_modifier;
let roll = new Roll(roll_formula, this.document.system);
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.document }),
flavor: flavor,
flags: {
dsa41: {
type: roll_type,
targets: get_targeted_actors(),
}
},
});
return;
}
if (roll_type == "trefferpunkte") {
const item = this.document.system.computed.kampf.waffen[item_id];
const title = game.i18n.localize("DSA41.roll_types." + roll_type) + ": " + item.name;
const data = await DSA41_Dialog.wait("Trefferpunkte", { window: { title: title }, item: item });
if (data.crit == "on") {
roll_formula = "2 * (" + item.item.system.nahkampfwaffe.basis + ")" + " + " + item.tp_kk;
}
let roll = new Roll(roll_formula + " + " + data.modifikator, this.document.system);
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.document }),
flavor: flavor,
flags: {
dsa41: {
type: roll_type,
targets: get_targeted_actors(),
}
},
});
return;
}
if (roll_type == "fernkampf-attacke") {
const title = game.i18n.localize("DSA41.roll_types." + roll_type) + ": " + item.name;
const data = await DSA41_Dialog.wait("FernkampfAttacke", { window: { title: title }, item: this.document.system.computed.kampf.fernkampf_waffen[item_id] });
const groessen_modifikator = Math.max(data.ziel_groesse + data.deckung + data.ziel_bewegung, -2);
const andere_modifikator = data.entfernung + data.wind + data.modifikator;
const total_modifikator = groessen_modifikator + andere_modifikator + hp_roll_modifier;
let roll = new Roll(roll_formula + " + " + total_modifikator, this.document.system);
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.document }),
flavor: game.i18n.localize("DSA41.roll_types." + roll_type),
flags: {
dsa41: {
type: roll_type,
targets: get_targeted_actors(),
}
},
});
return;
}
if (roll_type == "fernkampf-trefferpunkte") {
const title = game.i18n.localize("DSA41.roll_types." + roll_type) + ": " + item.name;
const data = await DSA41_Dialog.wait("FernkampfTrefferpunkte", { window: { title: title }, item: this.document.system.computed.kampf.fernkampf_waffen[item_id] });
const entfernung = item.system.fernkampfwaffe[data.entfernung];
if (data.crit == "on") {
roll_formula = "2 * (" + roll_formula + ")";
}
let roll = new Roll(roll_formula + " + " + (entfernung + data.modifikator), this.document.system);
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.document }),
flavor: game.i18n.localize("DSA41.roll_types." + roll_type),
flags: {
dsa41: {
type: roll_type,
targets: get_targeted_actors(),
}
},
});
return;
}
if (roll_type == "zauber") {
const title = game.i18n.localize("DSA41.roll_types." + roll_type) + ": " + item.name;
const data = await DSA41_Dialog.wait("Zauber", { window: {title: title}, item: item });
const zauberfertigkeitswert = item.system.zauberfertigkeitswert - data.modifikator - (data.magieresistenz ?? null);
const roll_modifier = zauberfertigkeitswert < 0 ? -zauberfertigkeitswert: 0;
if (hp_roll_modifier !== 0)
roll_modifier += hp_roll_modifier;
const roll1 = (await new Roll("1d20").evaluate()).total + roll_modifier;
const roll2 = (await new Roll("1d20").evaluate()).total + roll_modifier;
const roll3 = (await new Roll("1d20").evaluate()).total + roll_modifier;
const attribute1 = this.document.system.computed.attributes[item.system.attribute1];
const attribute2 = this.document.system.computed.attributes[item.system.attribute2];
const attribute3 = this.document.system.computed.attributes[item.system.attribute3];
const needed_zfw_roll1 = Math.max(roll1 - attribute1, 0);
const needed_zfw_roll2 = Math.max(roll2 - attribute2, 0);
const needed_zfw_roll3 = Math.max(roll3 - attribute3, 0);
const leftover_zfw = Math.max(zauberfertigkeitswert, 0) - needed_zfw_roll1 - needed_zfw_roll2 - needed_zfw_roll3;
const context = {
zauber: item,
modifikator: -data.modifikator,
magieresistenz: -(data.magieresistenz ?? 0),
attribute1: { type: item.system.attribute1, value: attribute1 },
attribute2: { type: item.system.attribute2, value: attribute2 },
attribute3: { type: item.system.attribute3, value: attribute3 },
roll1: roll1,
roll2: roll2,
roll3: roll3,
needed_zfw_roll1: -needed_zfw_roll1,
needed_zfw_roll2: -needed_zfw_roll2,
needed_zfw_roll3: -needed_zfw_roll3,
leftover_zfw: Math.min(leftover_zfw, item.system.zauberfertigkeitswert),
};
const message = await ChatMessage.create(
{
content: await renderTemplate("zauber_chat", context),
speaker: { actor: this.actor },
sound: CONFIG.sounds.dice,
},
);
return;
}
let roll = new Roll(roll_formula, this.document.system);
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.document }),
flavor: flavor,
flags: {
dsa41: {
type: roll_type,
targets: get_targeted_actors(),
}
},
});
},
"item-open": async function(event, target) {
const item_id = event.target.closest("[data-item-id]").dataset.itemId;
const item = this.document.items.get(item_id);
item.sheet.render(true)
},
"item-delete": async function(event, target) {
const item_id = event.target.closest("[data-item-id]").dataset.itemId;
const item = this.document.items.get(item_id);
item.delete();
},
"toggle_equipped": async function(event, target) {
const item_id = event.target.closest("[data-item-id]").dataset.itemId;
const item = this.document.items.get(item_id);
item.update({ "system.angelegt": !item.system.angelegt });
},
"post-zauber": async function(event, target) {
const item_id = event.target.closest("[data-item-id]").dataset.itemId;
const item = this.document.items.get(item_id);
ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor: this.document }),
author: game.user.id,
sound: CONFIG.sounds.dice,
content: item.system.beschreibung,
});
},
"toggle-wound": async function(event, target) {
const location = event.target.closest("[data-location]").dataset.location;
const current_count = this.document.system.wunden[location];
const toggled_count = Array.prototype.indexOf.call(event.target.parentNode.children, event.target) + 1;
this.document.update({ ["system.wunden." + location]: toggled_count > current_count ? toggled_count : toggled_count - 1 });
}
},
};
// allow changing embedded item fields
async _onChangeForm(formConfig, event) {
const item_id = event.target.closest("[data-item-id]")?.dataset.itemId;
const data_name = event.target.dataset.name;
if (!item_id || !data_name) return super._onChangeForm(formConfig, event);
event.stopImmediatePropagation();
const item = await this.actor.items.get(item_id);
const value = event.target.value;
item.update({ [data_name]: value });
}
tabGroups = { primary: "eigenschaften" };
}
class DSA41_ItemSheetV2 extends DSA41_ApplicationMixin(ItemSheetV2) {
static PARTS = {
Bewaffnung: { template: "systems/dsa-4th-edition/src/ItemSheets/Bewaffnung.hbs" },
Gegenstand: { template: "systems/dsa-4th-edition/src/ItemSheets/Gegenstand.hbs" },
Ruestung: { template: "systems/dsa-4th-edition/src/ItemSheets/Ruestung.hbs" },
Talent: { template: "systems/dsa-4th-edition/src/ItemSheets/Talent.hbs" },
Kampftalent: { template: "systems/dsa-4th-edition/src/ItemSheets/Kampftalent.hbs" },
Sonderfertigkeit: { template: "systems/dsa-4th-edition/src/ItemSheets/Sonderfertigkeit.hbs" },
VorNachteil: { template: "systems/dsa-4th-edition/src/ItemSheets/VorNachteil.hbs" },
Zauber: { template: "systems/dsa-4th-edition/src/ItemSheets/Zauber.hbs" },
};
static DEFAULT_OPTIONS = {
position: { width: "600", height: "auto" },
};
tabGroups = { primary: "tab1" };
_configureRenderOptions(options) {
super._configureRenderOptions(options);
options.parts = [ this.options.document.type ];
}
}