import { ChatUtility } from "./chat-utility.js"; import { RdDItemArme } from "./item-arme.js"; import { Misc } from "./misc.js"; import { RdDBonus } from "./rdd-bonus.js"; import { RdDCombat } from "./rdd-combat.js"; import { RdDDice } from "./rdd-dice.js"; /** * difficultés au delà de -10 */ const levelDown = [ { level: -11, score: 1, sign: 0, part: 0, epart: 2, etotal: 90 }, { level: -12, score: 1, sign: 0, part: 0, epart: 2, etotal: 70 }, { level: -13, score: 1, sign: 0, part: 0, epart: 2, etotal: 50 }, { level: -14, score: 1, sign: 0, part: 0, epart: 2, etotal: 30 }, { level: -15, score: 1, sign: 0, part: 0, epart: 2, etotal: 10 }, { level: -16, score: 1, sign: 0, part: 0, epart: 0, etotal: 2 } ]; const levelImpossible = { score: 0, sign: 0, part: 0, epart: 0, etotal: 1 }; /** * Table des résultats spéciaux - inutilisée, conservée si on veut afficher la table */ const specialResults = [ { part: 0, epart: 0, etotal: 0, min: 0, max: 0 }, { part: 1, epart: 81, etotal: 92, min: 1, max: 5 }, { part: 2, epart: 82, etotal: 92, min: 6, max: 10 }, { part: 3, epart: 83, etotal: 93, min: 11, max: 15 }, { part: 4, epart: 84, etotal: 93, min: 16, max: 20 }, { part: 5, epart: 85, etotal: 94, min: 21, max: 25 }, { part: 6, epart: 86, etotal: 94, min: 26, max: 30 }, { part: 7, epart: 87, etotal: 95, min: 31, max: 35 }, { part: 8, epart: 88, etotal: 95, min: 36, max: 40 }, { part: 9, epart: 89, etotal: 96, min: 41, max: 45 }, { part: 10, epart: 90, etotal: 96, min: 46, max: 50 }, { part: 11, epart: 91, etotal: 97, min: 51, max: 55 }, { part: 12, epart: 92, etotal: 97, min: 56, max: 60 }, { part: 13, epart: 93, etotal: 98, min: 61, max: 65 }, { part: 14, epart: 94, etotal: 98, min: 65, max: 70 }, { part: 15, epart: 95, etotal: 99, min: 71, max: 75 }, { part: 16, epart: 96, etotal: 99, min: 76, max: 80 }, { part: 17, epart: 97, etotal: 100, min: 81, max: 85 }, { part: 18, epart: 98, etotal: 100, min: 86, max: 90 }, { part: 19, epart: 99, etotal: 100, min: 81, max: 95 }, { part: 20, epart: 100, etotal: 100, min: 96, max: 100 } ]; 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 reussiteSignificative = reussites.find(r => r.code == "sign"); const reussiteNormale = reussites.find(r => r.code == "norm"); const echecNormal = reussites.find(r => r.code == "echec"); const caracMaximumResolution = 60; /* -------------------------------------------- */ export class RdDResolutionTable { static resolutionTable = this.build() /* -------------------------------------------- */ static build() { let table = [] for (var caracValue = 0; caracValue <= caracMaximumResolution; caracValue++) { table[caracValue] = this._computeRow(caracValue); } return table; } /* -------------------------------------------- */ static getResultat(code) { let resultat = reussites.find(r => code == r.code); if (resultat == undefined) { resultat = reussites.find(r => r.code == "error"); } return resultat; } /* -------------------------------------------- */ static explain(rolled) { let message = "
Jet : " + rolled.roll + " sur " + rolled.score + "% "; if (rolled.caracValue != null && rolled.finalLevel != null) { message += (rolled.diviseur > 1 ? `(1/${rolled.diviseur} de ` : "(") + rolled.caracValue + " à " + Misc.toSignedString(rolled.finalLevel) + ") "; } message += '' + rolled.quality + '' return message; } /* -------------------------------------------- */ static async buildRollDataHtml(rollData, template = 'chat-resultat-general.html') { rollData.ajustements = RdDResolutionTable._buildAjustements(rollData); rollData.show = rollData.show || {}; return await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/${template}`, rollData); } /* -------------------------------------------- */ static async displayRollData(rollData, userName, template = 'chat-resultat-general.html') { ChatUtility.chatWithRollMode( { content: await RdDResolutionTable.buildRollDataHtml(rollData, template) }, userName) } static _buildAjustements(rollData) { let list = []; if (rollData.competence) { list.push({ label: rollData.competence.name, value: rollData.competence.data.niveau }); } if (rollData.tactique) { const surprise = RdDBonus.find(rollData.tactique); list.push({ label: surprise.descr, value: surprise.attaque }); } if (rollData.surpriseDefenseur) { const surprise = RdDBonus.find(rollData.surpriseDefenseur); list.push({ label: surprise.descr, value: surprise.attaque }); } if (rollData.diffLibre != undefined) { const label = rollData.selectedSort ? rollData.selectedSort.name : 'Libre'; list.push({ label: label, value: rollData.diffLibre }); } if (rollData.diffConditions != undefined) { list.push({ label: 'Conditions', value: rollData.diffConditions }); } if (rollData.etat != undefined) { list.push({ label: 'Etat', value: rollData.etat }); } if (rollData.selectedCarac != undefined && rollData.moral != undefined && rollData.selectedCarac.label == 'Volonté') { list.push({ label: 'Moral', value: rollData.selectedCarac != undefined && rollData.moral != undefined && rollData.selectedCarac.label == 'Volonté' ? rollData.moral : undefined }); } if (RdDResolutionTable.isAjustementAstrologique(rollData)) { list.push({ label: 'Astrologique', value: rollData.ajustementAstrologique || 0 }); } if (rollData.rolled.bonus && rollData.selectedSort) { list.push({ descr: `Bonus de case: ${rollData.rolled.bonus}%` }); } if (rollData.diviseur > 1) { list.push({ descr: `Facteur significative ×${RdDResolutionTable._getFractionHtml(rollData.diviseur)}` }); } if (RdDCombat.isAttaqueFinesse(rollData.attackerRoll)) { list.push({ descr: 'Attaque particulière en finesse' }); } if (rollData.needParadeSignificative) { const catAttaque = RdDItemArme.getNomCategorieParade(rollData.attackerRoll.arme); const catParade = RdDItemArme.getNomCategorieParade(rollData.arme); list.push({ descr: `${catAttaque} vs ${catParade}` }); } if (rollData.surprise) { list.push({ descr: RdDBonus.find(rollData.surprise).descr }); } return list; } static _getFractionHtml(diviseur) { if (!diviseur || diviseur <= 1) return undefined; switch (diviseur || 1) { case 2: return '½'; case 4: return '¼'; default: return '1/' + diviseur; } } /* -------------------------------------------- */ static async rollData(rollData) { rollData.rolled = await this.roll(rollData.caracValue, rollData.finalLevel, rollData.bonus, rollData.diviseur, rollData.showDice); return rollData; } /* -------------------------------------------- */ static async roll(caracValue, finalLevel, bonus = undefined, diviseur = undefined, showDice = true) { let chances = this.computeChances(caracValue, finalLevel); this._updateChancesWithBonus(chances, bonus); this._updateChancesFactor(chances, diviseur); chances.showDice = showDice; let rolled = await this.rollChances(chances); rolled.caracValue = caracValue; rolled.finalLevel = finalLevel; rolled.bonus = bonus; rolled.factorHtml = RdDResolutionTable._getFractionHtml(diviseur); return rolled; } /* -------------------------------------------- */ static _updateChancesFactor(chances, diviseur) { if (diviseur && diviseur > 1) { let newScore = Math.floor(Number(chances.score) / diviseur); mergeObject(chances, this._computeCell(null, newScore), { overwrite: true }); } } /* -------------------------------------------- */ static _updateChancesWithBonus(chances, bonus) { if (bonus) { let newScore = Number(chances.score) + Number(bonus); mergeObject(chances, this._computeCell(null, newScore), { overwrite: true }); } } /* -------------------------------------------- */ static async rollChances(chances) { let myRoll = new Roll("1d100").roll(); myRoll.showDice = chances.showDice; await RdDDice.show(myRoll); chances.roll = myRoll.total; mergeObject(chances, this._computeReussite(chances, chances.roll)); return chances; } /* -------------------------------------------- */ static computeChances(caracValue, difficulte) { if (difficulte < -16) { return duplicate(levelImpossible); } if (difficulte < -10) { return duplicate(levelDown.find(levelData => levelData.level == difficulte)); } return duplicate(RdDResolutionTable.resolutionTable[caracValue][difficulte + 10]); } /* -------------------------------------------- */ static isAjustementAstrologique(rollData) { if (rollData.selectedCarac && rollData.selectedCarac.label.toLowerCase().includes('chance')) { return true; } if (rollData.selectedSort && rollData.selectedSort.data.isrituel) { return true; } return false; } /* -------------------------------------------- */ 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) { return reussites.find(x => x.condition(chances, roll)); } /* -------------------------------------------- */ 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, Math.max(Math.floor(caracValue * (diff + 10) / 2), 1)); } 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 _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 buildHTMLResults(caracValue, levelValue) { let cell = this.computeChances(caracValue, levelValue); cell.epart = cell.epart>99? 'N/A' : cell.epart; cell.etotal = cell.etotal>100? 'N/A' : cell.etotal; cell.score = Math.min(cell.score, 99); return ` Particulière: ${cell.part} - Significative: ${cell.sign} - Réussite: ${cell.score} - Echec Particulier: ${cell.epart} - Echec Total: ${cell.etotal} ` } /* -------------------------------------------- */ static buildHTMLTableExtract(caracValue, levelValue) { return this.buildHTMLTable(caracValue, levelValue, caracValue - 2, caracValue + 2, levelValue - 5, levelValue + 5) } static buildHTMLTable(caracValue, levelValue, minCarac = 1, maxCarac = 21, minLevel = -10, maxLevel = 11) { return this._buildHTMLTable(caracValue, levelValue, minCarac, maxCarac, minLevel, maxLevel) } /* -------------------------------------------- */ static _buildHTMLTable(caracValue, levelValue, minCarac, maxCarac, minLevel, maxLevel) { let countColonnes = maxLevel - minLevel; minCarac = Math.max(minCarac, 1); maxCarac = Math.min(maxCarac, caracMaximumResolution); minLevel = Math.max(minLevel, -10); maxLevel = Math.max(Math.min(maxLevel, 22), minLevel + countColonnes); let table = $("") .append(this._buildHTMLHeader(RdDResolutionTable.resolutionTable[0], minLevel, maxLevel)); for (var rowIndex = minCarac; rowIndex <= maxCarac; rowIndex++) { table.append(this._buildHTMLRow(RdDResolutionTable.resolutionTable[rowIndex], rowIndex, caracValue, levelValue, minLevel, maxLevel)); } table.append("
"); return table; } /* -------------------------------------------- */ static _buildHTMLHeader(dataRow, minLevel, maxLevel) { let tr = $(""); if (minLevel > -8) { tr.append($("").text("-8")) } if (minLevel > -7) { tr.append($("").text("...")); } for (let difficulte = minLevel; difficulte <= maxLevel; difficulte++) { tr.append($("").text(Misc.toSignedString(difficulte))); } return tr; } /* -------------------------------------------- */ static _buildHTMLRow(dataRow, rowIndex, caracValue, levelValue, minLevel, maxLevel) { let tr = $(""); let max = maxLevel; if (minLevel > -8) { let score = dataRow[-8 + 10].score; tr.append($("").text(score)) } if (minLevel > -7) { tr.append($("")) } for (let difficulte = minLevel; difficulte <= max; difficulte++) { let td = $(""); let score = dataRow[difficulte + 10].score; if (rowIndex == caracValue && levelValue == difficulte) { td.addClass('table-resolution-target'); } else if (difficulte == -8) { td.addClass('table-resolution-carac'); } tr.append(td.text(score)); } return tr; } }