diff --git a/fvtt-hamalron.mjs b/fvtt-hamalron.mjs index 67701d9..f8ec0cf 100644 --- a/fvtt-hamalron.mjs +++ b/fvtt-hamalron.mjs @@ -29,6 +29,7 @@ Hooks.once("init", function () { models, documents, openTarotDeckManager: () => applications.HamalronTarotDeckManager.openManager(), + openActionComplexe: () => applications.HamalronActionComplexe.open(), } // Global reference needed by initiative override @@ -124,6 +125,15 @@ Hooks.once("init", function () { default: { deck: [], discard: [] }, }) + game.settings.register("fvtt-hamalron", "actionComplexeState", { + name: "Action Complexe State", + hint: "Stores ongoing complex actions", + scope: "world", + config: false, + type: Object, + default: { actions: [] }, + }) + // Activate socket handler game.socket.on(`system.${SYSTEM.id}`, handleSocketEvent) @@ -156,10 +166,9 @@ Hooks.once("ready", function () { }) -// Add button to scene controls for Tarot Deck Manager +// Add button to scene controls for Tarot Deck Manager and Actions Complexes Hooks.on("getSceneControlButtons", (controls) => { if (game.user.isGM) { - // Handle both old and new Foundry API const controlsArray = Array.isArray(controls) ? controls : controls.controls || []; const tokenControls = controlsArray.find(c => c.name === "token") if (tokenControls) { @@ -170,6 +179,13 @@ Hooks.on("getSceneControlButtons", (controls) => { button: true, onClick: () => applications.HamalronTarotDeckManager.openManager(), }) + tokenControls.tools.push({ + name: "action-complexe", + title: "HAMALRON.ActionComplexe.Title", + icon: "fas fa-tasks", + button: true, + onClick: () => applications.HamalronActionComplexe.open(), + }) } } }) diff --git a/lang/fr.json b/lang/fr.json index 1e80f7f..4d21dfc 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -810,6 +810,24 @@ "label": "Seuil" } } + }, + "ActionComplexe": { + "Title": "Actions complexes", + "CreateAction": "Nouvelle action", + "CreateTitle": "Créer une action complexe", + "Create": "Créer", + "Name": "Nom", + "Symbole": "Symbole", + "Resistance": "Résistance", + "Notes": "Notes", + "Success": "Réussite", + "Failure": "Échec", + "Successes": "succès", + "NoActions": "Aucune action complexe en cours", + "Completed": "{name} — Action accomplie !", + "Progress": "{name} — {current}/{total} succès", + "DeleteTitle": "Supprimer l'action", + "DeleteWarning": "Voulez-vous vraiment supprimer cette action complexe ? Les données seront perdues." } } } \ No newline at end of file diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index a6d9409..506afaa 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -14,3 +14,4 @@ export { default as HamalronItemSheet } from "./sheets/base-item-sheet.mjs" export { default as HamalronActorSheet } from "./sheets/base-actor-sheet.mjs" export { default as HamalronTarotDeckManager } from "./tarot-deck-manager.mjs" export { default as HamalronSortilegeCasting } from "./sortilege-casting.mjs" +export { default as HamalronActionComplexe } from "./action-complexe.mjs" diff --git a/module/applications/action-complexe.mjs b/module/applications/action-complexe.mjs new file mode 100644 index 0000000..35120b4 --- /dev/null +++ b/module/applications/action-complexe.mjs @@ -0,0 +1,200 @@ +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 = ` +
+ + +
+
+ + +
+
+ + +
+
+ + +
` + 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: `

${game.i18n.format("HAMALRON.ActionComplexe.Completed", { name: action.name })}

${action.currentSuccesses}/${action.resistance} ${game.i18n.localize("HAMALRON.ActionComplexe.Successes")}

`, + 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: `

${game.i18n.localize("HAMALRON.ActionComplexe.DeleteWarning")}

`, + 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 + } +} diff --git a/templates/dialog-action-complexe.hbs b/templates/dialog-action-complexe.hbs new file mode 100644 index 0000000..930c183 --- /dev/null +++ b/templates/dialog-action-complexe.hbs @@ -0,0 +1,58 @@ +
+
+ +
+ + {{#if hasActions}} +
+ {{#each actions}} +
+
+ {{name}} + +
+ +
+
+
+
+ {{currentSuccesses}} / {{resistance}} +
+ +
+ {{currentSuccesses}} + {{failures}} +
+ + {{#if notes}} +
{{notes}}
+ {{/if}} + +
+ {{#unless isComplete}} + + + {{/unless}} + + +
+
+ {{/each}} +
+ {{else}} +
+ +

{{localize "HAMALRON.ActionComplexe.NoActions"}}

+
+ {{/if}} +