forked from public/foundryvtt-reve-de-dragon
		
	
		
			
				
	
	
		
			254 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			254 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { renderTemplate } from "./constants.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(level, percentage) {
 | |
|     return {
 | |
|       level: level,
 | |
|       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 actorChatName(actor) {
 | |
|     return actor ?? game.user.name;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   static async rollData(rollData) {
 | |
|     rollData.rolled = await this.roll(rollData.caracValue, rollData.finalLevel, rollData);
 | |
|     return rollData;
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   static async roll(caracValue, finalLevel, rollData = {}) {
 | |
|     let chances = foundry.utils.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.getFractionOneN(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 replaceParticuliereDemiSurprise(chances){
 | |
|     foundry.utils.mergeObject(chances, reussites.find(x => x.code == 'part'), { overwrite: true });
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   static significativeRequise(chances) {
 | |
|     chances.roll = Math.min(chances.part + 1, chances.sign)
 | |
|     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 computeReussite(chances, roll, diviseur) {
 | |
|     const reussite = reussites.find(x => x.condition(chances, roll))
 | |
|     if (diviseur > 1 && reussite.isSuccess) {
 | |
|       if (chances.norm < roll * diviseur) {
 | |
|         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.hbs', {
 | |
|       carac: carac,
 | |
|       difficulte: level,
 | |
|       min: minLevel,
 | |
|       rows: Misc.intArray(minCarac, maxCarac + 1),
 | |
|       cols: Misc.intArray(minLevel, maxLevel + 1)
 | |
|     });
 | |
|   }
 | |
| 
 | |
| } |