328 lines
11 KiB
JavaScript
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();
|
|
}
|
|
|
|
}
|