Files
vermine2047/module/system/dialogs/rollDialog.mjs
T
2026-06-06 10:21:24 +02:00

328 lines
11 KiB
JavaScript

import { VermineUtils } from "../roll.mjs";
const { HandlebarsApplicationMixin } = foundry.applications.api;
export default class RollDialog extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {
#actor;
get title() {
return game.i18n.localize("VERMINE.roll");
}
static DEFAULT_OPTIONS = {
classes: ["vermine-roll"],
tag: "form",
window: {
icon: "fas fa-dice-d10",
resizable: false
},
position: {
width: 520,
height: 600
},
actions: {
roll: RollDialog.#onRoll,
cancel: RollDialog.#onCancel
}
};
static PARTS = {
main: { template: "systems/vermine2047/templates/dialogs/roll-dialog.hbs" }
};
static async create(data = {}) {
const actorId = data.actorId ?? game.user.character?.id ?? canvas.tokens.controlled[0]?.actor?.id;
if (!actorId || typeof actorId !== "string") {
ui.notifications.warn(game.i18n.localize("VERMINE.error_no_actor_selected"));
return null;
}
const actor = await game.actors.get(actorId);
if (!actor) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_no_actor_selected"));
return null;
}
return new RollDialog({ actor, label: data.label, rolltype: data.rolltype });
}
constructor(options = {}) {
super(options);
this.#actor = options.actor;
this.label = options.label ?? null;
this.rolltype = options.rolltype ?? null;
}
async _prepareContext() {
const actor = this.#actor;
return {
actor,
system: actor.system,
config: CONFIG.VERMINE,
label: this.label,
rollType: this.rolltype,
labelKey: this.label,
speakerId: actor.id,
ability: null,
help: false,
specialty: false,
availableSpecialties: actor.items.filter(i => i.type === "specialty"),
availableItems: actor.items.filter(i => i.type === "item")
};
}
async _onRender(context, options) {
this.element.dataset.actorId = this.#actor.id;
for (const inp of this.element.querySelectorAll("[data-roll]")) {
inp.addEventListener("change", this.#onInputChange.bind(this));
}
const ability = this.element.querySelector("#ability");
if (ability) {
ability.addEventListener("change", this.#onChangeAbility.bind(this));
const selfControl = this.element.querySelector("#self_control");
if (selfControl) selfControl.max = ability.value;
}
const selfControl = this.element.querySelector("#self_control");
if (selfControl) {
selfControl.addEventListener("change", this.#onChangeSelfControl.bind(this));
}
this.element.querySelector("#difficulty")?.addEventListener("change", () => this.#updateUI());
this.element.querySelector("#handicap")?.addEventListener("change", () => this.#updateUI());
this.element.querySelector("#human-totem")?.addEventListener("change", () => this.#updateUI());
this.element.querySelector("#adapted-totem")?.addEventListener("change", () => this.#updateUI());
this.#displaySpecialties();
this.#updateUI();
if (ability?.value !== "0") {
this.element.querySelector("#self_control")?.dispatchEvent(new Event("change"));
}
}
// ── Getters ──────────────────────────────────────────────────────────
get #el() { return this.element; }
#getAbility() { return this.#el.querySelector("#ability"); }
#getSkill() { return this.#el.querySelector("#skill"); }
#getDifficulty() { return this.#el.querySelector("#difficulty"); }
#getHandicap() { return this.#el.querySelector("#handicap"); }
#getSelfCtrl() { return this.#el.querySelector("#self_control"); }
getDicePool() {
const abil = this.#getAbility();
const abilVal = parseInt(abil?.options[abil?.selectedIndex]?.value, 10) || 0;
const skill = this.#getSkill();
const skillPool = parseInt(skill?.options[skill?.selectedIndex]?.dataset?.pool, 10) || 0;
const sc = parseInt(this.#getSelfCtrl()?.value, 10) || 0;
const specChecked = this.#el.querySelector("#usingSpecialization")?.checked;
const helped = this.#el.querySelector("#helped")?.checked;
const tools = this.#el.querySelector("input[name='usingTools']:checked")?.value !== "0";
const bonuses = (specChecked ? 1 : 0) + (helped ? 1 : 0) + (tools ? 1 : 0);
return (abilVal + sc + skillPool + bonuses) || 0;
}
getDifficultySelect() {
const sel = this.#getDifficulty();
const idx = sel?.selectedIndex ?? 0;
return parseInt(sel?.options[idx]?.value, 10) || 7;
}
getReroll() {
const sel = this.#getSkill();
const idx = sel?.selectedIndex ?? 0;
return parseInt(sel?.options[idx]?.dataset?.reroll, 10) || 0;
}
getHandicapSelect() {
const sel = this.#getHandicap();
return parseInt(sel?.value, 10) || 1;
}
getSkillCategory() {
const sel = this.#getSkill();
const idx = sel?.selectedIndex ?? 0;
return sel?.options[idx]?.dataset?.category ?? null;
}
getSkillLevel() {
const sel = this.#getSkill();
const idx = sel?.selectedIndex ?? 0;
const val = sel?.options[idx]?.value;
return val ? parseInt(val, 10) : null;
}
hasSpecialtySelected() {
const checked = this.#el.querySelector("input[name='usingSpecialization']:checked");
return checked && checked.value !== "aucune";
}
getRollType() {
const sel = this.#getSkill();
return sel?.value ? "skill" : "ability";
}
getLabel() {
const type = this.getRollType();
if (type === "skill") {
const sel = this.#getSkill();
const idx = sel?.selectedIndex ?? 0;
return sel?.options[idx]?.dataset?.label ?? "";
}
const sel = this.#getAbility();
const idx = sel?.selectedIndex ?? 0;
return sel?.options[idx]?.dataset?.label ?? "";
}
getSelfControl() {
return parseInt(this.#getSelfCtrl()?.value, 10) || 0;
}
getMaxEffort() {
const sel = this.#getAbility();
return parseInt(sel?.value, 10) || 0;
}
getTotems() {
return {
human: this.#el.querySelector("#human-totem")?.checked ?? false,
adapted: this.#el.querySelector("#adapted-totem")?.checked ?? false
};
}
getKeepTotem() {
return this.#el.querySelector("#keep-totem-select")?.value ?? null;
}
// ── UI ───────────────────────────────────────────────────────────────
#displaySpecialties() {
for (const el of this.#el.querySelectorAll("[data-spec-skill]")) {
el.style.display = "inline";
}
}
#calculateBonusCount() {
let b = 0;
if (this.#el.querySelector("#helped")?.checked) b += 1;
b += parseInt(this.#el.querySelector("#group")?.value, 10) || 0;
b += parseInt(this.#getSelfCtrl()?.value, 10) || 0;
const tools = this.#el.querySelector("input[name='usingTools']:checked");
if (tools && tools.value !== "0") b += 1;
const human = this.#el.querySelector("#human-totem");
if (human?.checked) b += parseInt(this.#actor?.system?.adaptation?.totems?.human?.value, 10) || 0;
const adapted = this.#el.querySelector("#adapted-totem");
if (adapted?.checked) b += parseInt(this.#actor?.system?.adaptation?.totems?.adapted?.value, 10) || 0;
if (this.hasSpecialtySelected()) b += 1;
return b;
}
#updateUI() {
const total = this.getDicePool();
const totalEl = this.#el.querySelector("#dice-pool-total");
if (totalEl) totalEl.textContent = `${total}D`;
const bonusEl = this.#el.querySelector("#total-bonus");
if (bonusEl) bonusEl.textContent = this.#calculateBonusCount();
const diffSel = this.#getDifficulty();
const diffEl = this.#el.querySelector("#current-difficulty");
if (diffEl && diffSel) {
const idx = diffSel.selectedIndex;
const val = diffSel.options[idx].value;
const lbl = diffSel.options[idx].text.split(" ")[0];
diffEl.textContent = `${lbl} (${val})`;
}
const handSel = this.#getHandicap();
const handEl = this.#el.querySelector("#current-handicap");
if (handEl && handSel) {
handEl.textContent = handSel.options[handSel.selectedIndex].text;
}
const abilSel = this.#getAbility();
const abilValEl = this.#el.querySelector("#abilityScoreValue");
if (abilSel && abilValEl) {
const idx = abilSel.selectedIndex;
abilValEl.textContent = idx > 0 ? abilSel.options[idx].value : "0";
}
const specChecked = this.#el.querySelector("input[name='usingSpecialization']:checked");
const specEl = this.#el.querySelector(".current-specialty");
if (specEl && specChecked) {
specEl.textContent = specChecked.value === "aucune"
? game.i18n.localize("VERMINE.none")
: specChecked.value;
}
}
// ── Event handlers ───────────────────────────────────────────────────
#onInputChange() {
this.#updateUI();
}
#onChangeAbility(ev) {
const sel = ev.currentTarget;
const score = sel.options[sel.selectedIndex]?.value ?? "0";
const scoreEl = this.#el.querySelector("#abilityScore");
if (scoreEl) scoreEl.value = score;
const sc = this.#getSelfCtrl();
if (sc) sc.max = score;
this.#updateUI();
}
#onChangeSelfControl(ev) {
const valEl = this.#el.querySelector("#self_control_value");
if (valEl) valEl.textContent = ev.currentTarget.value;
}
static async #onCancel(event, target) {
this.close();
}
static async #onRoll(event, target) {
const selfCtrl = this.getSelfControl();
if (selfCtrl > 0) {
const current = this.#actor?.system?.attributes?.self_control?.value ?? 0;
if (current < selfCtrl) {
ui.notifications.warn(game.i18n.localize("VERMINE.error_not_enough_self_control"));
return;
}
}
const abilityVal = this.#el.querySelector('[name="ability"]')?.value;
if (!abilityVal || abilityVal === "0") {
ui.notifications.warn(game.i18n.localize("VERMINE.error_select_ability"));
return;
}
if (selfCtrl > 0) {
const newVal = this.#actor.system.attributes.self_control.value - selfCtrl;
await this.#actor.update({ "system.attributes.self_control.value": newVal });
}
await VermineUtils.roll({
actor: this.#actor,
NoD: this.getDicePool(),
Reroll: this.getReroll(),
difficulty: this.getDifficultySelect(),
handicap: this.getHandicapSelect(),
rollLabel: this.getLabel(),
totems: this.getTotems(),
self_control: selfCtrl,
max_effort: this.getMaxEffort(),
keepTotem: this.getKeepTotem(),
skillCategory: this.getSkillCategory(),
skillLevel: this.getSkillLevel(),
hasSpecialty: this.hasSpecialtySelected()
});
this.close();
}
}