foundry-dsa41-game/src/module/sheets/characterSheet.mjs

439 lines
15 KiB
JavaScript

export class CharacterSheet extends ActorSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'actor', 'character'],
width: 1100,
height: 480,
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'description',
},
],
});
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/actor/actor-character-sheet.hbs`;
}
/** @override */
getData() {
const context = super.getData();
// Use a safe clone of the actor data for further operations.
const actorData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = actorData.system;
context.flags = actorData.flags;
this.#addSkillsToContext(context)
this.#addAdvantagesToContext(context)
this.#addAttributesToContext(context)
this.#addEquipmentsToContext(context)
return context;
}
#addSkillsToContext(context) {
const actorData = context.data;
context.skills = {};
context.flatSkills = [];
Object.values(actorData.items).forEach( (item, index) => {
if (item.type === "Skill") {
const talentGruppe = item.system.gruppe;
const eigenschaften = Object.values(item.system.probe);
const werte = [
{name: eigenschaften[0], value: this.prepareEigenschaftRoll(actorData, eigenschaften[0])},
{name: eigenschaften[1], value: this.prepareEigenschaftRoll(actorData, eigenschaften[1])},
{name: eigenschaften[2], value: this.prepareEigenschaftRoll(actorData, eigenschaften[2])}
]
if (context.skills[talentGruppe] == null) {
context.skills[talentGruppe] = [];
}
const obj = {
type: "talent",
gruppe: talentGruppe,
name: item.name,
taw: "" + item.system.taw,
tawPath: `system.items.${index}.taw`,
werte,
rollEigenschaft1: werte[0].value,
rollEigenschaft2: werte[1].value,
rollEigenschaft3: werte[2].value,
eigenschaft1: werte[0].name,
eigenschaft2: werte[1].name,
eigenschaft3: werte[2].name,
probe: `(${eigenschaften.join("/")})`,
id: item._id,
};
context.skills[talentGruppe].push(obj);
context.flatSkills.push(obj);
}
}
);
}
#addAdvantagesToContext(context) {
context.advantages = [];
const actorData = context.data;
Object.values(actorData.items).forEach( (item) => {
if (item.type === "Advantage") {
context.advantages.push({
id: item._id,
name: item.name,
value: item.system.value,
options: item.system.auswahl,
description: item.system.description,
});
}
}
);
}
#addAttributesToContext(context) {
const actorData = context.data;
context.attributes = [
{
eigenschaft: "mu",
name: "MU",
tooltip: "Mut",
wert: actorData.system.attribute.mu.aktuell ?? 0,
},
{
eigenschaft: "kl",
name: "KL",
tooltip: "Klugheit",
wert: actorData.system.attribute.kl.aktuell ?? 0,
},
{
eigenschaft: "in",
name: "IN",
tooltip: "Intuition",
wert: actorData.system.attribute.in.aktuell ?? 0,
},
{
eigenschaft: "ch",
name: "CH",
tooltip: "Charisma",
wert: actorData.system.attribute.ch.aktuell ?? 0,
},
{
eigenschaft: "ff",
name: "FF",
tooltip: "Fingerfertigkeit",
wert: actorData.system.attribute.ff.aktuell ?? 0,
},
{
eigenschaft: "ge",
name: "GE",
tooltip: "Geschicklichkeit",
wert: actorData.system.attribute.ge.aktuell ?? 0,
},
{
eigenschaft: "ko",
name: "KO",
tooltip: "Konstitution",
wert: actorData.system.attribute.ko.aktuell ?? 0,
},
{
eigenschaft: "kk",
name: "KK",
tooltip: "Körperkraft",
wert: actorData.system.attribute.kk.aktuell ?? 0,
},
];
}
#addEquipmentsToContext(context) {
context.equipments = [];
const actorData = context.data;
context.carryingweight = 0;
Object.values(actorData.items).forEach( (item, index) => {
if (item.type === "Equipment") {
context.equipments.push({
index: index,
id: item._id,
quantity: item.system.quantity,
name: item.name,
})
context.carryingweight += item.system.quantity * item.system.weight;
}
})
context.maxcarryingcapacity = actorData.system.attribute.kk.aktuell
context.carryingpercentage = (context.carryingweight / context.maxcarryingcapacity)*100;
}
prepareEigenschaftRoll(actorData, name) {
return actorData.system.attribute[name.toLowerCase()].aktuell
}
async _onTalentRoll(event) {
event.preventDefault();
const dataset = event.currentTarget.dataset;
console.log(dataset)
if (dataset.rolleigenschaft1) {
let roll1 = new Roll("3d20", this.actor.getRollData());
let evaluated1 = (await roll1.evaluate())
const dsaDieRollEvaluated = this._evaluateRoll(evaluated1.terms[0].results, {
taw: dataset.taw,
werte: [dataset.rolleigenschaft1, dataset.rolleigenschaft2, dataset.rolleigenschaft3],
})
if (dsaDieRollEvaluated.tap >= 0) { // erfolg
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.actor}),
flavor: ` ${dsaDieRollEvaluated.meisterlich?'Meisterlich geschafft':'Geschafft'} mit ${dsaDieRollEvaluated.tap} Punkten übrig`,
rollMode: game.settings.get('core', 'rollMode'),
})
} else { // misserfolg
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.actor}),
flavor: ` ${dsaDieRollEvaluated.meisterlich?'Gepatzt':''} mit ${Math.abs(dsaDieRollEvaluated.tap)} Punkten daneben`,
rollMode: game.settings.get('core', 'rollMode'),
})
}
}
}
_evaluateRoll(rolledDice, { taw, lowerThreshold = 1, upperThreshold = 20, countToMeisterlich = 3, countToPatzer = 3, werte = [] } ) {
let tap = taw;
let meisterlichCounter = 0;
let patzerCounter = 0;
let failCounter = 0;
rolledDice.forEach( (rolledDie, index) => {
if (tap < 0 && rolledDie.result > werte[index]) {
tap -= rolledDie.result - werte[index];
if (tap <0) { // konnte nicht vollständig ausgeglichen werden
failCounter++;
}
} else if (rolledDie.result > werte[index]) { // taw ist bereits aufgebraucht und wert kann nicht ausgeglichen werden
tap -= rolledDie.result - werte[index];
failCounter++;
}
if (rolledDie.result <= lowerThreshold) meisterlichCounter++;
if (rolledDie.result > upperThreshold) patzerCounter++;
})
return {
tap,
meisterlich: meisterlichCounter === countToMeisterlich,
patzer: patzerCounter === countToPatzer,
}
}
_onAttributeRoll(event) {
event.preventDefault();
const dataset = event.currentTarget.dataset;
if (dataset.roll) {
let label = dataset.label ? `[Attribut] ${dataset.label}` : '';
let roll = new Roll(dataset.roll, this.actor.getRollData());
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
});
return roll;
}
}
_onRoll(event) {
event.preventDefault();
const dataset = event.currentTarget.dataset;
if (dataset.roll) {
let label = dataset.label ? `${dataset.label}` : '';
let roll = new Roll(dataset.roll, this.actor.getRollData());
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
});
return roll;
}
}
openEmbeddedDocument(documentId) {
this.object.items.get(documentId).sheet.render(true)
}
showAdjustAttributeDialog(attributeName, attributeField, previousValue) {
const thisActor = this;
const myContent = `
Value:
<input id="attributeValue" type="number" value="${previousValue}" />
`;
function updateAttribute(html) {
const value = html.find("input#attributeValue").val();
const attribute = {}
attribute[attributeField.toLowerCase()] = {
aktuell: value
}
thisActor.object.update({ system: { attribute }})
}
new Dialog({
title: `${attributeName} ändern auf`,
content: myContent,
buttons: {
button1: {
label: "Ändern",
callback: (html) => {
updateAttribute(html)
},
icon: `<i class="fas fa-check"></i>`
}
}
}).render(true);
}
activateListeners(html) {
super.activateListeners(html);
html.on('click', '.attribute.rollable', (evt) => {
this._onAttributeRoll(evt);
});
html.on('click', '.talent.rollable', (evt) => {
this._onTalentRoll(evt);
});
html.on('click', '.sidebar-element.rollable', (evt) => {
this._onRoll(evt);
});
html.on('click', '.talent .name', (evt) => {
this.openEmbeddedDocument(evt.target.dataset.id);
evt.stopPropagation();
})
html.on('click', '.advantage .name', (evt) => {
this.openEmbeddedDocument(evt.target.dataset.id);
evt.stopPropagation();
})
html.on('click', '.equipment', (evt) => {
this.openEmbeddedDocument(evt.target.parentElement.dataset.id);
evt.stopPropagation();
})
new ContextMenu(html, '.talent.rollable', [
{
name: "Entfernen",
icon: '<i class="fa-solid fa-trash"></i>',
callback: (event) => {
this.object.deleteEmbeddedDocuments('Item', [event[0].dataset.id])
},
condition: () => true
}
]);
new ContextMenu(html, '.attribute.rollable', [
{
name: "Anpassen",
icon: '<i class="fa-solid fa-pen"></i>',
callback: (event) => {
this.showAdjustAttributeDialog(event[0].dataset.name, event[0].dataset.label, event[0].dataset.value)
},
condition: () => true
}
]);
let handler = ev => this._onDragStart(ev);
// Find all items on the character sheet.
html.find('.talent.rollable').each((i, li) => {
// Add draggable attribute and dragstart listener.
li.setAttribute("draggable", true);
li.addEventListener("dragstart", handler, false);
});
new ContextMenu(html, '.equipment', [
{
name: "Aus dem Inventar entfernen",
icon: '<i class="fa-solid fa-trash"></i>',
callback: (event) => {
this.object.deleteEmbeddedDocuments('Item', [event[0].dataset.id])
},
condition: () => true
}
]);
}
#handleDroppedSkill(actor, skill) {
const array = Array.from(actor.items);
for ( let i = 0; i < array.length; i++ ) {
if (array[i].name === skill.name) {
return false;
}
}
}
#handleDroppedAdvantage(actor, advantage) {
const array = Array.from(actor.items);
for ( let i = 0; i < array.length; i++ ) {
if (array[i].name === advantage.name) { // TODO: adjust for uniqueness
return false;
}
}
}
#handleDroppedEquipment(actor, equipment) {
const array = Array.from(actor.items);
for ( let i = 0; i < array.length; i++ ) {
if (array[i].name === equipment.name) { // TODO: adjust item quantity if item is the same
console.log(equipment);
return false;
}
}
}
static getElementByName(collection, id) {
const array = Array.from(collection);
for (const element of array) {
if (element._id === id) {
return element;
}
}
}
static onDroppedData(actor, characterSheet, data) {
const uuid = foundry.utils.parseUuid(data.uuid);
const collection = uuid.collection.index ?? uuid.collection;
const document = CharacterSheet.getElementByName(collection, uuid.id);
const {
name,
type
} = document
console.log(name, type)
switch (type) {
case "Skill":
return characterSheet.#handleDroppedSkill(actor, document); // on false cancel this whole operation
case "Advantage":
return characterSheet.#handleDroppedAdvantage(actor, document);
case "Equipment":
return characterSheet.#handleDroppedEquipment(actor, document);
default:
return false;
}
}
}