import BaseItem from "./baseItem.mjs"; const { DocumentIdField, ArrayField, NumberField, SchemaField, StringField, HTMLField } = foundry.data.fields; export class SkillDataModel extends BaseItem { static defineSchema() { return { name: new StringField({required: true}), gruppe: new StringField({required: true}), taw: new NumberField({integer: true, initial: 0}), at: new NumberField({required: false, integer: true, initial: 0}), pa: new NumberField({required: false, integer: true, nullable: true, initial: 0}), probe: new ArrayField(new StringField(), {exact: 3}), // References one of the eight attributes by name voraussetzung: new SchemaField({ talent: new DocumentIdField(), wert: new NumberField(), }, {required: false}), // Required skills at a given level talent: new HTMLField({required: true}), behinderung: new StringField({required: false}), // BE-X komplexität: new NumberField({required: false}), // In case of languages } } /** * Augment the basic Item data model with additional dynamic data. */ prepareData() { } /** * Prepare a data object which is passed to any Roll formulas which are created related to this Item * @private */ getRollData() { } /** * Determines the values to consult for the given type of roll (normal: talent roll, attack: AT, parry: PA * @returns {{NORMAL: string, ATTACK: string, PARRY: string}} * @constructor */ get SKILL_MODE() { return { NORMAL: "NORMAL", ATTACK: "ATTACK", PARRY: "PARRY", } } /** * * @param rollMode {["publicroll","gmroll"] } * @param mode * @returns {Promise} */ async roll(rollMode = null, mode = this.SKILL_MODE.NORMAL) { rollMode = rollMode ?? game.settings.get('core', 'rollMode'); switch (mode) { case this.SKILL_MODE.NORMAL: return this.#talentRoll(rollMode) case this.SKILL_MODE.ATTACK: case this.SKILL_MODE.PARRY: return this.#combatRoll(rollMode, mode) } } async #combatRoll(rollMode, mode) { const owner = this.parent.parent const rollData = owner.getRollData() let targetNumber = 0 if (mode === this.SKILL_MODE.ATTACK) { targetNumber = this.at + owner.system.at.basis } else { targetNumber = this.pa + owner.system.pa.basis } let roll1 = new Roll(`1d20cs<${targetNumber}`, owner.getRollData()); let evaluated1 = (await roll1.evaluate()) const rolledValue = evaluated1.terms[0].results[0].result if (rolledValue === 1 || rolledValue === 20) { // TODO: Modify this target // fill with actual evaluation (targetNumber should be reduced by X and roll against that again) } let message = "" if (mode === this.SKILL_MODE.ATTACK) { if (rolledValue <= targetNumber) { message = `Würde treffen [${rolledValue}]` } else { message = `Verfehlt [${rolledValue}]` } } else { if (rolledValue <= targetNumber) { message = `Würde parrieren [${rolledValue}]` } else { message = `Verfehlt die parade [${rolledValue}]` } } evaluated1.toMessage({ speaker: ChatMessage.getSpeaker({actor: owner}), flavor: message, rollMode, }) } async #talentRoll(rollMode) { const owner = this.parent.parent let roll1 = new Roll("3d20", owner.getRollData()); let evaluated1 = (await roll1.evaluate()) const dsaDieRollEvaluated = this._evaluateRoll(evaluated1.terms[0].results, { taw: this.taw, werte: [this.probe[0], this.probe[1], this.probe[2]], }) if (dsaDieRollEvaluated.tap >= 0) { // erfolg evaluated1.toMessage({ speaker: ChatMessage.getSpeaker({actor: owner}), flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Meisterlich geschafft' : 'Geschafft'} mit ${dsaDieRollEvaluated.tap} Punkten übrig`, rollMode, }) } else { // misserfolg evaluated1.toMessage({ speaker: ChatMessage.getSpeaker({actor: owner}), flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Gepatzt' : ''} mit ${Math.abs(dsaDieRollEvaluated.tap)} Punkten daneben`, 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, } } }