foundryvtt-reve-de-dragon/module/item-competence.js
Vincent Vandemeulebrouck c7dfc8682d Support de personnages sans compétences
Un personnage peut maintenant n'avoir aucune compétence

Exemple: les entités invoquées (Kanaillous, ..) n'ont pas besoin
d'une liste de compétences sans aucune valeur.

Ceci permettrait de supprimer des compétences prohibées.

# Conflicts:
#	module/actor.js
#	module/actor/base-actor-reve.js
2024-12-09 00:09:31 +01:00

294 lines
11 KiB
JavaScript

import { Grammar } from "./grammar.js";
import { RdDItem } from "./item.js";
import { SANS_COMPETENCE } from "./item/base-items.js";
import { Misc } from "./misc.js";
const competenceTroncs = [["Esquive", "Dague", "Corps à corps"],
["Epée à 1 main", "Epée à 2 mains", "Hache à 1 main", "Hache à 2 mains", "Lance", "Masse à 1 main", "Masse à 2 mains"]];
const xp_par_niveau = [5, 5, 5, 10, 10, 10, 10, 15, 15, 15, 15, 20, 20, 20, 20, 30, 30, 40, 40, 60, 60, 100, 100, 100, 100, 100, 100, 100, 100, 100];
const niveau_max = xp_par_niveau.length - 10;
/* -------------------------------------------- */
const limitesArchetypes = [
{ niveau: 0, nombreMax: 100 },
{ niveau: 1, nombreMax: 10 },
{ niveau: 2, nombreMax: 9 },
{ niveau: 3, nombreMax: 8 },
{ niveau: 4, nombreMax: 7 },
{ niveau: 5, nombreMax: 6 },
{ niveau: 6, nombreMax: 5 },
{ niveau: 7, nombreMax: 4 },
{ niveau: 8, nombreMax: 3 },
{ niveau: 9, nombreMax: 2 },
{ niveau: 10, nombreMax: 1 },
{ niveau: 11, nombreMax: 1 },
];
/* -------------------------------------------- */
export const CATEGORIES_COMPETENCES = {
"generale": { base: -4, label: "Générales" },
"particuliere": { base: -8, label: "Particulières" },
"specialisee": { base: -11, label: "Spécialisées" },
"connaissance": { base: -11, label: "Connaissances" },
"draconic": { base: -11, label: "Draconic" },
"melee": { base: -6, label: "Mêlée" },
"tir": { base: -8, label: "Tir" },
"lancer": { base: -8, label: "Lancer" }
}
function _buildCumulXP() {
let cumulXP = { "-11": 0 };
let cumul = 0;
for (let i = 0; i <= xp_par_niveau.length; i++) {
let level = i - 10;
cumul += xp_par_niveau[i];
cumulXP[level] = cumul;
}
return cumulXP;
}
const competence_xp_cumul = _buildCumulXP();
export class RdDItemCompetence extends Item {
/* -------------------------------------------- */
static getLabelCategorie(category) {
return CATEGORIES_COMPETENCES[category].label;
}
/* -------------------------------------------- */
/* -------------------------------------------- */
static getNiveauBase(category, itemType) {
let categories = RdDItem.getCategories(itemType)
return categories[category]?.base ?? 0;
}
/* -------------------------------------------- */
static getCategorie(competence) {
return competence?.system.categorie;
}
static isDraconic(competence) {
return competence?.system.categorie == 'draconic';
}
/* -------------------------------------------- */
static getVoieDraconic(competences, voie) {
return RdDItemCompetence.findFirstItem(competences, voie, {
preFilter: it => it.isCompetence() && RdDItemCompetence.isDraconic(it),
description: 'Draconic',
});
}
/* -------------------------------------------- */
static isCompetenceArme(competence) {
if (competence.isCompetence() && !competence.isCorpsACorps() && !competence.isEsquive()) {
switch (competence.system.categorie) {
case 'melee':
case 'tir':
case 'lancer':
return true;
}
}
return false;
}
/* -------------------------------------------- */
static isArmeUneMain(competence) {
return competence.isCompetenceArme() && competence.name.toLowerCase().includes("1 main");
}
static isArme2Main(competence) {
return competence.isCompetenceArme() && competence.name.toLowerCase().includes("2 main");
}
static isThanatos(competence) {
return competence.isCompetencePersonnage() && Grammar.toLowerCaseNoAccent(competence.name).includes('thanatos');
}
/* -------------------------------------------- */
static isMalusEncombrementTotal(competence) {
return competence?.name.toLowerCase().match(/(natation|acrobatie)/) || 0;
}
/* -------------------------------------------- */
static getListTronc(compName) {
for (let troncList of competenceTroncs) {
for (let troncName of troncList) {
if (troncName == compName)
return troncList;
}
}
return [];
}
/* -------------------------------------------- */
static computeTotalXP(competences) {
const total = competences.map(c => RdDItemCompetence.computeXP(c))
.reduce(Misc.sum(), 0);
const economieTronc = RdDItemCompetence.computeEconomieXPTronc(competences);
return total - economieTronc;
}
/* -------------------------------------------- */
static computeXP(competence) {
const factor = RdDItemCompetence.isThanatos(competence) ? 2 : 1; // Thanatos compte double !
const xpNiveau = RdDItemCompetence.computeDeltaXP(competence.system.base, competence.system.niveau ?? competence.system.base);
const xp = competence.system.xp ?? 0;
const xpSort = competence.system.xp_sort ?? 0;
return factor * (xpNiveau + xp) + xpSort;
}
/* -------------------------------------------- */
static computeEconomieXPTronc(competences) {
return competenceTroncs.map(
list => list.map(name => RdDItemCompetence.findCompetence(competences, name, { onMessage: message => { } }))
// calcul du coût xp jusqu'au niveau 0 maximum
.map(it => RdDItemCompetence.computeDeltaXP(it?.system.base ?? -11, Math.min(it?.system.niveau ?? -11, 0)))
.sort(Misc.ascending())
.splice(0, list.length - 1) // prendre toutes les valeurs sauf l'une des plus élevées
.reduce(Misc.sum(), 0)
).reduce(Misc.sum(), 0);
}
/* -------------------------------------------- */
static computeDeltaXP(from, to) {
RdDItemCompetence._valideNiveau(from);
RdDItemCompetence._valideNiveau(to);
return competence_xp_cumul[to] - competence_xp_cumul[from];
}
/* -------------------------------------------- */
static computeCompetenceXPCost(competence) {
let xp = RdDItemCompetence.getDeltaXp(competence.system.base, competence.system.niveau ?? competence.system.base);
xp += competence.system.xp ?? 0;
if (compData.name.includes('Thanatos')) xp *= 2; /// Thanatos compte double !
xp += competence.system.xp_sort ?? 0;
return xp;
}
/* -------------------------------------------- */
static computeEconomieCompetenceTroncXP(competences) {
let economie = 0;
for (let troncList of competenceTroncs) {
let list = troncList.map(name => RdDItemCompetence.findCompetence(competences, name))
.sort(Misc.descending(c => this.system.niveau)); // tri du plus haut au plus bas
list.splice(0, 1); // ignorer la plus élevée
list.map(c => c).forEach(c => {
economie += RdDItemCompetence.getDeltaXp(c.system.base, Math.min(c.system.niveau, 0))
});
}
return economie;
}
/* -------------------------------------------- */
static levelUp(item, stressTransforme) {
item.system.xpNext = RdDItemCompetence.getCompetenceNextXp(item.system.niveau);
const xpManquant = item.system.xpNext - item.system.xp;
item.system.isLevelUp = xpManquant <= 0;
item.system.isStressLevelUp = (xpManquant > 0 && stressTransforme >= xpManquant && item.system.niveau < item.system.niveau_archetype);
item.system.stressXpMax = 0;
if (xpManquant > 0 && stressTransforme > 0 && item.system.niveau < item.system.niveau_archetype) {
item.system.stressXpMax = Math.min(xpManquant, stressTransforme);
}
}
/* -------------------------------------------- */
static isNiveauBase(item) {
return item.system.niveau == undefined || Number(item.system.niveau) == RdDItemCompetence.getNiveauBase(item.system.categorie, item.type);
}
/* -------------------------------------------- */
static findCompetence(list, idOrName, options = {}) {
if (idOrName == undefined || idOrName == "") {
return RdDItemCompetence.sansCompetence();
}
options = foundry.utils.mergeObject(options, { preFilter: it => it.isCompetence(), description: 'compétence' }, { overwrite: false, inplace: false });
return RdDItemCompetence.findFirstItem(list, idOrName, options);
}
/* -------------------------------------------- */
static findCompetences(list, name, options = {}) {
options = foundry.utils.mergeObject(options, { preFilter: it => it.isCompetence(), description: 'compétence' }, { overwrite: false, inplace: false });
return Misc.findAllLike(name, list, options);
}
static sansCompetence() { return SANS_COMPETENCE }
static findFirstItem(list, idOrName, options) {
return list.find(it => it.id == idOrName && options.preFilter(it))
?? Misc.findFirstLike(idOrName, list, options);
}
/* -------------------------------------------- */
static getCompetenceNextXp(niveau) {
return RdDItemCompetence.getCompetenceXp(niveau + 1);
}
/* -------------------------------------------- */
static getCompetenceXp(niveau) {
RdDItemCompetence._valideNiveau(niveau);
return niveau < -10 ? 0 : xp_par_niveau[niveau + 10];
}
/* -------------------------------------------- */
static getDeltaXp(from, to) {
RdDItemCompetence._valideNiveau(from);
RdDItemCompetence._valideNiveau(to);
return competence_xp_cumul[to] - competence_xp_cumul[from];
}
/* -------------------------------------------- */
static _valideNiveau(niveau) {
if (niveau < -11 || niveau > niveau_max) {
console.warn(`Niveau ${niveau} en dehors des niveaux de compétences: [-11, ${niveau_max} ]`);
}
}
/* -------------------------------------------- */
static computeResumeArchetype(competences) {
const computed = foundry.utils.duplicate(limitesArchetypes);
computed.forEach(it => { it.nombre = 0; it.reste = it.nombreMax; });
competences.map(it => Math.max(0, it.system.niveau_archetype))
.filter(n => n > 0)
.forEach(n => {
computed[n] = computed[n] ?? { niveau: n, nombreMax: 0, reste: 0, nombre: 0 };
computed[n].reste--;
computed[n].nombre++;
});
return computed.filter(it => it.niveau > 0);
}
/* -------------------------------------------- */
static triVisible(competences) {
return competences
? competences.filter(it => !it.system.isHidden).sort((a, b) => RdDItemCompetence.compare(a, b))
: []
}
static $positionTri(comp) {
if (comp.name.startsWith("Survie")) {
if (comp.name.includes("Cité")) return 0;
if (comp.name.includes("Extérieur")) return 1;
return 2;
}
if (comp.system.categorie.startsWith("melee")) {
if (comp.name.includes("Corps")) return 0;
if (comp.name.includes("Dague")) return 1;
if (comp.name.includes("Esquive")) return 2;
return 3;
}
if (comp.system.categorie.startsWith("draconic")) {
if (comp.name.includes("Oniros")) return 0;
if (comp.name.includes("Hypnos")) return 1;
if (comp.name.includes("Narcos")) return 2;
if (comp.name.includes("Thanatos")) return 3;
return 4;
}
return 0;
}
static compare(a, b) {
const diff = RdDItemCompetence.$positionTri(a) - RdDItemCompetence.$positionTri(b);
return diff ? diff : a.name.localeCompare(b.name);
}
}