foundryvtt-reve-de-dragon/module/rdd-resolution-table.js
Vincent Vandemeulebrouck 582df7e290 Simplify ChatMessage whispers
Les messages dans les TMRs sont envoyés au GM

Simplification des messages de tchat liés à un actor: on peut
utiliser les Owners (car les GMs sont owner).

Au lieu de passer le name de l'Actor (qui peut être incorrect si deux
actors ont le même, mais pas les mêmes propriétaires), on passe
directement l'actor pour déterminer mles destinataires de messages
2024-10-16 23:32:18 +02:00

296 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 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(
{ content: await RdDResolutionTable.buildRollDataHtml(rollData, template) },
actor
)
}
static actorChatName(actor) {
return actor ?? 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 = 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.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)
});
}
}