import { Grammar } from "./grammar.js"; import { RdDItem } from "./item.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)) // 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) { return Misc.findAllLike(name, list, { filter: it => it.isCompetence(), description: 'compétence' }); } static sansCompetence() { return { name: "Sans compétence", type: "competence", img: "systems/foundryvtt-reve-de-dragon/icons/templates/icone_parchement_vierge.webp", system: { niveau: 0, default_diffLibre: 0, base: 0, categorie: "Aucune", description: "", descriptionmj: "", defaut_carac: "", } }; } 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.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); } }