- Dialogue d'apprentissage personnage : 4 nouveaux types d'achat.
- Capacité de Totem : options filtrées (mode de jeu, niveau de
groupe, totem, non-apprises), difficulté = valeur d'Apprentissage,
limites 3×N1 / 1×N2-N3 / max 5, marque system.learned de l'item
(convention type character) et learned.ability du personnage.
- Élément d'Historique : difficulté 9, handicap 1 si astérisque
(background.asterisk), marque learned.background + item.
- Test de Réputation (réduire/augmenter) : réduire = difficulté
10 − score visé (−1 pt), augmenter = difficulté = réputation
actuelle (+1 par succès), bornes 0-10, marque learned.reputation.
- Évolution : option Dé de Totem Adapté ×2 quand Adapté > Humain
(1D remplacé par un dé adapté compté ×2 via VermineUtils.roll).
- Correction règles : les Dés d'Expérience sont dépensés même en cas
d'échec du jet d'apprentissage (décompte hors #applySuccess).
- Expérience de Groupe : nouveau dialogue d'apprentissage de Groupe
(niveau, réserve, capacité de totem), réserve d'expérience et phase
dans system.experience, boutons sur la fiche Groupe, reset des
limites par phase (level/reserve/ability).
- Phase d'expérience : filtre par groupe sélectionné, associe chaque
personnage à son premier groupe, reset des limites de tous les
nouveaux types d'apprentissage.
- Corrections : l'attaque de créature n'ajoute plus la Réaction du Rôle
(regles_creatures.txt), suppression de la case XP des en-têtes PJ
(legacy et AppV2).
- Divers : .eslintrc.js (règles inexistantes/obsolètes retirées),
libellé jaune de dialogue en couleur chitine, i18n FR/EN, chips
« appris » sur l'onglet expérience du personnage.
183 lines
7.5 KiB
JavaScript
183 lines
7.5 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);
|
|
}
|
|
}
|
|
return {
|
|
characters: characters.map(a => ({
|
|
id: a.id,
|
|
name: a.name,
|
|
dice: a.system?.experience?.dice || 0,
|
|
groupId: memberOf.get(a.id) || ""
|
|
})),
|
|
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.#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). */
|
|
#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;
|
|
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 || null;
|
|
if (groupId) {
|
|
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]")) {
|
|
// Quand un groupe précis est choisi, ne récompenser que ses membres.
|
|
if (groupId && row.dataset.groupId !== groupId) 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) {
|
|
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();
|
|
}
|
|
}
|