Release Creation / build (release) Failing after 1m32s
- VermineTotemDice : influence Humain/Adapté (+1D/-1D par domaine, règles p. 121), gains/pertes via Instincts/Interdits avec limites (3D/totem, 5D total) et règles d'échange. - roll.mjs applique l'influence selon le Totem dominant (au lieu de l'ancienne logique basée sur identity.totem). - Dialogue TotemDiceDialog (instinct Humain/autre totem, interdit Humain, acte grave ±2D) + bouton et ligne d'influence sur la fiche personnage. - Dés de Totem gagnés en cas d'échec à l'apprentissage Dé d'Évolution (dés dépensés, garde capacité vide). - Couleur de texte @color-text-light-2 assombrie (#c9e0c0 → #3f9b55) pour la lisibilité dans tous les dialogues. fix: robustesse et visibilité des évolutions - buyEvolutionDialog : n'efface les Dés d'Évolution que si l'item est créé. - Flag mutation éditable sur la fiche item evolution + badge Adaptation/Mutation dans la liste du personnage. - loseDie : applique aussi la limite totale de 5 dés au gain Adapté. - Case "acte grave" conservée entre les rendus du dialogue.
172 lines
6.1 KiB
JavaScript
172 lines
6.1 KiB
JavaScript
/**
|
|
* VermineTotemDice - Gestion des Dés de Totems (Humain / Adapté).
|
|
*
|
|
* Règles p. 121 :
|
|
* - Influence : si un joueur possède strictement plus de Dés d'un Totem que
|
|
* de l'autre, ce Totem prend l'ascendant (+1D/-1D sur certains Domaines).
|
|
* - Gains et pertes via les Instincts et les Interdits (1D ou 2D selon la
|
|
* gravité), avec des limites (3 Dés du même Totem, 5 Dés au maximum).
|
|
*/
|
|
export class VermineTotemDice {
|
|
|
|
/**
|
|
* Totem dominant d'un personnage.
|
|
* @param {foundry.abstract.Document} actor
|
|
* @returns {'human'|'adapted'|'neutral'}
|
|
*/
|
|
static getInfluence(actor) {
|
|
const human = actor.system?.adaptation?.totems?.human?.value || 0;
|
|
const adapted = actor.system?.adaptation?.totems?.adapted?.value || 0;
|
|
if (human > adapted) return "human";
|
|
if (adapted > human) return "adapted";
|
|
return "neutral";
|
|
}
|
|
|
|
/**
|
|
* Modificateur (Bonus/Malus) d'influence pour une catégorie de compétence.
|
|
* @param {foundry.abstract.Document} actor
|
|
* @param {string} skillCategory catégorie (man, animal, tool, weapon, survival, world)
|
|
* @returns {number} -1, 0 ou +1
|
|
*/
|
|
static influenceModifier(actor, skillCategory) {
|
|
const influence = this.getInfluence(actor);
|
|
if (influence === "neutral" || !skillCategory) return 0;
|
|
const cfg = CONFIG.VERMINE?.totemInfluence?.[influence];
|
|
if (!cfg) return 0;
|
|
if ((cfg.bonus || []).includes(skillCategory)) return 1;
|
|
if ((cfg.malus || []).includes(skillCategory)) return -1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Applique un gain d'un Dé de Totem selon les règles d'échange et de limites.
|
|
* Retourne un objet décrivant ce qui s'est passé (pour notification).
|
|
* @param {foundry.abstract.Document} actor
|
|
* @param {'human'|'adapted'} totem
|
|
* @param {number} [count=1] 1 ou 2 dés (acte grave)
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
static async gainDie(actor, totem, count = 1) {
|
|
const human = actor.system.adaptation.totems.human;
|
|
const adapted = actor.system.adaptation.totems.adapted;
|
|
const actions = [];
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const step = this._gainOneDie(human.value, adapted.value, totem);
|
|
if (step.unchanged) {
|
|
actions.push({ ok: false, reason: "max" });
|
|
break;
|
|
}
|
|
human.value = step.human;
|
|
adapted.value = step.adapted;
|
|
actions.push({ ok: true, exchanged: step.exchanged, replaced: step.replaced });
|
|
}
|
|
|
|
await actor.update({
|
|
"system.adaptation.totems.human.value": human.value,
|
|
"system.adaptation.totems.adapted.value": adapted.value
|
|
});
|
|
|
|
return this._summarize(actions, totem);
|
|
}
|
|
|
|
/**
|
|
* Applique une perte d'un Dé de Totem.
|
|
* Règle : seul l'Humain a des Interdits. Briser un Interdit Humain fait
|
|
* perdre 1D Humain ; si le personnage n'en possède pas, il gagne 1D Adapté.
|
|
* @param {foundry.abstract.Document} actor
|
|
* @param {'human'} totem
|
|
* @param {number} [count=1] 1 ou 2 dés (acte grave)
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
static async loseDie(actor, totem, count = 1) {
|
|
const human = actor.system.adaptation.totems.human;
|
|
const adapted = actor.system.adaptation.totems.adapted;
|
|
const actions = [];
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
if (human.value > 0) {
|
|
human.value -= 1;
|
|
actions.push({ ok: true, lost: true });
|
|
} else if (adapted.value < adapted.max && (human.value + adapted.value) < 5) {
|
|
// Pas de Dé Humain : gagne un Dé Adapté à la place (limites appliquées).
|
|
adapted.value += 1;
|
|
actions.push({ ok: true, gainedAdapted: true });
|
|
} else {
|
|
actions.push({ ok: false, reason: "max" });
|
|
break;
|
|
}
|
|
}
|
|
|
|
await actor.update({
|
|
"system.adaptation.totems.human.value": human.value,
|
|
"system.adaptation.totems.adapted.value": adapted.value
|
|
});
|
|
|
|
return this._summarize(actions, totem);
|
|
}
|
|
|
|
/**
|
|
* Ajoute un Dé du Totem donné en respectant les limites et les échanges.
|
|
* Règles :
|
|
* - 3 Dés max du même Totem, 5 Dés max au total.
|
|
* - Si le joueur gagne un Dé qu'il ne peut conserver, il peut supprimer un
|
|
* Dé du Totem opposé à la place ; sinon le gain est annulé.
|
|
* - Gagner un 4e Dé Adapté est impossible, mais supprime un Dé Humain.
|
|
* @param {number} human
|
|
* @param {number} adapted
|
|
* @param {'human'|'adapted'} totem
|
|
* @returns {Object}
|
|
*/
|
|
static _gainOneDie(human, adapted, totem) {
|
|
const limitTotem = 3;
|
|
const opposite = totem === "human" ? "adapted" : "human";
|
|
const total = human + adapted;
|
|
const current = totem === "human" ? human : adapted;
|
|
const oppositeVal = totem === "human" ? adapted : human;
|
|
|
|
// Le Totem cible est déjà au maximum (3) : impossible d'empiler.
|
|
if (current >= limitTotem) {
|
|
if (oppositeVal > 0) {
|
|
// Supprime un Dé du Totem opposé à la place (le gain n'augmente pas).
|
|
const result = { human, adapted, unchanged: false, replaced: true };
|
|
result[opposite] -= 1;
|
|
return result;
|
|
}
|
|
return { human, adapted, unchanged: true };
|
|
}
|
|
|
|
// Limite totale atteinte (5) : peut remplacer un Dé opposé.
|
|
if (total >= 5) {
|
|
if (oppositeVal > 0) {
|
|
const result = { human, adapted, unchanged: false, exchanged: true };
|
|
result[totem] += 1;
|
|
result[opposite] -= 1;
|
|
return result;
|
|
}
|
|
return { human, adapted, unchanged: true };
|
|
}
|
|
|
|
// Gain normal.
|
|
const result = { human, adapted, unchanged: false };
|
|
result[totem] += 1;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Résume les actions appliquées en un libellé localisé.
|
|
* @param {Array} actions
|
|
* @param {'human'|'adapted'} totem
|
|
* @returns {Object}
|
|
*/
|
|
static _summarize(actions, totem) {
|
|
const gained = actions.filter(a => a.ok && !a.exchanged && !a.replaced && !a.lost && !a.gainedAdapted).length;
|
|
const exchanged = actions.filter(a => a.exchanged).length;
|
|
const replaced = actions.filter(a => a.replaced).length;
|
|
const lost = actions.filter(a => a.lost).length;
|
|
const gainedAdapted = actions.filter(a => a.gainedAdapted).length;
|
|
const refused = actions.some(a => !a.ok);
|
|
return { totem, gained, exchanged, replaced, lost, gainedAdapted, refused };
|
|
}
|
|
}
|