209 lines
6.5 KiB
JavaScript
209 lines
6.5 KiB
JavaScript
import { VermineUtils } from "../roll.mjs"
|
|
|
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
|
|
|
/**
|
|
* Dialogue de jet simplifié pour les PNJ.
|
|
*
|
|
* Un PNJ n'a ni caractéristiques ni compétences : toutes ses actions sont
|
|
* résolues par une valeur unique dérivée de son Profil (Menace / Expérience /
|
|
* Rôle), exposée dans system.computed. Ce dialogue fixe le pool selon le type
|
|
* de jet (Action, Spécialité, Attaque ou Réaction) et n'offre que la difficulté,
|
|
* le handicap et les bonus simples.
|
|
*
|
|
* Utilisé par :
|
|
* - la fiche PNJ (boutons de jet rapide Action / Spécialité / Réaction) ;
|
|
* - la défense des PNJ dans les échanges de coups (mode verrouillé).
|
|
*/
|
|
export default class NpcRollDialog 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: NpcRollDialog.#onRoll,
|
|
cancel: NpcRollDialog.#onCancel
|
|
}
|
|
}
|
|
|
|
static PARTS = {
|
|
main: { template: "systems/vermine2047/templates/dialogs/npc-roll-dialog.hbs" }
|
|
}
|
|
|
|
static async create(data = {}) {
|
|
const actor = data.actor instanceof Actor ? data.actor : await game.actors.get(data.actorId)
|
|
if (!actor) {
|
|
ui.notifications.warn(game.i18n.localize("VERMINE.error_no_actor_selected"))
|
|
return null
|
|
}
|
|
return new NpcRollDialog({
|
|
actor,
|
|
rolltype: data.rolltype ?? "action",
|
|
locks: data.locks ?? null,
|
|
defense: data.defense ?? null
|
|
})
|
|
}
|
|
|
|
constructor(options = {}) {
|
|
super(options)
|
|
this.#actor = options.actor
|
|
this.rolltype = options.rolltype ?? "action"
|
|
this.locks = options.locks ?? null
|
|
this.defense = options.defense ?? null
|
|
}
|
|
|
|
// ── Getters ──────────────────────────────────────────────────────────
|
|
|
|
get computed() { return this.#actor.system?.computed || {} }
|
|
|
|
/** Pool de dés selon le type de jet. */
|
|
get pool() {
|
|
const c = this.computed
|
|
switch (this.rolltype) {
|
|
case "specialty": return c.specialties || 0
|
|
case "attack": return c.attack || 0
|
|
case "reaction": return c.reaction || 0
|
|
default: return c.action || 3
|
|
}
|
|
}
|
|
|
|
/** Réussites automatiques (bonus de Réaction du rôle). */
|
|
get freeSuccesses() {
|
|
return this.rolltype === "reaction" ? (this.computed.reactionBonus || 0) : 0
|
|
}
|
|
|
|
/** Relances : utilisables uniquement sur les attaques ou les Spécialités. */
|
|
get rerolls() {
|
|
if (this.rolltype !== "specialty" && this.rolltype !== "attack") return 0
|
|
return this.computed.rerolls || 0
|
|
}
|
|
|
|
get label() {
|
|
const keys = {
|
|
action: "ADVERSITY.action",
|
|
specialty: "ADVERSITY.specialties",
|
|
attack: "ADVERSITY.attack",
|
|
reaction: "ADVERSITY.reaction"
|
|
}
|
|
return game.i18n.localize(keys[this.rolltype] || "VERMINE.roll")
|
|
}
|
|
|
|
// ── Rendu ────────────────────────────────────────────────────────────
|
|
|
|
async _prepareContext() {
|
|
const difficultyOptions = []
|
|
for (let d = 3; d <= 10; d++) {
|
|
difficultyOptions.push({ difficulty: d, label: String(d) })
|
|
}
|
|
let lockedDifficulty = null
|
|
let defaultDifficulty = 7
|
|
if (this.locks?.difficulty !== undefined && this.locks?.difficulty !== null) {
|
|
lockedDifficulty = parseInt(this.locks.difficulty, 10)
|
|
defaultDifficulty = lockedDifficulty
|
|
}
|
|
return {
|
|
actor: this.#actor,
|
|
system: this.#actor.system,
|
|
config: CONFIG.VERMINE,
|
|
rollLabel: this.label,
|
|
rolltype: this.rolltype,
|
|
pool: this.pool,
|
|
freeSuccesses: this.freeSuccesses,
|
|
rerolls: this.rerolls,
|
|
isDefense: Boolean(this.defense),
|
|
lockedDifficulty,
|
|
difficultyOptions,
|
|
defaultDifficulty,
|
|
speakerId: this.#actor.id,
|
|
availableItems: this.#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))
|
|
}
|
|
this.element.querySelector("#handicap")?.addEventListener("change", () => this.#updateUI())
|
|
this.#updateUI()
|
|
}
|
|
|
|
get #el() { return this.element }
|
|
|
|
#getHandicap() {
|
|
const sel = this.#el.querySelector("#handicap")
|
|
return Math.max(0, (parseInt(sel?.value, 10) || 1) - 1)
|
|
}
|
|
|
|
#getDifficulty() {
|
|
const sel = this.#el.querySelector("#difficulty")
|
|
return parseInt(sel?.value, 10) || 7
|
|
}
|
|
|
|
#getHelped() { return this.#el.querySelector("#helped")?.checked ? 1 : 0 }
|
|
|
|
#getGroup() { return parseInt(this.#el.querySelector("#group")?.value, 10) || 0 }
|
|
|
|
getPoolBreakdown() {
|
|
const help = this.#getHelped()
|
|
const group = this.#getGroup()
|
|
return { pool: this.pool, help, group, total: this.pool + help + group }
|
|
}
|
|
|
|
#updateUI() {
|
|
const b = this.getPoolBreakdown()
|
|
const totalEl = this.#el.querySelector("#dice-pool-total")
|
|
if (totalEl) totalEl.textContent = `${b.total}D`
|
|
const bonusEl = this.#el.querySelector("#total-bonus")
|
|
if (bonusEl) bonusEl.textContent = b.help + b.group
|
|
const freeEl = this.#el.querySelector("#free-successes")
|
|
if (freeEl) freeEl.textContent = String(this.freeSuccesses)
|
|
}
|
|
|
|
#onInputChange() {
|
|
this.#updateUI()
|
|
}
|
|
|
|
// ── Actions ──────────────────────────────────────────────────────────
|
|
|
|
static async #onCancel(event, target) {
|
|
this.close()
|
|
}
|
|
|
|
static async #onRoll(event, target) {
|
|
const label = this.defense
|
|
? (this.defense.type === "parade"
|
|
? game.i18n.localize("VERMINE.defense_parade")
|
|
: game.i18n.localize("VERMINE.defense_esquive"))
|
|
: this.label
|
|
|
|
await VermineUtils.roll({
|
|
actor: this.#actor,
|
|
NoD: this.getPoolBreakdown().total,
|
|
Reroll: this.rerolls,
|
|
difficulty: this.#getDifficulty(),
|
|
handicap: this.#getHandicap(),
|
|
rollLabel: label,
|
|
bonusSuccesses: this.freeSuccesses,
|
|
poolBreakdown: this.getPoolBreakdown(),
|
|
messageFlags: this.defense ? { "vermine-defense": this.defense } : null
|
|
})
|
|
|
|
this.close()
|
|
}
|
|
}
|