import { Evaluated } from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/client/dice/roll"; import { M5Character } from "../actors/M5Character"; import { M5EwResult, M5ModOperation, M5ModType, M5RollData, M5RollResult, M5SkillUnlearned, M5Stats } from "../M5Base"; import { stat } from "fs"; export class M5Roll { // extends Roll static readonly TEMPLATE_PATH = "systems/midgard5/templates/chat/roll-m5.hbs"; public _evaluated: boolean = false; public _total: number = 0; public pool: PoolTerm = null; constructor(public data: M5RollData, public actor: any, public label: string, public id?: string) { //super(null) //this.data = rollData } // @ts-ignore //override evaluate(options?: InexactPartial): Evaluated> | Promise>> { evaluate() { const indexMap = new Map(); const rollNames = Object.keys(this.data.rolls); const rolls = rollNames .filter((rollName) => this.data.rolls[rollName].enabled) .map((rollName, index) => { indexMap.set(index, rollName); const formula = this.data.rolls[rollName]; const roll = new Roll(formula.formula, this.data); return roll; }); this.pool = PoolTerm.fromRolls(rolls); console.log("evaluate", this._evaluated, this.pool); return this.pool.evaluate({ async: true }).then((results) => { this._total = 0; results.rolls.forEach((roll, index) => { const rollIndex = indexMap.get(index); const rollResult = this.data.rolls[rollIndex] as M5RollResult; rollResult.result = roll.result; rollResult.total = roll.total; rollResult.totalStr = roll.total.toString(); this._total += roll.total; let rowRes = M5EwResult.TBD; let face100 = -1; roll.dice.forEach((d, dIndex) => { rollResult.dice[dIndex.toString()] = d.total; if (rowRes === M5EwResult.TBD && dIndex === 0) { if (d.faces === 20) { //if (rollResult.type === "ew") { if (d.total === 1) rowRes = M5EwResult.FUMBLE; else if (d.total === 20) rowRes = M5EwResult.CRITICAL; else if (d.total >= 16) rowRes = M5EwResult.HIGH; } else if (d.faces === 100) { face100 = d.total as number; } } }); const parseResult = M5Roll.parseDiceSides(rollResult.formula); //console.log("evaluate roll", parseResult) if (parseResult?.sides === 20) { if (roll.total < 20) { if (rowRes === M5EwResult.TBD || rowRes === M5EwResult.HIGH) rowRes = M5EwResult.FAIL; } else { if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.PASS; } } else if (face100 >= 0) { const threshold100 = roll.total + face100; const threshold = Math.floor(threshold100 / 10); if (face100 === 100) { if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.FUMBLE; } else if (roll.total < 0) { if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.FAIL; } else if (face100 <= threshold) { if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.CRITICAL; } else { if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.PASS; } } rollResult.css = rowRes; }); this.data.res.label = this.label; if ((game as Game).settings.get("midgard5", "automatedPP")) { if ((this.data.i.type === "language" || this.data.i.type === "general") && this.data.rolls[0].dice[0] >= 16) { this.actor.items.get(this.id).update({ system: { pp: this.data.i.pp + 1, }, }); } else if (this.data.rolls[0].dice[0] === 20) { if (this.data.i.type === "combat") { // Rolling through skill this.actor.items.get(this.id).update({ system: { pp: this.data.i.pp + 1, }, }); } else if (this.data.iType === "weapon") { // Rolling through Weapon Item const skill = this.actor.items.get(this.data.i.skillId); skill.update({ system: { pp: skill.system.pp + 1, }, }); } else if (this.data.iType === "defensiveWeapon") { // Rolling through defensiveWeapon Item const skill = this.actor.items.get(this.data.i.skillId); skill.update({ system: { pp: skill.system.pp + 1, }, }); } else if (this.data.iType === "spell") { // Rolling through Spell Item const klasse = this.actor.items.find((x) => x.type === "class" && x.system.equipped); klasse.update({ system: { lernKostenZauber: { [this.data.i.process]: { pp: klasse.system.lernKostenZauber[this.data.i.process].pp + 1 }, }, }, }); } } } this._evaluated = true; return this; }); } async render(): Promise { return renderTemplate(M5Roll.TEMPLATE_PATH, this.data); } async toMessage() { if (!this._evaluated) await this.evaluate(); const rMode = (game as Game).settings.get("core", "rollMode"); const chatData = { type: CONST.CHAT_MESSAGE_TYPES.ROLL, content: await this.render(), speaker: ChatMessage.getSpeaker({ actor: this.actor }), sound: CONFIG.sounds.dice, roll: Roll.fromTerms([this.pool]), }; ChatMessage.applyRollMode(chatData, rMode); return ChatMessage.create(chatData); } static fromAttributeValue(actor: any, attributeKey: string, attributeValue: number) { const rollData = actor.getRollData() as M5RollData; const itemData = actor.items.filter((x) => x.type === "effect").map((y) => y.system.mods); rollData.c = 0; for (let effectKey in itemData) { for (let modkey in itemData[effectKey]) if (itemData[effectKey][modkey].type === M5ModType.ATTRIBUTE && itemData[effectKey][modkey].operation === M5ModOperation.ROLL) { rollData.c += itemData[effectKey][modkey].value; } } rollData.i = attributeValue; rollData.rolls["0"] = { formula: "@i - 1d100 - @c", enabled: true, label: (game as Game).i18n.localize("midgard5.pw"), result: "", total: 0, totalStr: "", dice: {}, css: "", } as M5RollResult; return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.actor-${attributeKey}-long`)); } static fromUnlearnedSkill(actor: any, skill: M5SkillUnlearned, skillName: string) { const rollData = actor.getRollData() as M5RollData; rollData.i = { fw: skill.fw, bonus: actor.system.calc?.attributes[skill.attribute]?.bonus ?? 0, }; rollData.iType = "skill"; rollData.rolls["0"] = { formula: "1d20 + @i.fw + @i.bonus", enabled: true, label: (game as Game).i18n.localize("midgard5.pw"), result: "", total: 0, totalStr: "", dice: {}, css: "", } as M5RollResult; return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.${skillName}`)); } static brawl(actor: any) { const rollData = actor.getRollData() as M5RollData; rollData.i = { attackBonus: 0, damageBonus: 0, }; rollData.rolls["0"] = { formula: "1d20 + @c.calc.stats.brawlFw", enabled: true, label: (game as Game).i18n.localize("midgard5.attack"), result: "", total: 0, totalStr: "", dice: {}, css: "", } as M5RollResult; rollData.rolls["1"] = { formula: "1d6 - 4 + @c.calc.stats.damageBonus.value", enabled: true, label: (game as Game).i18n.localize("midgard5.damage"), result: "", total: 0, totalStr: "", dice: {}, css: "", } as M5RollResult; return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.brawl")); } static perception(actor: any) { const rollData = actor.getRollData() as M5RollData; rollData.rolls["0"] = { formula: "1d20 + @c.calc.stats.perception.value + @c.calc.stats.perceptionFW", enabled: true, label: (game as Game).i18n.localize("midgard5.perception"), result: "", total: 0, totalStr: "", dice: {}, css: "", } as M5RollResult; return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.perception")); } static cleanSpell(actor: any) { const rollData = actor.getRollData() as M5RollData; rollData.rolls["0"] = { formula: "1d20 + @c.calc.stats.spellCasting.value", enabled: true, label: (game as Game).i18n.localize("midgard5.spellCasting"), result: "", total: 0, totalStr: "", dice: {}, css: "", } as M5RollResult; return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.spellCasting")); } static drinking(actor: any) { const rollData = actor.getRollData() as M5RollData; rollData.rolls["0"] = { formula: "1d20 + @c.calc.stats.drinking.value + @c.calc.stats.drinkingFW", enabled: true, label: (game as Game).i18n.localize("midgard5.drinking"), result: "", total: 0, totalStr: "", dice: {}, css: "", } as M5RollResult; return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.drinking")); } static defense(actor: any) { const rollData = actor.getRollData() as M5RollData; rollData.i = { defenseBonus: 0, }; rollData.rolls["0"] = { formula: "1d20 + @c.calc.stats.defense.value + @c.calc.stats.defenseBonus.value", enabled: true, label: (game as Game).i18n.localize("midgard5.defense"), result: "", total: 0, totalStr: "", dice: {}, css: "", } as M5RollResult; return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.defense")); } static resistanceMind(actor: any) { const rollData = actor.getRollData() as M5RollData; rollData.i = { defenseBonus: 0, }; rollData.rolls["0"] = { formula: "1d20 + @c.calc.stats.resistanceMind.value", enabled: true, label: (game as Game).i18n.localize("midgard5.resistanceMind"), result: "", total: 0, totalStr: "", dice: {}, css: "", } as M5RollResult; return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.resistanceMind")); } static resistanceBody(actor: any) { const rollData = actor.getRollData() as M5RollData; rollData.i = { defenseBonus: 0, }; rollData.rolls["0"] = { formula: "1d20 + @c.calc.stats.resistanceBody.value", enabled: true, label: (game as Game).i18n.localize("midgard5.resistanceBody"), result: "", total: 0, totalStr: "", dice: {}, css: "", } as M5RollResult; return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.resistanceBody")); } static parseDiceSides(formula: string): FormulaParseResult { const ewMatcher: RegExp = /\d*[dD]20/g; const pwMatcher: RegExp = /(\d+)\s*\-\s*\d*[dD]100/g; let res = formula.match(ewMatcher); if (res && !!res[0]) { return { sides: 20, type: "ew", threshold: null, }; } res = formula.match(pwMatcher); if (res && !!res[1]) { return { sides: 100, type: "pw", threshold: parseInt(res[1]), }; } return null; } } interface FormulaParseResult { sides: number; type: string; threshold: number; }