fix(experience): phase ciblée (groupe/sans groupe/individuel) + dépense des Dés d'Évolution
Release Creation / build (release) Failing after 1m18s

Phase d'Expérience :
- sélecteur de groupe : « Tous les personnages », « Sans groupe » ou un groupe précis → seuls ses membres sont récompensés
- case « Inclure dans cette phase » par personnage pour exclure de la phase
- chaque ligne affiche son groupe ([Groupe] / [Sans groupe])
- les collectifs ne sont versés au groupe que si un vrai groupe est choisi

Dés d'Évolution :
- achat : ne consomme que le coût du niveau (le reste du stock est conservé) au lieu d'effacer tout
- total « après achat » recalculé en direct selon le niveau sélectionné
- libellés d'options nettoyés (1D au lieu de 1D — 1D)
This commit is contained in:
2026-08-08 18:35:01 +02:00
parent c7e6660d9d
commit 0ef0de45ff
6 changed files with 64 additions and 23 deletions
+17 -5
View File
@@ -5,8 +5,8 @@ const { HandlebarsApplicationMixin } = foundry.applications.api;
/**
* Dialogue d'achat d'une Adaptation/Mutation avec les Dés d'Évolution
* accumulés. Vérifie le coût (niveau 1-4), la limite du Mode de jeu et les
* restrictions sur les Mutations ; en cas de succès, crée l'item et efface
* les Dés d'Évolution accumulés.
* restrictions sur les Mutations ; en cas de succès, crée l'item et consomme
* un nombre de Dés d'Évolution égal au niveau choisi.
*/
export default class BuyEvolutionDialog extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {
@@ -61,7 +61,7 @@ export default class BuyEvolutionDialog extends HandlebarsApplicationMixin(found
};
const levels = [];
for (let value = 1; value <= 4; value++) {
levels.push({ value, cost: value });
levels.push({ value });
}
return {
evolutionDice: actor.system?.experience?.evolution || 0,
@@ -73,6 +73,18 @@ export default class BuyEvolutionDialog extends HandlebarsApplicationMixin(found
};
}
async _onRender() {
this.element.querySelectorAll("[data-roll]").forEach(inp => inp.addEventListener("change", () => this.#updateUI()));
this.#updateUI();
}
/** Met à jour le total des niveaux « après achat » selon le niveau sélectionné. */
#updateUI() {
const cost = parseInt(this.element.querySelector("#evolution-level")?.value, 10) || 0;
const el = this.element.querySelector("#evolution-total-value");
if (el) el.textContent = `${VermineExperience.evolutionTotalLevels(this.#actor) + cost}D`;
}
static async #onCancel(event, target) {
this.close();
}
@@ -102,12 +114,12 @@ export default class BuyEvolutionDialog extends HandlebarsApplicationMixin(found
mutation
}
}]);
// N'efface les Dés d'Évolution que si l'item a bien été créé.
// Ne consomme que le coût du niveau choisi (Règles p. 116-117 : coût = niveau).
if (!created || created.length === 0) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_evolution_create"));
return;
}
await actor.update({ "system.experience.evolution": 0 });
await actor.update({ "system.experience.evolution": Math.max(0, (actor.system?.experience?.evolution || 0) - cost) });
ui.notifications.info(game.i18n.localize("VERMINE.buy_evolution_done"));
this.close();
+29 -13
View File
@@ -48,13 +48,18 @@ export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(fo
if (!memberOf.has(id)) memberOf.set(id, g.id);
}
}
const groupById = new Map(groups.map(g => [g.id, g]));
return {
characters: characters.map(a => ({
id: a.id,
name: a.name,
dice: a.system?.experience?.dice || 0,
groupId: memberOf.get(a.id) || ""
})),
characters: characters.map(a => {
const groupId = memberOf.get(a.id) || "";
return {
id: a.id,
name: a.name,
dice: a.system?.experience?.dice || 0,
groupId,
groupName: groupId ? groupById.get(groupId)?.name : ""
};
}),
groups: groups.map(a => ({
id: a.id,
name: a.name,
@@ -72,6 +77,9 @@ export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(fo
async _onRender(context, options) {
this.element.querySelectorAll("[data-roll]").forEach(inp => inp.addEventListener("change", () => this.#updateUI()));
this.element.querySelector("#group-select")?.addEventListener("change", () => this.#updateGroupFilter());
this.element.querySelectorAll("[name^='include-']").forEach(cb =>
cb.addEventListener("click", event => event.stopPropagation())
);
this.#updateUI();
this.#updateGroupFilter();
}
@@ -84,11 +92,16 @@ export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(fo
if (el) el.textContent = `${danger + discoveries + duration}D`;
}
/** Filtre les personnages selon le groupe sélectionné (vide = tous). */
/** Filtre les personnages selon le groupe sélectionné (vide = tous, "none" = sans groupe). */
#updateGroupFilter() {
const groupId = this.element.querySelector("#group-select")?.value || "";
for (const row of this.element.querySelectorAll("[data-character-id]")) {
const show = !groupId || row.dataset.groupId === groupId;
const rowGroupId = row.dataset.groupId || "";
const show = groupId === ""
? true
: groupId === "none"
? rowGroupId === ""
: rowGroupId === groupId;
row.hidden = !show;
}
}
@@ -102,8 +115,8 @@ export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(fo
+ (parseInt(this.element.querySelector("#collective-discoveries")?.value, 10) || 0)
+ (parseInt(this.element.querySelector("#collective-duration")?.value, 10) || 0);
const groupId = this.element.querySelector("#group-select")?.value || null;
if (groupId) {
const groupId = this.element.querySelector("#group-select")?.value || "";
if (groupId && groupId !== "none") {
const group = game.actors.get(groupId);
if (group && collective > 0) {
const current = group.system?.experience?.dice || 0;
@@ -121,8 +134,11 @@ export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(fo
const results = [];
for (const row of this.element.querySelectorAll("[data-character-id]")) {
// Quand un groupe précis est choisi, ne récompenser que ses membres.
if (groupId && row.dataset.groupId !== groupId) continue;
// Filtre par groupe sélectionné (vide = tous, "none" = sans groupe)
const rowGroupId = row.dataset.groupId || "";
if (groupId === "none" ? rowGroupId !== "" : groupId !== "" && rowGroupId !== groupId) continue;
// Case « Inclure » décochée → personnage exclu de cette phase
if (!row.querySelector(`[name="include-${row.dataset.characterId}"]`)?.checked) continue;
const id = row.dataset.characterId;
const actor = game.actors.get(id);
if (!actor) continue;
@@ -164,7 +180,7 @@ export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(fo
}
const lines = [`${game.i18n.localize("VERMINE.experience_phase")} +1`];
if (groupId) {
if (groupId && groupId !== "none") {
const group = game.actors.get(groupId);
lines.push(`${group?.name ?? ""} (${game.i18n.localize("VERMINE.collective")}): +${collective}D`);
}