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 += `
+
+
+
`
+
+ 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 @@