ChatMessage.create est async, il faut donc de préférence l'appeler avec un await. Des effets secondaires avaient lieu (ordre de messages, updates ultérieurs parfois pas pris en compte)
345 lines
12 KiB
JavaScript
345 lines
12 KiB
JavaScript
import { ChatUtility } from "./chat-utility.js";
|
|
import { Grammar } from "./grammar.js";
|
|
import { RdDInitiative } from "./initiative.mjs";
|
|
import { RdDItem } from "./item.js";
|
|
import { CATEGORIES_COMPETENCES, 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 },
|
|
];
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
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 RdDItem {
|
|
|
|
static get ITEM_TYPE() { return ITEM_TYPES.competence }
|
|
|
|
static get defaultIcon() { return "systems/foundryvtt-reve-de-dragon/icons/competence_defaut.webp" }
|
|
|
|
isNiveauBase() {
|
|
return this.system.niveau == this.system.base && this.system.xp == 0
|
|
}
|
|
|
|
getBaseInit() {
|
|
const carac = this.getInitCarac()
|
|
if (carac == undefined) {
|
|
return undefined
|
|
}
|
|
return RdDInitiative.ajustementInitiative(carac.value, this.system.niveau)
|
|
}
|
|
|
|
getInitCarac() {
|
|
if (!this.actor) {
|
|
return undefined
|
|
}
|
|
switch (this.system.categorie) {
|
|
case CATEGORIES_COMPETENCES.melee.key: return this.actor.system.carac.melee
|
|
case CATEGORIES_COMPETENCES.tir.key: return this.actor.system.carac.tir
|
|
case CATEGORIES_COMPETENCES.lancer.key: return this.actor.system.carac.lancer
|
|
case CATEGORIES_COMPETENCES.draconic.key: return this.actor.system.carac.reve
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
async _preUpdate(changed, options, user) {
|
|
await this.checkCompetenceXP(changed.system?.xp)
|
|
return super._preUpdate(changed, options, user)
|
|
}
|
|
|
|
async checkCompetenceXP(newXP, display = true) {
|
|
if (this.actor?.isPersonnage() && newXP && newXP != this.system.xp && newXP > 0) {
|
|
const newNiv = this.system.niveau + 1
|
|
let needed = RdDItemCompetence.getCompetenceNextXp(newNiv)
|
|
if (newXP >= needed) {
|
|
const xpData = {
|
|
alias: this.actor.getAlias(),
|
|
competence: this.name,
|
|
niveau: newNiv,
|
|
xp: newXP,
|
|
archetype: this.system.niveau_archetype,
|
|
archetypeWarning: newNiv > this.system.niveau_archetype
|
|
}
|
|
if (display) {
|
|
await ChatMessage.create({
|
|
whisper: ChatUtility.getOwners(this.actor),
|
|
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-competence-xp.hbs`, xpData)
|
|
})
|
|
}
|
|
return xpData
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
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(competenceName) {
|
|
return competenceName?.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);
|
|
}
|
|
|
|
} |