From 2bd236fb2980f5aae8cd4e583f626eb3fb9e6998 Mon Sep 17 00:00:00 2001 From: LeRatierBretonnier Date: Fri, 10 Jul 2026 20:49:09 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20syst=C3=A8me=20de=20r=C3=A9cup=C3=A9rat?= =?UTF-8?q?ion=20des=20r=C3=A9sistances?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dialogue de récupération accessible depuis la feuille personnage - Coupe/Denier : test difficulté 7 (1x/jour), +1 si réussi - Épée : +1 manuel (repos/soins) ou test guérison rapide - Bâton : +1 manuel (appréciation MJ) - Suivi des dates de dernière récupération par symbole - Désactive le test si déjà fait aujourd'hui - Bouton d'accès intégré dans la section résistances (icône lit) - Réutilise HamalronTirageTarot.prompt pour les tests --- lang/fr.json | 11 ++ module/applications/resistance-recovery.mjs | 148 ++++++++++++++++++ .../applications/sheets/personnage-sheet.mjs | 6 + module/models/personnage.mjs | 1 + templates/personnage-main.hbs | 9 +- 5 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 module/applications/resistance-recovery.mjs diff --git a/lang/fr.json b/lang/fr.json index 4d21dfc..387995c 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -828,6 +828,17 @@ "Progress": "{name} — {current}/{total} succès", "DeleteTitle": "Supprimer l'action", "DeleteWarning": "Voulez-vous vraiment supprimer cette action complexe ? Les données seront perdues." + }, + "Recovery": { + "Title": "Récupération des résistances", + "Subtitle": "Récupérer vos points de résistance perdus", + "Today": "Aujourd'hui : {date}", + "Test": "Tester (dif. 7)", + "HealTest": "Test guérison rapide", + "Done": "Déjà fait aujourd'hui", + "Last": "Dernière récup", + "Success": "Récupération réussie ! +1 en {label}", + "Failure": "Échec de la récupération en {label}. Réessayez demain." } } } \ No newline at end of file diff --git a/module/applications/resistance-recovery.mjs b/module/applications/resistance-recovery.mjs new file mode 100644 index 0000000..4780589 --- /dev/null +++ b/module/applications/resistance-recovery.mjs @@ -0,0 +1,148 @@ +import { SYSTEM } from "../config/system.mjs" + +export default class HamalronResistanceRecovery { + static SYMBOLE_LABELS = { + coupe: "Coupe", + epee: "Épée", + denier: "Denier", + baton: "Bâton", + } + + static async prompt(actor) { + const resistances = actor.system.resistances + const recovery = actor.system.resistanceRecovery || {} + const today = new Date().toISOString().split("T")[0] + const todayNum = Number(new Date().toISOString().split("T")[0].replace(/-/g, "")) + + const symInfos = Object.entries(SYSTEM.TAROT_SYMBOLES).map(([key, val]) => { + const r = resistances[key] + const lastDate = recovery[key] + const lastNum = lastDate ? Number(lastDate.replace(/-/g, "")) : 0 + const canRecover = key === "epee" + ? true + : (lastNum < todayNum) + const isDenierCoupe = key === "denier" || key === "coupe" + return { + id: key, + label: val.label, + value: r?.value ?? 0, + max: r?.max ?? 0, + canRecover, + isDenierCoupe, + lastRecovery: lastDate || "-", + } + }) + + const content = HamalronResistanceRecovery.#renderContent(actor, symInfos) + + const result = await foundry.applications.api.DialogV2.wait({ + window: { title: game.i18n.localize("HAMALRON.Recovery.Title") }, + classes: ["fvtt-hamalron"], + position: { width: 480 }, + content, + buttons: [ + { + action: "close", + label: game.i18n.localize("Cancel"), + default: true, + }, + ], + actions: { + recoverTest: async (event, button, dialog) => { + const btn = event.currentTarget + const symbole = btn.dataset.symbole + if (!symbole) return + const rollItem = { name: HamalronResistanceRecovery.SYMBOLE_LABELS[symbole] || symbole, value: 0 } + await HamalronResistanceRecovery.#doRecoveryTest(actor, symbole, rollItem) + HamalronResistanceRecovery.prompt(actor) + }, + adjustResistance: async (event, button, dialog) => { + const btn = event.currentTarget + const symbole = btn.dataset.symbole + const delta = Number(btn.dataset.delta) || 0 + if (!symbole) return + const resistance = actor.system.resistances[symbole] + if (!resistance) return + const newValue = Math.max(0, Math.min(resistance.max, (resistance.value || 0) + delta)) + await actor.update({ [`system.resistances.${symbole}.value`]: newValue }) + HamalronResistanceRecovery.prompt(actor) + }, + }, + rejectClose: false, + }) + } + + static #renderContent(actor, symInfos) { + const today = new Date().toISOString().split("T")[0] + let html = `

${game.i18n.localize("HAMALRON.Recovery.Subtitle")}

` + html += `

${game.i18n.format("HAMALRON.Recovery.Today", { date: today })}

` + html += `
` + + for (const info of symInfos) { + const pct = info.max > 0 ? Math.round((info.value / info.max) * 100) : 0 + html += `
+
+ ${info.label} + ${info.value}/${info.max} +
+
+
` + + if (info.isDenierCoupe) { + if (info.canRecover) { + html += `` + } else { + html += ` ${game.i18n.localize("HAMALRON.Recovery.Done")}` + } + } else if (info.id === "epee") { + html += `` + html += `` + } else { // baton + html += `` + } + + html += `` + + html += `
${game.i18n.localize("HAMALRON.Recovery.Last")}: ${info.lastRecovery}
+
` + } + + html += `
` + return html + } + + static async #doRecoveryTest(actor, symbole, rollItem) { + const niveauData = SYSTEM.NIVEAU_COMPETENCES.inexperimente + const roll = await HamalronTirageTarot.prompt({ + rollType: "stat", + rollItem, + actorId: actor.id, + actorName: actor.name, + actorImage: actor.img, + hasTarget: false, + difficulty: "7", + }) + if (!roll) return + + const today = new Date().toISOString().split("T")[0] + const recoveryData = foundry.utils.duplicate(actor.system.resistanceRecovery || {}) + recoveryData[symbole] = today + await actor.update({ [`system.resistanceRecovery`]: recoveryData }) + + if (roll.total >= 7) { + const resistance = actor.system.resistances[symbole] + if (resistance && resistance.value < resistance.max) { + const newValue = Math.min(resistance.max, (resistance.value || 0) + 1) + await actor.update({ [`system.resistances.${symbole}.value`]: newValue }) + } + ui.notifications.info(game.i18n.format("HAMALRON.Recovery.Success", { label: HamalronResistanceRecovery.SYMBOLE_LABELS[symbole] })) + } else { + ui.notifications.info(game.i18n.format("HAMALRON.Recovery.Failure", { label: HamalronResistanceRecovery.SYMBOLE_LABELS[symbole] })) + } + } +} diff --git a/module/applications/sheets/personnage-sheet.mjs b/module/applications/sheets/personnage-sheet.mjs index 112c836..ec9e035 100644 --- a/module/applications/sheets/personnage-sheet.mjs +++ b/module/applications/sheets/personnage-sheet.mjs @@ -18,6 +18,7 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { createArmor: HamalronPersonnageSheet.#onCreateArmor, createWeapon: HamalronPersonnageSheet.#onCreateWeapon, createSortilege: HamalronPersonnageSheet.#onCreateSortilege, + openRecovery: HamalronPersonnageSheet.#onOpenRecovery, modifyResistance: HamalronPersonnageSheet.#onModifyResistance, discardCard: HamalronPersonnageSheet.#onDiscardCard, rollCompetence: HamalronPersonnageSheet.#onRollCompetence, @@ -173,6 +174,11 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newSortilege"), type: "sortilege" }]) } + static async #onOpenRecovery(event, target) { + const { default: HamalronResistanceRecovery } = await import("../resistance-recovery.mjs") + await HamalronResistanceRecovery.prompt(this.document) + } + static async #onModifyResistance(event, target) { const symbole = target.dataset.symbole const delta = Number.parseInt(target.dataset.delta) diff --git a/module/models/personnage.mjs b/module/models/personnage.mjs index 93c09db..f9d9db1 100644 --- a/module/models/personnage.mjs +++ b/module/models/personnage.mjs @@ -35,6 +35,7 @@ export default class HamalronPersonnage extends foundry.abstract.TypeDataModel { schema.historial = new fields.HTMLField({ required: true, textSearch: true }) schema.progression = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) schema.rangMagie = new fields.StringField({ required: true, initial: "initie", choices: Object.fromEntries(Object.entries(SYSTEM.RANG_MAGICIEN).map(([key, val]) => [key, val.label])) }) + schema.resistanceRecovery = new fields.ObjectField({ required: false, initial: {} }) schema.biodata = new fields.SchemaField({ age: new fields.StringField({ required: true, nullable: false, initial: "" }), diff --git a/templates/personnage-main.hbs b/templates/personnage-main.hbs index 5a4df32..ea62c4f 100644 --- a/templates/personnage-main.hbs +++ b/templates/personnage-main.hbs @@ -138,7 +138,14 @@
- {{localize "HAMALRON.Label.resistances"}} + + {{localize "HAMALRON.Label.resistances"}} + {{#unless isEditMode}} + + + + {{/unless}} +
{{#each system.resistances as |resistance symbole|}}