Files
vermine2047/module/system/dialogs/experiencePhaseDialog.mjs
T
uberwald 0ef0de45ff
Release Creation / build (release) Failing after 1m18s
fix(experience): phase ciblée (groupe/sans groupe/individuel) + dépense des Dés d'Évolution
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)
2026-08-08 18:35:01 +02:00

199 lines
8.3 KiB
JavaScript

const { HandlebarsApplicationMixin } = foundry.applications.api;
/**
* Dialogue du meneur pour ouvrir une phase d'Expérience.
* Permet d'attribuer les Dés d'Expérience collectifs (Danger, Découvertes,
* Durée) au Groupe et aux personnages, ainsi que les Dés individuels
* (Implication, Influence, Interprétation), les récompenses d'Objectifs et
* la récompense de Traumatisme choisi.
*/
export default class ExperiencePhaseDialog extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {
static DEFAULT_OPTIONS = {
classes: ["vermine-roll"],
tag: "form",
window: {
icon: "fas fa-star",
resizable: true
},
position: {
width: 620,
height: 700
},
actions: {
apply: ExperiencePhaseDialog.#onApply,
cancel: ExperiencePhaseDialog.#onCancel
}
};
static PARTS = {
main: { template: "systems/vermine2047/templates/dialogs/experience-phase-dialog.hbs", scrollable: [""] }
};
static async create() {
return new ExperiencePhaseDialog();
}
get title() {
return game.i18n.localize("VERMINE.experience_phase");
}
async _prepareContext() {
const characters = game.actors.filter(a => a.type === "character");
const groups = game.actors.filter(a => a.type === "group");
// Associe chaque personnage à son premier groupe (même logique que RollDialog.#findGroup)
const memberOf = new Map();
for (const g of groups) {
for (const id of g.system?.members || []) {
if (!memberOf.has(id)) memberOf.set(id, g.id);
}
}
const groupById = new Map(groups.map(g => [g.id, g]));
return {
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,
dice: a.system?.experience?.dice || 0
})),
rewards: [
{ value: 0, label: game.i18n.localize("VERMINE.reward_anecdotic") },
{ value: 1, label: game.i18n.localize("VERMINE.reward_minor") },
{ value: 2, label: game.i18n.localize("VERMINE.reward_major") },
{ value: 3, label: game.i18n.localize("VERMINE.reward_exceptionnal") }
]
};
}
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();
}
#updateUI() {
const danger = parseInt(this.element.querySelector("#collective-danger")?.value, 10) || 0;
const discoveries = parseInt(this.element.querySelector("#collective-discoveries")?.value, 10) || 0;
const duration = parseInt(this.element.querySelector("#collective-duration")?.value, 10) || 0;
const el = this.element.querySelector("#collective-total");
if (el) el.textContent = `${danger + discoveries + duration}D`;
}
/** 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 rowGroupId = row.dataset.groupId || "";
const show = groupId === ""
? true
: groupId === "none"
? rowGroupId === ""
: rowGroupId === groupId;
row.hidden = !show;
}
}
static async #onCancel(event, target) {
this.close();
}
static async #onApply(event, target) {
const collective = (parseInt(this.element.querySelector("#collective-danger")?.value, 10) || 0)
+ (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 || "";
if (groupId && groupId !== "none") {
const group = game.actors.get(groupId);
if (group && collective > 0) {
const current = group.system?.experience?.dice || 0;
const phase = (group.system?.experience?.phase || 0) + 1;
await group.update({
"system.experience.dice": current + collective,
"system.experience.phase": phase,
"system.experience.learned.phase": phase,
"system.experience.learned.level": false,
"system.experience.learned.reserve": false,
"system.experience.learned.ability": false
});
}
}
const results = [];
for (const row of this.element.querySelectorAll("[data-character-id]")) {
// 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;
const implication = parseInt(row.querySelector(`[name="implication-${id}"]`)?.value, 10) || 0;
const influence = parseInt(row.querySelector(`[name="influence-${id}"]`)?.value, 10) || 0;
const interpretation = parseInt(row.querySelector(`[name="interpretation-${id}"]`)?.value, 10) || 0;
const objectiveMinor = row.querySelector(`[name="objective-minor-${id}"]`)?.checked ? 3 : 0;
const objectiveMajor = row.querySelector(`[name="objective-major-${id}"]`)?.checked ? 5 : 0;
const trauma = row.querySelector(`[name="trauma-${id}"]`)?.checked ? 5 : 0;
const individual = implication + influence + interpretation;
const bonus = objectiveMinor + objectiveMajor + trauma;
const gained = collective + individual + bonus;
const ex = actor.system?.experience;
const nextPhase = (ex?.phase || 0) + 1;
const update = {
"system.experience.phase": nextPhase,
"system.experience.dice": (ex?.dice || 0) + gained,
"system.experience.collective.danger": parseInt(this.element.querySelector("#collective-danger")?.value, 10) || 0,
"system.experience.collective.discoveries": parseInt(this.element.querySelector("#collective-discoveries")?.value, 10) || 0,
"system.experience.collective.duration": parseInt(this.element.querySelector("#collective-duration")?.value, 10) || 0,
"system.experience.individual.implication": implication,
"system.experience.individual.influence": influence,
"system.experience.individual.interpretation": interpretation,
"system.experience.traumaReward": trauma > 0,
"system.experience.learned.phase": nextPhase,
"system.experience.learned.skill": false,
"system.experience.learned.specialty": false,
"system.experience.learned.reserve": false,
"system.experience.learned.characteristic": false,
"system.experience.learned.evolution": false,
"system.experience.learned.ability": false,
"system.experience.learned.background": false,
"system.experience.learned.reputation": false
};
await actor.update(update);
results.push({ name: actor.name, gained });
}
const lines = [`${game.i18n.localize("VERMINE.experience_phase")} +1`];
if (groupId && groupId !== "none") {
const group = game.actors.get(groupId);
lines.push(`${group?.name ?? ""} (${game.i18n.localize("VERMINE.collective")}): +${collective}D`);
}
for (const r of results) {
lines.push(`${r.name}: +${r.gained}D`);
}
await ChatMessage.create({
user: game.user?._id,
speaker: ChatMessage.getSpeaker(),
content: `<div class="vermine-roll-message"><h3>${game.i18n.localize("VERMINE.experience_rewards")}</h3><div class="roll-context flexrow flex-wrap">${lines.map(l => `<span class="context-item">${l}</span>`).join("")}</div></div>`
});
this.close();
}
}