import { ChatUtility } from "./chat-utility.js"; import { Misc } from "./misc.js"; import { RdDDice } from "./rdd-dice.js"; import { ReglesOptionnelles } from "./settings/regles-optionnelles.js"; /** * difficultés au delà de -10 */ const levelDown = [ { level: -11, score: 1, norm: 1, sign: 0, part: 0, epart: 2, etotal: 90 }, { level: -12, score: 1, norm: 1, sign: 0, part: 0, epart: 2, etotal: 70 }, { level: -13, score: 1, norm: 1, sign: 0, part: 0, epart: 2, etotal: 50 }, { level: -14, score: 1, norm: 1, sign: 0, part: 0, epart: 2, etotal: 30 }, { level: -15, score: 1, norm: 1, sign: 0, part: 0, epart: 2, etotal: 10 }, { level: -16, score: 1, norm: 1, sign: 0, part: 0, epart: 0, etotal: 2 } ]; const levelImpossible = { score: 0, norm: 0, sign: 0, part: 0, epart: 0, etotal: 1 }; const reussites = [ { code: "etotal", isPart: false, isSign: false, isSuccess: false, isEchec: true, isEPart: true, isETotal: true, ptTache: -4, ptQualite: -6, quality: "Echec total", condition: (target, roll) => roll >= target.etotal && roll <= 100 }, { code: "epart", isPart: false, isSign: false, isSuccess: false, isEchec: true, isEPart: true, isETotal: false, ptTache: -2, ptQualite: -4, quality: "Echec particulier", condition: (target, roll) => (roll >= target.epart && roll < target.etotal) }, { code: "echec", isPart: false, isSign: false, isSuccess: false, isEchec: true, isEPart: false, isETotal: false, ptTache: 0, ptQualite: -2, quality: "Echec normal", condition: (target, roll) => (roll > target.norm && roll < target.etotal) }, { code: "norm", isPart: false, isSign: false, isSuccess: true, isEchec: false, isEPart: false, isETotal: false, ptTache: 1, ptQualite: 0, quality: "Réussite normale", condition: (target, roll) => (roll > target.sign && roll <= target.norm) }, { code: "sign", isPart: false, isSign: true, isSuccess: true, isEchec: false, isEPart: false, isETotal: false, ptTache: 2, ptQualite: 1, quality: "Réussite significative", condition: (target, roll) => (roll > target.part && roll <= target.sign) }, { code: "part", isPart: true, isSign: true, isSuccess: true, isEchec: false, isEPart: false, isETotal: false, ptTache: 3, ptQualite: 2, quality: "Réussite Particulière!", condition: (target, roll) => (roll > 0 && roll <= target.part) }, { code: "error", isPart: false, isSign: false, isSuccess: false, isEchec: true, isEPart: true, isETotal: true, ptTache: 0, ptQualite: 0, quality: "Jet de dés invalide", condition: (target, roll) => (roll <= 0 || roll > 100) } ]; const reussiteInsuffisante = { code: "notSign", isPart: false, isSign: false, isSuccess: false, isEchec: true, isEPart: false, isETotal: false, ptTache: 0, ptQualite: -2, quality: "Réussite insuffisante", condition: (target, roll) => false } /* -------------------------------------------- */ const CARAC_MAXIMUM_RESOLUTION = 40; /* -------------------------------------------- */ export class RdDResolutionTable { static resolutionTable = this.build() /* -------------------------------------------- */ static build() { let table = [] for (var caracValue = 0; caracValue <= CARAC_MAXIMUM_RESOLUTION; caracValue++) { table[caracValue] = this._computeRow(caracValue); } return table; } /* -------------------------------------------- */ static computeChances(carac, level) { if (level < -16) { return levelImpossible; } if (level < -10) { return levelDown.find(it => it.level == level); } const percentage = RdDResolutionTable.computePercentage(carac, level); return this._computeCell(level, percentage); } /* -------------------------------------------- */ static _computeRow(caracValue) { let dataRow = [ this._computeCell(-10, Math.max(Math.floor(caracValue / 4), 1)), this._computeCell(-9, Math.max(Math.floor(caracValue / 2), 1)) ] for (var diff = -8; diff <= 22; diff++) { dataRow[diff + 10] = this._computeCell(diff, RdDResolutionTable.computePercentage(caracValue, diff)); } return dataRow; } /* -------------------------------------------- */ static _computeCell(niveau, percentage) { return { niveau: niveau, score: percentage, norm: Math.min(99, percentage), sign: this._reussiteSignificative(percentage), part: this._reussitePart(percentage), epart: this._echecParticulier(percentage), etotal: this._echecTotal(percentage) }; } /* -------------------------------------------- */ static getResultat(code) { let resultat = reussites.find(r => code == r.code); if (resultat == undefined) { resultat = reussites.find(r => r.code == "error"); } return resultat; } /* -------------------------------------------- */ static async displayRollData(rollData, actor = undefined, template = 'chat-resultat-general.html') { return await ChatUtility.createChatWithRollMode(RdDResolutionTable.actorChatName(actor), { content: await RdDResolutionTable.buildRollDataHtml(rollData, template) }); } static actorChatName(actor) { return actor?.userName ?? game.user.name; } /* -------------------------------------------- */ static async buildRollDataHtml(rollData, template = 'chat-resultat-general.html') { rollData.show = rollData.show || {}; return await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/${template}`, rollData); } /* -------------------------------------------- */ static async rollData(rollData) { rollData.rolled = await this.roll(rollData.caracValue, rollData.finalLevel, rollData); return rollData; } /* -------------------------------------------- */ static async roll(caracValue, finalLevel, rollData = {}) { let chances = duplicate(this.computeChances(caracValue, finalLevel)); this._updateChancesWithBonus(chances, rollData.bonus, finalLevel); this._updateChancesFactor(chances, rollData.diviseurSignificative); chances.showDice = rollData.showDice; chances.rollMode = rollData.rollMode; let rolled = await this.rollChances(chances, rollData.diviseurSignificative, rollData.forceDiceResult); rolled.caracValue = caracValue; rolled.finalLevel = finalLevel; rolled.bonus = rollData.bonus; rolled.factorHtml = Misc.getFractionHtml(rollData.diviseurSignificative); if (ReglesOptionnelles.isUsing("afficher-colonnes-reussite")) { rolled.niveauNecessaire = this.findNiveauNecessaire(caracValue, rolled.roll); rolled.ajustementNecessaire = rolled.niveauNecessaire - finalLevel; } return rolled; } /* -------------------------------------------- */ static findNiveauNecessaire(carac, rolled) { if (carac == 0) { return NaN; } if (rolled >= carac){ const upper = Math.ceil(rolled/carac); return 2*upper -10 } if (rolled > Math.floor(carac/2)) { return -8 } if (rolled > Math.floor(carac/4)) { return -9 } if (rolled > 1) { return -10 } return -11; } /* -------------------------------------------- */ static _updateChancesFactor(chances, diviseur) { if (chances.level > -11 && diviseur && diviseur > 1) { let newScore = Math.floor(chances.score / diviseur); foundry.utils.mergeObject(chances, this._computeCell(undefined, newScore), { overwrite: true }); } } /* -------------------------------------------- */ static _updateChancesWithBonus(chances, bonus, finalLevel) { if (bonus && finalLevel > -11) { let newScore = Number(chances.score) + bonus; foundry.utils.mergeObject(chances, this._computeCell(undefined, newScore), { overwrite: true }); } } /* -------------------------------------------- */ static significativeRequise(chances) { chances.roll = Math.floor(chances.score / 2); foundry.utils.mergeObject(chances, reussites.find(x => x.code == 'sign'), { overwrite: true }); } /* -------------------------------------------- */ static succesRequis(chances) { chances.roll = chances.score; foundry.utils.mergeObject(chances, reussites.find(x => x.code == 'norm'), { overwrite: true }); } /* -------------------------------------------- */ static async rollChances(chances, diviseur, forceDiceResult = -1) { chances.forceDiceResult = forceDiceResult <= 0 || forceDiceResult > 100 ? undefined : { total: forceDiceResult }; chances.roll = await RdDDice.rollTotal("1d100", chances); foundry.utils.mergeObject(chances, this.computeReussite(chances, chances.roll, diviseur), { overwrite: true }); return chances; } /* -------------------------------------------- */ static computePercentage(carac, diff) { if (diff < -16) return 0 if (diff < -10) return 1 if (diff == -10) return Math.max(Math.floor(carac / 4), 1) if (diff == -9) return Math.max(Math.floor(carac / 2), 1) return Math.max(Math.floor(carac * (diff + 10) / 2), 1); } /* -------------------------------------------- */ static isEchec(rollData) { switch (rollData.surprise) { case 'demi': return !rollData.rolled.isSign; case 'totale': return true; } return rollData.rolled.isEchec; } /* -------------------------------------------- */ static isEchecTotal(rollData) { if (rollData.arme && rollData.surprise == 'demi') { return rollData.rolled.isEchec; } return rollData.rolled.isETotal; } /* -------------------------------------------- */ static isParticuliere(rollData) { if (rollData.arme && rollData.surprise) { return false; } return rollData.rolled.isPart; } /* -------------------------------------------- */ static isReussite(rollData) { switch (rollData.surprise) { case 'demi': return rollData.rolled.isSign; case 'totale': return false; } return rollData.rolled.isSuccess; } /* -------------------------------------------- */ static computeReussite(chances, roll, diviseur) { const reussite = reussites.find(x => x.condition(chances, roll)); if (diviseur > 1 && reussite.code == 'norm') { return reussiteInsuffisante; } return reussite; } /* -------------------------------------------- */ static _reussiteSignificative(percentage) { return Math.floor(percentage / 2); } /* -------------------------------------------- */ static _reussitePart(percentage) { return Math.ceil(percentage / 5); } /* -------------------------------------------- */ static _echecParticulier(percentage) { const epart = Math.ceil(percentage / 5) + 80; return epart >= 100 ? 101 : epart; } /* -------------------------------------------- */ static _echecTotal(percentage) { const etotal = Math.ceil(percentage / 10) + 91; return percentage >= 100 ? 101 : Math.min(etotal, 100); } /* -------------------------------------------- */ static subTable(carac, level, delta = { carac: 2, level: 5}) { return { carac, level, minCarac: carac - (delta?.carac ?? 2), maxCarac: carac + (delta?.carac ?? 2), minLevel: level - (delta?.level ?? 5), maxLevel: level + (delta?.level ?? 5) }; } /* -------------------------------------------- */ static async buildHTMLTable({ carac: carac, level: level, minCarac = 1, maxCarac = 21, minLevel = -10, maxLevel = 11 }) { let colonnes = maxLevel - minLevel; minCarac = Math.max(minCarac, 1); maxCarac = Math.min(maxCarac, minCarac + 20); minLevel = Math.max(minLevel, -10); maxLevel = Math.max(Math.min(maxLevel, 30), minLevel + colonnes); return await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/resolution-table.html', { carac: carac, difficulte: level, min: minLevel, rows: Misc.intArray(minCarac, maxCarac+1), cols: Misc.intArray(minLevel, maxLevel+1) }); } }