foundryvtt-reve-de-dragon/module/rdd-resolution-table.js

295 lines
12 KiB
JavaScript

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 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 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);
mergeObject(chances, this._computeCell(undefined, newScore), { overwrite: true });
}
}
/* -------------------------------------------- */
static _updateChancesWithBonus(chances, bonus, finalLevel) {
if (bonus && finalLevel > -11) {
let newScore = Number(chances.score) + bonus;
mergeObject(chances, this._computeCell(undefined, newScore), { overwrite: true });
}
}
/* -------------------------------------------- */
static significativeRequise(chances) {
chances.roll = Math.floor(chances.score / 2);
mergeObject(chances, reussites.find(x => x.code == 'sign'), { overwrite: true });
}
/* -------------------------------------------- */
static succesRequis(chances) {
chances.roll = chances.score;
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);
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)
});
}
}