Files
fvtt-hamalron/module/applications/action-complexe.mjs
T
uberwald 8a27211ff5 feat: actions complexes avec suivi de résistance
- Nouvelle fenêtre Actions complexes (GM-only)
- Création d'actions avec nom, symbole, résistance (nb de succès requis)
- Barre de progression + compteurs succès/échecs
- Boutons Réussite/Échec pour suivre l'avancement
- Notification et message chat quand l'objectif est atteint
- Persistance dans un setting world-scope
- Bouton dans les contrôles de scène (icône tasks)
- Accessible via game.system.api.openActionComplexe()

Conforme aux règles p.157 : actions nécessitant 2-5 victoires
2026-07-10 20:30:20 +02:00

201 lines
6.5 KiB
JavaScript

const { HandlebarsApplicationMixin } = foundry.applications.api
export default class HamalronActionComplexe extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {
constructor(options = {}) {
super(options)
HamalronActionComplexe.#instance = this
this.actions = []
this.#loadState()
}
static #instance
static get instance() {
if (!HamalronActionComplexe.#instance) {
HamalronActionComplexe.#instance = new HamalronActionComplexe()
}
return HamalronActionComplexe.#instance
}
static DEFAULT_OPTIONS = {
id: "action-complexe",
classes: ["fvtt-hamalron", "action-complexe"],
tag: "div",
window: {
title: "HAMALRON.ActionComplexe.Title",
icon: "fas fa-tasks",
resizable: true,
},
position: {
width: 500,
height: "auto",
},
actions: {
createAction: HamalronActionComplexe.#onCreateAction,
recordSuccess: HamalronActionComplexe.#onRecordSuccess,
recordFailure: HamalronActionComplexe.#onRecordFailure,
deleteAction: HamalronActionComplexe.#onDeleteAction,
resetAction: HamalronActionComplexe.#onResetAction,
},
}
static PARTS = {
main: {
template: "systems/fvtt-hamalron/templates/dialog-action-complexe.hbs",
},
}
async _prepareContext(options) {
const context = await super._prepareContext(options)
context.actions = this.actions.map(a => ({
...a,
pct: a.resistance > 0 ? Math.round((a.currentSuccesses / a.resistance) * 100) : 0,
isComplete: a.currentSuccesses >= a.resistance,
symboleIcon: HamalronActionComplexe.#symboleIcon(a.symbole),
}))
context.hasActions = context.actions.length > 0
return context
}
static #symboleIcon(symbole) {
const icons = { coupe: "fa-heart", epee: "fa-crossed-swords", denier: "fa-coins", baton: "fa-staff" }
return icons[symbole] || "fa-circle"
}
#loadState() {
const state = game.settings.get("fvtt-hamalron", "actionComplexeState")
if (state?.actions?.length) {
this.actions = state.actions
}
}
async #saveState() {
await game.settings.set("fvtt-hamalron", "actionComplexeState", { actions: this.actions })
}
static #onCreateAction(event, target) {
const self = this
const content = `
<div class="form-group">
<label>${game.i18n.localize("HAMALRON.ActionComplexe.Name")}</label>
<input type="text" id="ac-name" />
</div>
<div class="form-group">
<label>${game.i18n.localize("HAMALRON.ActionComplexe.Symbole")}</label>
<select id="ac-symbole">
<option value="coupe">${game.i18n.localize("HAMALRON.Tarot.FIELDS.symbole.coupe")}</option>
<option value="epee">${game.i18n.localize("HAMALRON.Tarot.FIELDS.symbole.epee")}</option>
<option value="denier">${game.i18n.localize("HAMALRON.Tarot.FIELDS.symbole.denier")}</option>
<option value="baton">${game.i18n.localize("HAMALRON.Tarot.FIELDS.symbole.baton")}</option>
</select>
</div>
<div class="form-group">
<label>${game.i18n.localize("HAMALRON.ActionComplexe.Resistance")}</label>
<input type="number" id="ac-resistance" value="3" min="1" max="10" />
</div>
<div class="form-group">
<label>${game.i18n.localize("HAMALRON.ActionComplexe.Notes")}</label>
<textarea id="ac-notes" rows="2"></textarea>
</div>`
foundry.applications.api.DialogV2.wait({
window: { title: game.i18n.localize("HAMALRON.ActionComplexe.CreateTitle") },
content,
rejectClose: false,
buttons: [{
action: "create",
label: game.i18n.localize("HAMALRON.ActionComplexe.Create"),
default: true,
callback: (event, button) => {
const name = button.form.elements["ac-name"].value.trim()
if (!name) return null
return {
name,
symbole: button.form.elements["ac-symbole"].value,
resistance: Number(button.form.elements["ac-resistance"].value) || 3,
notes: button.form.elements["ac-notes"].value.trim(),
}
},
}, {
action: "cancel",
label: game.i18n.localize("Cancel"),
}],
}).then(result => {
if (!result) return
self.actions.push({
id: foundry.utils.randomID(8),
name: result.name,
symbole: result.symbole,
resistance: result.resistance,
currentSuccesses: 0,
failures: 0,
notes: result.notes,
createdAt: Date.now(),
})
self.#saveState()
self.render()
})
}
static async #onRecordSuccess(event, target) {
const id = target.dataset.actionId
const action = this.actions.find(a => a.id === id)
if (!action || action.isComplete) return
action.currentSuccesses++
await this.#saveState()
if (action.currentSuccesses >= action.resistance) {
ui.notifications.info(
game.i18n.format("HAMALRON.ActionComplexe.Completed", { name: action.name })
)
ChatMessage.create({
content: `<div class="fvtt-hamalron"><h3><i class="fas fa-trophy"></i> ${game.i18n.format("HAMALRON.ActionComplexe.Completed", { name: action.name })}</h3><p>${action.currentSuccesses}/${action.resistance} ${game.i18n.localize("HAMALRON.ActionComplexe.Successes")}</p></div>`,
style: CONST.CHAT_MESSAGE_STYLES.OTHER,
})
}
this.render()
}
static async #onRecordFailure(event, target) {
const id = target.dataset.actionId
const action = this.actions.find(a => a.id === id)
if (!action) return
action.failures++
await this.#saveState()
this.render()
}
static async #onDeleteAction(event, target) {
const id = target.dataset.actionId
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { title: game.i18n.localize("HAMALRON.ActionComplexe.DeleteTitle") },
content: `<p>${game.i18n.localize("HAMALRON.ActionComplexe.DeleteWarning")}</p>`,
rejectClose: false,
modal: true,
})
if (!confirmed) return
this.actions = this.actions.filter(a => a.id !== id)
await this.#saveState()
this.render()
}
static #onResetAction(event, target) {
const id = target.dataset.actionId
const action = this.actions.find(a => a.id === id)
if (!action) return
action.currentSuccesses = 0
action.failures = 0
this.#saveState()
this.render()
}
static open() {
if (!game.user.isGM) {
ui.notifications.warn(game.i18n.localize("HAMALRON.Notifications.GMOnly"))
return
}
HamalronActionComplexe.instance.render(true, { focus: true })
return HamalronActionComplexe.instance
}
}