Refactor des fiches de creatures

This commit is contained in:
2026-03-30 09:27:11 +02:00
parent 1b5da3e144
commit 6fda4b9246
71 changed files with 547 additions and 231 deletions

View File

@@ -13,7 +13,7 @@ export function registerSettings() {
}
export async function migrateIfNeeded() {
const current = game.system.version ?? MIGRATION_VERSION
const current = MIGRATION_VERSION
const stored = game.settings.get(SYSTEM_ID, "migrationVersion") ?? "0.0.0"
if (!foundry.utils.isNewerVersion(current, stored)) return
@@ -31,7 +31,7 @@ async function migrateActors() {
for (const actor of game.actors.contents) {
const updateData = migrateActorData(actor)
if (Object.keys(updateData).length > 0) {
updates.push(actor.update(updateData, { enforceTypes: false }))
updates.push(actor.update(updateData))
}
}
await Promise.all(updates)
@@ -44,7 +44,7 @@ async function migrateCompendiumActors() {
for (const actor of content) {
const updateData = migrateActorData(actor)
if (Object.keys(updateData).length > 0) {
await actor.update(updateData, { pack: pack.collection, enforceTypes: false })
await actor.update(updateData, { pack: pack.collection })
}
}
}
@@ -55,7 +55,7 @@ async function migrateItems() {
for (const item of game.items.contents) {
const updateData = migrateItemData(item)
if (Object.keys(updateData).length > 0) {
updates.push(item.update(updateData, { enforceTypes: false }))
updates.push(item.update(updateData))
}
}
await Promise.all(updates)
@@ -68,7 +68,7 @@ async function migrateCompendiumItems() {
for (const item of content) {
const updateData = migrateItemData(item)
if (Object.keys(updateData).length > 0) {
await item.update(updateData, { pack: pack.collection, enforceTypes: false })
await item.update(updateData, { pack: pack.collection })
}
}
}
@@ -119,7 +119,14 @@ function migrateItemData(item) {
const updateData = {}
const system = item.system ?? {}
// Add item-specific migrations here as needed
// Normalize legacy French damageAspect values to English keys
if (item.type === "weapon") {
const ASPECT_FR_TO_EN = { eau: "water", terre: "earth", feu: "fire", bois: "wood" }
const normalized = ASPECT_FR_TO_EN[system.damageAspect]
if (normalized) {
updateData["system.damageAspect"] = normalized
}
}
return updateData
}

View File

@@ -119,9 +119,6 @@ Hooks.once("init", async () => {
})
Hooks.once("ready", async () => {
if (!game.modules.get("lib-wrapper")?.active && game.user.isGM) {
ui.notifications.error("System fvtt-chroniques-de-l-etrange requires the 'libWrapper' module. Please install and activate it.")
}
await migrateIfNeeded()
})
@@ -149,7 +146,7 @@ function injectCompendiumLink(html) {
</section>
`
section.querySelector("button[data-action='open-cde-link']")?.addEventListener("click", () => {
window.open("https://antre-monde.com/les-chroniques-de-letrengae/", "_blank")
window.open("https://antre-monde.com/les-chroniques-de-letrange/", "_blank")
})
header.parentNode.insertBefore(section, header)

View File

@@ -61,6 +61,11 @@ export function registerHandlebarsHelpers() {
Handlebars.registerHelper("getElementIcon", function (aspect) {
const icons = {
metal: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp",
water: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
earth: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
fire: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",
wood: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp",
// legacy French keys
eau: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
terre: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
feu: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",

View File

@@ -55,7 +55,7 @@ function buildNPCOptions(sys) {
function readInitFields(dialog) {
const root = dialog.element ?? dialog
const selectedKey = root.querySelector("select[name='firstaction']")?.value ?? ""
const modifier = parseInt(root.querySelector("input[name='modifier']")?.value ?? 0) || 0
const modifier = parseInt(root.querySelector("input[name='modifier']")?.value ?? "0", 10) || 0
return { selectedKey, modifier }
}

View File

@@ -13,6 +13,8 @@
* Each category is associated with one of the five aspects in Wu Xing cycle order.
*/
import { MAGICS } from "../config/constants.js"
const RESULT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-dice-result.html"
const SKILL_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-skill-dice-prompt.html"
const SKILL_SPECIAL_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-skill-special-dice-prompt.html"
@@ -288,6 +290,11 @@ export async function rollForActor(actor, rollKey) {
numberofdice = sys.aspect[skillLibel]?.value ?? 0
title = game.i18n.localize(sys.aspect[skillLibel]?.label ?? "CDE.Roll")
break
case "aptitude":
// NPC aptitude roll — flat pool with WuXing prompt
numberofdice = sys.aptitudes?.[skillLibel]?.value ?? 0
title = game.i18n.localize(`CDE.${skillLibel.charAt(0).toUpperCase() + skillLibel.slice(1)}`)
break
case "skill":
numberofdice = sys.skills[skillLibel]?.value ?? 0
title = game.i18n.localize(sys.skills[skillLibel]?.label ?? "CDE.Roll")
@@ -329,7 +336,7 @@ export async function rollForActor(actor, rollKey) {
ui.notifications.warn(game.i18n.localize("CDE.Error6"))
return
}
title = `${game.i18n.localize(MAGIC_I18N_KEYS[skillLibel] ?? "CDE.Magics")} [${game.i18n.localize(game.system.CONST?.MAGICS?.[skillLibel]?.speciality?.[specialLibel]?.label ?? "")}]`
title = `${game.i18n.localize(MAGIC_I18N_KEYS[skillLibel] ?? "CDE.Magics")} [${game.i18n.localize(MAGICS?.[skillLibel]?.speciality?.[specialLibel]?.label ?? "")}]`
break
case "itemkungfu": {
// skillLibel = item._id — look up the kungfu item to find which skill + aspect to use
@@ -474,7 +481,7 @@ export async function rollForActor(actor, rollKey) {
let defaultSpecialAspect = 0
if (isMagicSpecial && specialLibel) {
// Look up the speciality's element from the MAGICS config constant
const specialCfg = game.system.CONST?.MAGICS?.[skillLibel]?.speciality?.[specialLibel]
const specialCfg = MAGICS?.[skillLibel]?.speciality?.[specialLibel]
const aspectName = LABELELEMENT_TO_ASPECT[specialCfg?.labelelement]
if (aspectName) {
defaultSpecialAspect = ASPECT_NAMES.indexOf(aspectName)

View File

@@ -11,6 +11,7 @@ export class CDEBaseActorSheet extends HandlebarsApplicationMixin(foundry.applic
create: CDEBaseActorSheet.#onItemCreate,
edit: CDEBaseActorSheet.#onItemEdit,
delete: CDEBaseActorSheet.#onItemDelete,
editImage: CDEBaseActorSheet.#onEditImage,
},
}
@@ -21,7 +22,7 @@ export class CDEBaseActorSheet extends HandlebarsApplicationMixin(foundry.applic
}
async _prepareContext() {
const descriptionHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true })
const descriptionHTML = await TextEditor.enrichHTML(this.document.system.description ?? "", { async: true })
const cssClass = this.options.classes?.join(" ") ?? ""
return {
actor: this.document,
@@ -77,4 +78,19 @@ export class CDEBaseActorSheet extends HandlebarsApplicationMixin(foundry.applic
const item = this.document.items.get(itemId)
if (item) item.delete()
}
static async #onEditImage(event, target) {
const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr)
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
const fp = new FilePicker({
current,
type: "image",
redirectToRoot: img ? [img] : [],
callback: (path) => this.document.update({ [attr]: path }),
top: this.position.top + 40,
left: this.position.left + 10,
})
return fp.browse()
}
}

View File

@@ -59,7 +59,7 @@ export class CDECharacterSheet extends CDEBaseActorSheet {
}
_onRender(context, options) {
super._onRender?.(context, options)
super._onRender(context, options)
this.#bindInitiativeControls()
this.#bindPrefs()
this.#bindRollButtons()
@@ -99,7 +99,7 @@ export class CDECharacterSheet extends CDEBaseActorSheet {
<form class="flexcol">
<div class="form-group">
<label>${game.i18n.localize("CDE.ThrowType")}</label>
<select name="choice" value="${current.choice}">
<select name="choice">
<option value="0"${current.choice === "0" ? " selected" : ""}>0</option>
<option value="1"${current.choice === "1" ? " selected" : ""}>1</option>
<option value="2"${current.choice === "2" ? " selected" : ""}>2</option>
@@ -111,14 +111,18 @@ export class CDECharacterSheet extends CDEBaseActorSheet {
<input type="checkbox" name="check" ${current.check ? "checked" : ""}/>
</div>
</form>`
const prefs = await Dialog.prompt({
title: game.i18n.localize("CDE.Preferences"),
const prefs = await foundry.applications.api.DialogV2.prompt({
window: { title: game.i18n.localize("CDE.Preferences") },
content: html,
label: game.i18n.localize("CDE.Validate"),
callback: (dlg) => {
const choice = dlg.querySelector("select[name='choice']")?.value ?? "0"
const check = dlg.querySelector("input[name='check']")?.checked ?? false
return { choice, check }
rejectClose: false,
ok: {
label: game.i18n.localize("CDE.Validate"),
callback: (_ev, _btn, dialog) => {
const root = dialog.element ?? dialog
const choice = root.querySelector("select[name='choice']")?.value ?? "0"
const check = root.querySelector("input[name='check']")?.checked ?? false
return { choice, check }
},
},
})
if (prefs) {
@@ -166,7 +170,7 @@ export class CDECharacterSheet extends CDEBaseActorSheet {
speaker: ChatMessage.getSpeaker({ actor: this.document }),
content,
rolls: [roll],
rollMode: "roll",
rollMode: game.settings.get("core", "rollMode") ?? "roll",
})
})
}

View File

@@ -1,4 +1,5 @@
import { rollInitiativeNPC } from "../../initiative.js"
import { rollForActor } from "../../rolling.js"
import { CDEBaseActorSheet } from "./base.js"
export class CDENpcSheet extends CDEBaseActorSheet {
@@ -15,15 +16,30 @@ export class CDENpcSheet extends CDEBaseActorSheet {
async _prepareContext() {
const context = await super._prepareContext()
context.supernaturals = context.items.filter((item) => item.type === "supernatural")
context.spells = context.items.filter((item) => item.type === "spell")
context.kungfus = context.items.filter((item) => item.type === "kungfu")
context.equipments = context.items.filter((item) => item.type === "item")
context.spells = context.items.filter((item) => item.type === "spell")
context.kungfus = context.items.filter((item) => item.type === "kungfu")
context.weapons = context.items.filter((item) => item.type === "weapon")
context.armors = context.items.filter((item) => item.type === "armor")
context.equipments = context.items.filter((item) => item.type === "item")
return context
}
_onRender(context, options) {
super._onRender?.(context, options)
super._onRender(context, options)
this.#bindInitiativeControls()
this.#bindRollButtons()
}
#bindRollButtons() {
const cells = this.element?.querySelectorAll(".cde-roll-trigger[data-libel-id]")
if (!cells?.length) return
cells.forEach((cell) => {
cell.addEventListener("click", (event) => {
event.preventDefault()
const rollKey = cell.dataset.libelId
if (rollKey) rollForActor(this.document, rollKey)
})
})
}
#bindInitiativeControls() {

View File

@@ -6,7 +6,9 @@ export class CDEBaseItemSheet extends HandlebarsApplicationMixin(foundry.applica
position: { width: 520, height: "auto" },
window: { resizable: true },
form: { submitOnChange: true },
actions: {},
actions: {
editImage: CDEBaseItemSheet.#onEditImage,
},
}
tabGroups = { primary: "details" }
@@ -17,8 +19,8 @@ export class CDEBaseItemSheet extends HandlebarsApplicationMixin(foundry.applica
async _prepareContext() {
const cssClass = this.options.classes?.join(" ") ?? ""
const enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true })
const enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.notes ?? "", { async: true })
const enrichedDescription = await TextEditor.enrichHTML(this.document.system.description ?? "", { async: true })
const enrichedNotes = await TextEditor.enrichHTML(this.document.system.notes ?? "", { async: true })
return {
item: this.document,
system: this.document.system,
@@ -40,4 +42,19 @@ export class CDEBaseItemSheet extends HandlebarsApplicationMixin(foundry.applica
this.changeTab(tab, group, { force: true })
}
}
static async #onEditImage(event, target) {
const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr)
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
const fp = new FilePicker({
current,
type: "image",
redirectToRoot: img ? [img] : [],
callback: (path) => this.document.update({ [attr]: path }),
top: this.position.top + 40,
left: this.position.left + 10,
})
return fp.browse()
}
}