258 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			258 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 reussiteNormale = { 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) };
 | 
						|
const reussiteSignificative = { 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) };
 | 
						|
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 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) },
 | 
						|
  reussiteNormale,
 | 
						|
  reussiteSignificative,
 | 
						|
  { 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 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 reussiteSignificative
 | 
						|
    }
 | 
						|
    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)
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
} |