diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5bc50e4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,55 @@ +# AGENTS.md — fvtt-hamalron + +Foundry VTT **game system** (not a module) for Hamalron JDR, a French-language TTRPG. + +System ID: `fvtt-hamalron` · Compatible FVTT v13–v14 · Language: French only (`lang/fr.json`) + +## Architecture + +| Layer | Location | Notes | +|-------|----------|-------| +| Entrypoint | `fvtt-hamalron.mjs` | ES module, loaded via `system.json` → `esmodules` | +| Models | `module/models/*.mjs` | 12 data models (personnage, pnj, arme, armure, competence, equipement, faction, langue, peuple, region, sortilege, tarot) | +| Documents | `module/documents/*.mjs` | Actor, Item, ChatMessage, Roll (tirage-tarot) | +| Sheets | `module/applications/sheets/*.mjs` | App V2 sheets, one per item/actor type + base classes | +| Config | `module/config/system.mjs` | Exposes `globalThis.SYSTEM` with all game constants | +| Styles | `styles/*.less` → compiled to `css/fvtt-hamalron.css` | +| Templates | `templates/*.hbs` | Handlebars partials | +| Tarot deck | `module/applications/tarot-deck-manager.mjs` | Singleton manager, Fisher-Yates shuffle, GM-only | + +## Commands + +```sh +# Build CSS (LESS → CSS) +npx gulp css # one-shot compile +npx gulp # default: compile + watch +``` + +## Linting + +```sh +npx eslint . +``` + +Uses ESLint 9 flat config (`eslint.config.mjs`) with Prettier + JSDoc plugins. Rules: `semi: never`, `quotes: double`, `indent: 2`. + +## Compendium pack management + +Pack data lives in `packs-system/hamalron-items/` (LevelDB format, used by Foundry at runtime). + +The repo has `@foundryvtt/foundryvtt-cli` and tools for YAML ↔ LevelDB conversion: +- `tools/packCompendiumsToDist.mjs` — compiles from `packs_src/` (YAML) to `packs/` (.db) +- `tools/unpackCompendiumsFromDist.mjs` — extracts from `packs/` (.db) to `packs_src/` (YAML) + +**Note:** `package.json` scripts reference `pushLDBtoYML`/`pullYMLtoLDB` — these files don't exist yet. Use the tool files directly: `node tools/packCompendiumsToDist.mjs` / `node tools/unpackCompendiumsFromDist.mjs` after creating the source directories. + +## Gotchas + +- **No tests** exist anywhere. +- **No CI/CD** (no `.github/` directory). +- **No typechecking** — plain JS with JSDoc comments. +- The `system.json` `hotReload` config references `lang/en.json` and `acks.css` — these files do **not** exist. Only `lang/fr.json` is shipped. +- All actor/item sheets are App V2 (unregistered core V1 sheets). +- Tarot deck state is persisted in a world-scope setting `tarotDeckState` (not a `Cards` document). +- Socket event handler is a no-op (`module/socket.mjs`). +- Initiative formula: `1d6` (no decimals). diff --git a/fvtt-hamalron.mjs b/fvtt-hamalron.mjs index bf3bf6c..a29d9df 100644 --- a/fvtt-hamalron.mjs +++ b/fvtt-hamalron.mjs @@ -102,7 +102,7 @@ Hooks.once("init", function () { HamalronUtils.registerSettings() HamalronUtils.registerHandlebarsHelpers() - console.info("FTL Nomad | System Initialized") + console.info("HAMALRON JDR | System Initialized") }) @@ -120,7 +120,7 @@ function preLocalizeConfig() { } Hooks.once("ready", function () { - console.info("FTL Nomad | Ready") + console.info("HAMALRON JDR | Ready") if (game.user.isGM) { ClassCounter.registerUsageCount("fvtt-hamalron", {}) } diff --git a/module/applications/sheets/base-actor-sheet.mjs b/module/applications/sheets/base-actor-sheet.mjs index d39bb55..b75dace 100644 --- a/module/applications/sheets/base-actor-sheet.mjs +++ b/module/applications/sheets/base-actor-sheet.mjs @@ -33,7 +33,6 @@ export default class HamalronActorSheet extends HandlebarsApplicationMixin(found toggleSheet: HamalronActorSheet.#onToggleSheet, edit: HamalronActorSheet.#onItemEdit, delete: HamalronActorSheet.#onItemDelete, - updateCheckboxArray: HamalronActorSheet.#onUpdateCheckboxArray, toChat: HamalronActorSheet.#toChat, }, @@ -69,7 +68,6 @@ export default class HamalronActorSheet extends HandlebarsApplicationMixin(found actor: this.document, system: this.document.system, source: this.document.toObject(), - enrichedBackstory: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.backstory, { async: true }), isEditMode: this.isEditMode, isPlayMode: this.isPlayMode, isEditable: this.isEditable, @@ -187,44 +185,19 @@ export default class HamalronActorSheet extends HandlebarsApplicationMixin(found return fp.browse() } - static #onUpdateCheckboxArray(event, target) { - console.log("Update checkbox array", event, target) - let arrayName = target.dataset.name - let arrayIdx = Number(target.dataset.index) - let dataPath = `system.mortality.${arrayName}` - let tab = foundry.utils.duplicate(this.document.system.mortality[arrayName]) - tab[arrayIdx] = target.checked - this.actor.update({ [dataPath]: tab }) - } - static async #toChat(event, target) { const itemUuid = target.getAttribute("data-item-uuid") const item = fromUuidSync(itemUuid) if (!item) return - let content = "" - if (item.type === "perk") { - content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/chat-perk.hbs", item.toObject()) - } - if (item.type === "malefica") { - content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/chat-malefica.hbs", item.toObject()) - } - if (item.type === "ritual") { - content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/chat-ritual.hbs", item.toObject()) - } - if (item.type === "species-trait") { - content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/chat-trait.hbs", item.toObject()) - } - if (item.type === "tarot") { - content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/chat-tarot.hbs", item.toObject()) - } - const chatData = { + if (item.type !== "tarot") return + const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/chat-tarot.hbs", item.toObject()) + const actor = this.actor || this.document?.parent + ChatMessage.create({ user: game.user.id, - speaker: ChatMessage.getSpeaker({ actor: this.actor }), - content: content, + speaker: ChatMessage.getSpeaker({ actor }), + content, type: CONST.CHAT_MESSAGE_TYPES.OTHER, - } - ChatMessage.create(chatData, { renderSheet: false }) - + }, { renderSheet: false }) } /** diff --git a/module/applications/sheets/base-item-sheet.mjs b/module/applications/sheets/base-item-sheet.mjs index a98ae15..57d1f37 100644 --- a/module/applications/sheets/base-item-sheet.mjs +++ b/module/applications/sheets/base-item-sheet.mjs @@ -127,12 +127,13 @@ export default class HamalronItemSheet extends HandlebarsApplicationMixin(foundr const el = event.currentTarget if ("link" in event.target.dataset) return - // Extract the data you need - let dragData = null + const itemId = el.dataset.itemId + if (!itemId) return - if (!dragData) return + const item = this.document.items.get(itemId) + if (!item) return - // Set data transfer + const dragData = item.toDragData() event.dataTransfer.setData("text/plain", JSON.stringify(dragData)) } diff --git a/module/applications/sheets/enemy-sheet.mjs b/module/applications/sheets/enemy-sheet.mjs index e56c77f..7aa1980 100644 --- a/module/applications/sheets/enemy-sheet.mjs +++ b/module/applications/sheets/enemy-sheet.mjs @@ -12,10 +12,9 @@ export default class HamalronEnemySheet extends HamalronActorSheet { contentClasses: ["enemy-content"], }, actions: { - createTrait: HamalronEnemySheet.#onCreateTrait, createEquipment: HamalronEnemySheet.#onCreateEquipment, createWeapon: HamalronEnemySheet.#onCreateWeapon, - createMalefica: HamalronEnemySheet.#onCreateMalefica, + createSortilege: HamalronEnemySheet.#onCreateSortilege, } } @@ -74,13 +73,13 @@ export default class HamalronEnemySheet extends HamalronActorSheet { break case "traits": context.tab = context.tabs.traits - context.traits = doc.itemTypes["species-trait"] + context.traits = doc.itemTypes.competence || [] context.traits.sort((a, b) => a.name.localeCompare(b.name)) - context.weapons = doc.itemTypes.weapon + context.weapons = doc.itemTypes.arme || [] context.weapons.sort((a, b) => a.name.localeCompare(b.name)) - context.maleficas = doc.itemTypes.malefica - context.maleficas.sort((a, b) => a.name.localeCompare(b.name)) - context.equipments = doc.itemTypes.equipment + context.sortileges = doc.itemTypes.sortilege || [] + context.sortileges.sort((a, b) => a.name.localeCompare(b.name)) + context.equipments = doc.itemTypes.equipement || [] context.equipments.sort((a, b) => a.name.localeCompare(b.name)) break case "biography": @@ -92,25 +91,16 @@ export default class HamalronEnemySheet extends HamalronActorSheet { return context } - /** - * Creates a new attack item directly from the sheet and embeds it into the document. - * @param {Event} event The initiating click event. - * @param {HTMLElement} target The current target of the event listener. - */ - static #onCreateTrait(event, target) { - this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newTrait"), type: "species-trait" }]) - } - static #onCreateEquipment(event, target) { - this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newEquipment"), type: "equipment" }]) + this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newEquipment"), type: "equipement" }]) } - static #onCreateMalefica(event, target) { - this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newMalefica"), type: "malefica" }]) + static #onCreateSortilege(event, target) { + this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newSortilege"), type: "sortilege" }]) } static #onCreateWeapon(event, target) { - this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newWeapon"), type: "weapon" }]) + this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newWeapon"), type: "arme" }]) } @@ -135,8 +125,8 @@ export default class HamalronEnemySheet extends HamalronActorSheet { case "stat": { let statId = $(event.currentTarget).data("stat-id"); - item = this.actor.system.stats[statId]; - await this.document.system.roll(rollType, item) + item = this.actor.system?.stats?.[statId]; + if (item) await this.document.system.roll(rollType, item) } break case "weapon": @@ -144,7 +134,7 @@ export default class HamalronEnemySheet extends HamalronActorSheet { { let li = $(event.currentTarget).parents(".item"); item = this.actor.items.get(li.data("item-id")); - await this.document.system.roll(rollType, item) + if (item) await this.document.system.roll(rollType, item) } break default: @@ -154,7 +144,7 @@ export default class HamalronEnemySheet extends HamalronActorSheet { async _onDrop(event) { if (!this.isEditable || !this.isEditMode) return - const data = TextEditor.getDragEventData(event) + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event) // Handle different data types switch (data.type) { diff --git a/module/applications/sheets/personnage-sheet.mjs b/module/applications/sheets/personnage-sheet.mjs index 3ecff00..1bd89e3 100644 --- a/module/applications/sheets/personnage-sheet.mjs +++ b/module/applications/sheets/personnage-sheet.mjs @@ -1,5 +1,6 @@ import HamalronActorSheet from "./base-actor-sheet.mjs" import { SYSTEM } from "../../config/system.mjs" +import HamalronUtils from "../../utils.mjs" export default class HamalronPersonnageSheet extends HamalronActorSheet { /** @override */ @@ -16,11 +17,7 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { createEquipment: HamalronPersonnageSheet.#onCreateEquipment, createArmor: HamalronPersonnageSheet.#onCreateArmor, createWeapon: HamalronPersonnageSheet.#onCreateWeapon, - createDeal: HamalronPersonnageSheet.#onCreateDeal, - createMalefica: HamalronPersonnageSheet.#onCreateMalefica, - createRitual: HamalronPersonnageSheet.#onCreateRitual, - createPerk: HamalronPersonnageSheet.#onCreatePerk, - modifyAmmo: HamalronPersonnageSheet.#onModifyAmmo, + createSortilege: HamalronPersonnageSheet.#onCreateSortilege, modifyResistance: HamalronPersonnageSheet.#onModifyResistance, discardCard: HamalronPersonnageSheet.#onDiscardCard, rollCompetence: HamalronPersonnageSheet.#onRollCompetence, @@ -118,21 +115,15 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { case "sortileges": context.tab = context.tabs.sortileges context.sortileges = doc.itemTypes.sortilege || [] - // Sort the sortileges by system.domain and then by the system.level - context.sortileges.sort((a, b) => { - if (a.system.domain === b.system.domain) { - return a.system.level.localeCompare(b.system.level) - } - return a.system.domain.localeCompare(b.system.domain) - }) + context.sortileges.sort((a, b) => a.name.localeCompare(b.name)) break case "equipment": context.tab = context.tabs.equipment - context.weapons = doc.itemTypes.weapon || [] + context.weapons = doc.itemTypes.arme || [] context.weapons.sort((a, b) => a.name.localeCompare(b.name)) - context.armors = doc.itemTypes.armor || [] + context.armors = doc.itemTypes.armure || [] context.armors.sort((a, b) => a.name.localeCompare(b.name)) - context.equipments = doc.itemTypes.equipment || [] + context.equipments = doc.itemTypes.equipement || [] context.equipments.sort((a, b) => a.name.localeCompare(b.name)) break case "tarot": @@ -142,16 +133,14 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { context.handLimit = 7 context.handSize = context.tarotCards.length context.isOverLimit = context.handSize > context.handLimit - context.canAddCard = context.handSize < 8 - // Marquer les cartes de succès et enrichir les descriptions + context.canAddCard = context.handSize < context.handLimit for (const card of context.tarotCards) { card.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(card.system.description, { async: true }) - // Vérifier si c'est une carte de succès if (doc.system.cartesSucces && card.system.type !== "atout") { card.isSuccessCard = Object.keys(doc.system.cartesSucces).some(symbole => { const successCardData = doc.system.cartesSucces[symbole] return card.system.symbole === symbole && - card.system.valeur === successCardData.value.toString() + String(card.system.valeur) === String(successCardData.value) }) } else { card.isSuccessCard = false @@ -160,19 +149,7 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { break case "biography": context.tab = context.tabs.biography - context.deals = doc.itemTypes.deal || [] - context.enrichedBackstory = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.backstory, { async: true }) - context.enrichedAppearance = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.appearance, { async: true }) - context.enrichedScars = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.scars, { async: true }) - context.enrichedLikes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.likes, { async: true }) - context.enrichedDislikes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.dislikes, { async: true }) - context.enrichedFears = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.fears, { async: true }) - context.enrichedVices = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.vices, { async: true }) - context.enrichedGoals = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.goals, { async: true }) - context.enrichedAmbitions = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.ambitions, { async: true }) - context.enrichedValues = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.values, { async: true }) - context.enrichedBonds = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.bonds, { async: true }) - context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.notes, { async: true }) + context.enrichedHistorial = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.historial, { async: true }) break } return context @@ -180,44 +157,19 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { static #onCreateEquipment(event, target) { - this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newEquipment"), type: "equipment" }]) - } - - static #onCreateDeal(event, target) { - this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newDeal"), type: "deal" }]) - } - - static #onCreateMalefica(event, target) { - this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newMalefica"), type: "malefica" }]) - } - - static #onCreateRitual(event, target) { - this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newRitual"), type: "ritual" }]) + this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newEquipment"), type: "equipement" }]) } static #onCreateWeapon(event, target) { - this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newWeapon"), type: "weapon" }]) + this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newWeapon"), type: "arme" }]) } static #onCreateArmor(event, target) { - this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newArmor"), type: "armor" }]) + this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newArmor"), type: "armure" }]) } - static #onCreatePerk(event, target) { - this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newPerk"), type: "perk" }]) - } - - static async #onModifyAmmo(event, target) { - const quantity = parseInt($(event.target).data("quantity")) - const itemId = $(event.target).data("item-id") - const item = this.document.items.get(itemId) - if (!item) { - console.warn(`HamalronCharacterSheet: Item with ID ${itemId} not found`) - return - } - const currentAmmo = item.system.ammoQuantity || 0 - const newAmmo = Math.max(0, currentAmmo + quantity) // Ensure ammo doesn't go below 0 - await item.update({ "system.ammoQuantity": newAmmo }) + static #onCreateSortilege(event, target) { + this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newSortilege"), type: "sortilege" }]) } static async #onModifyResistance(event, target) { @@ -326,7 +278,7 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { const { default: HamalronTirageTarot } = await import("../../documents/tirage-tarot.mjs") await HamalronTirageTarot.handleLeMatEffect(this.document, deckManager) } else { - const originalItem = game.items.get(newCard.id) + const originalItem = await HamalronUtils.findTarotItem(newCard) if (originalItem) { await this.document.createEmbeddedDocuments("Item", [originalItem.toObject()]) ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.cardDiscardedAndDrawn") + newCard.name) @@ -337,7 +289,7 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.cardDiscardedNoNew")) } - deckManager.saveDeckState() + await deckManager.saveDeckState() deckManager.render() } @@ -382,14 +334,13 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { const { default: HamalronTirageTarot } = await import("../../documents/tirage-tarot.mjs") await HamalronTirageTarot.handleLeMatEffect(this.document, deckManager) } else { - // Trouver l'item original pour le dupliquer - const originalItem = game.items.get(newCard.id) + const originalItem = await HamalronUtils.findTarotItem(newCard) if (originalItem) { await this.document.createEmbeddedDocuments("Item", [originalItem.toObject()]) ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.newCardDrawn") + newCard.name) } } - deckManager.saveDeckState() + await deckManager.saveDeckState() deckManager.render() } } @@ -426,7 +377,7 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { switch (rollType) { case "stat": statKey = $(event.currentTarget).data("stat-id"); - item = this.actor.system.stats[statKey]; + item = this.actor.system?.stats?.[statKey]; break case "weapon": case "damage": @@ -436,7 +387,7 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { default: throw new Error(`Unknown roll type ${rollType}`) } - await this.document.system.roll(rollType, item) + if (item) await this.document.system.roll(rollType, item) } async _onDrop(event) { @@ -456,21 +407,11 @@ export default class HamalronPersonnageSheet extends HamalronActorSheet { const currentTarotCount = this.document.items.filter(i => i.type === "tarot").length - // Check if we already have 8 cards (hard limit) - if (currentTarotCount >= 8) { + if (currentTarotCount >= 7) { ui.notifications.warn(game.i18n.localize("HAMALRON.Notifications.tarotHandFull")) return } } - if (item.type === "species-trait") { - // Check if the item is already in the actor - const existingTrait = this.document.items.find(i => i.type === "species-trait" && i.name === item.name) - if (existingTrait) { - await existingTrait.delete() - // Display info message - ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.speciesTraitDeleted") + existingTrait.name) - } - } return super._onDropItem(item) } diff --git a/module/applications/sheets/sortilege-sheet.mjs b/module/applications/sheets/sortilege-sheet.mjs index 893c1fa..0a7941a 100644 --- a/module/applications/sheets/sortilege-sheet.mjs +++ b/module/applications/sheets/sortilege-sheet.mjs @@ -3,12 +3,12 @@ import HamalronItemSheet from "./base-item-sheet.mjs" export default class HamalronSortilegeSheet extends HamalronItemSheet { /** @override */ static DEFAULT_OPTIONS = { - classes: ["ritual"], + classes: ["sortilege"], position: { width: 600, }, window: { - contentClasses: ["ritual-content"], + contentClasses: ["sortilege-content"], }, } diff --git a/module/config/system.mjs b/module/config/system.mjs index 6bb6d5c..2414667 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -120,6 +120,13 @@ export const NIVEAU_COMPETENCES = { } } +export const CHOICE_ADVANTAGES_DISADVANTAGES = { + "0": { id: "0", label: "Aucun" }, + "1": { id: "1", label: "1" }, + "2": { id: "2", label: "2" }, + "3": { id: "3", label: "3" }, +} + /** * Include all constant definitions within the SYSTEM global export * @type {Object} @@ -135,5 +142,6 @@ export const SYSTEM = { ENEMY_TYPES: ENEMY_TYPES, SORTILEGE_CATEGORIES: SORTILEGE_CATEGORIES, NIVEAU_COMPETENCES: NIVEAU_COMPETENCES, + CHOICE_ADVANTAGES_DISADVANTAGES, ASCII } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 75a70e1..20c589c 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -1,43 +1,17 @@ -import HamalronUtils from "../utils.mjs" - export default class HamalronActor extends Actor { - static async create(data, options) { - - // Case of compendium global import - if (data instanceof Array) { - return super.create(data, options); - } - // If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic - if (data.items) { - let actor = super.create(data, options); - return actor; - } - - if (data.type === 'character') { - } - - return super.create(data, options); - } - - _onUpdate(changed, options, userId) { - return super._onUpdate(changed, options, userId) - } - - async _preCreate(data, options, user) { - await super._preCreate(data, options, user) + await super._preCreate(data, options, user) - // Configure prototype token settings - const prototypeToken = {} - if (this.type === "character") { - Object.assign(prototypeToken, { - sight: { enabled: true }, - actorLink: true, - disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY, - }) - this.updateSource({ prototypeToken }) + if (this.type === "personnage") { + this.updateSource({ + prototypeToken: { + sight: { enabled: true }, + actorLink: true, + disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY, + } + }) + } } -} } diff --git a/module/documents/chat-message.mjs b/module/documents/chat-message.mjs index e1c9b14..d9f8b6c 100644 --- a/module/documents/chat-message.mjs +++ b/module/documents/chat-message.mjs @@ -1,9 +1,8 @@ -import HamalronTirageTarot from "./tirage-tarot.mjs" - export default class HamalronChatMessage extends ChatMessage { async _renderRollContent(messageData) { const data = messageData.message - if (this.rolls[0] instanceof HamalronRoll) { + const { default: HamalronTirageTarot } = await import("./tirage-tarot.mjs") + if (this.rolls[0] instanceof HamalronTirageTarot) { const isPrivate = !this.isContentVisible // _renderRollHTML va appeler render sur tous les rolls const rollHTML = await this._renderRollHTML(isPrivate) diff --git a/module/documents/tirage-tarot.mjs b/module/documents/tirage-tarot.mjs index 8cc79b2..b9e7d83 100644 --- a/module/documents/tirage-tarot.mjs +++ b/module/documents/tirage-tarot.mjs @@ -1,7 +1,8 @@ import { SYSTEM } from "../config/system.mjs" +import HamalronUtils from "../utils.mjs" -export default class HamalronTirageTarot { +export default class HamalronTirageTarot extends Roll { /** * The HTML template path used to render dice checks of this type * @type {string} @@ -41,7 +42,7 @@ export default class HamalronTirageTarot { const newCards = deckManager.drawCards(1) if (newCards.length > 0) { const newCard = newCards[0] - const originalItem = game.items.get(newCard.id) + const originalItem = await HamalronUtils.findTarotItem(newCard) if (originalItem) { await actor.createEmbeddedDocuments("Item", [originalItem.toObject()]) } @@ -50,12 +51,42 @@ export default class HamalronTirageTarot { } // 3. Regrouper la défausse et le deck, puis mélanger - deckManager.deck.push(...deckManager.discard) - deckManager.discard = [] - deckManager.shuffleDeck() - - await deckManager.saveDeckState() + await deckManager.resetDeck() deckManager.render() + + // 4. Proposer aux autres joueurs de renouveler leur main + if (game.user.isGM) { + const refreshOthers = await foundry.applications.api.DialogV2.confirm({ + window: { title: "Le Mat - Renouvellement" }, + content: `
Le Mat a été tiré ! Les autres joueurs peuvent renouveler leur main s'ils le désirent.
`, + rejectClose: false, + modal: true, + }) + if (refreshOthers) { + for (const otherActor of game.actors.filter(a => a.type === "personnage")) { + if (otherActor.id === actor.id) continue + const otherCards = otherActor.items.filter(i => i.type === "tarot") + if (otherCards.length > 0) { + const otherCardIds = otherCards.map(c => c.id) + await otherActor.deleteEmbeddedDocuments("Item", otherCardIds) + for (let i = 0; i < 7; i++) { + if (deckManager.deck.length > 0) { + const newCards = deckManager.drawCards(1) + if (newCards.length > 0) { + const originalItem = await HamalronUtils.findTarotItem(newCards[0]) + if (originalItem) { + await otherActor.createEmbeddedDocuments("Item", [originalItem.toObject()]) + } + } + } + } + } + } + await deckManager.saveDeckState() + deckManager.render() + } + } + ui.notifications.info(`Le Mat ! Toutes vos cartes ont été défaussées et vous recevez 7 nouvelles cartes. Le deck a été mélangé.`) } @@ -78,14 +109,10 @@ export default class HamalronTirageTarot { if (options.tarotCards && actor.system.cartesSucces) { const competenceSymbole = options.rollItem.system.carteSymbole options.tarotCards = options.tarotCards.map(card => { - // Pour qu'une carte soit une carte de succès : - // 1. Elle ne doit pas être un atout - // 2. Son symbole doit correspondre à celui de la compétence - // 3. Sa valeur doit correspondre à celle d'une carte de succès pour ce symbole const isSuccessCard = card.system.type !== "atout" && card.system.symbole === competenceSymbole && actor.system.cartesSucces[competenceSymbole] && - card.system.valeur === actor.system.cartesSucces[competenceSymbole].value.toString() + String(card.system.valeur) === String(actor.system.cartesSucces[competenceSymbole].value) // Créer un objet simple avec toutes les propriétés nécessaires return { id: card.id, @@ -103,8 +130,10 @@ export default class HamalronTirageTarot { let formula = options.rollItem.system.damage if (options.rollItem?.system?.damageStat && options.rollItem.system.damageStat !== "none" && options.rollItem.system.damageStat !== "") { let statKey = options.rollItem.system?.damageStat.toLowerCase() - let statValue = actor.system.stats[statKey].value - formula = `${formula} + ${statValue}` + let statValue = actor.system?.stats?.[statKey]?.value + if (statValue != null) { + formula = `${formula} + ${statValue}` + } } let damageRoll = new Roll(formula) await damageRoll.evaluate() @@ -117,8 +146,10 @@ export default class HamalronTirageTarot { case "weapon": { options.weapon = foundry.utils.duplicate(options.rollItem) - let statKey = options.weapon.system.stat.toLowerCase() - options.rollItem = actor.system.stats[statKey] + let statKey = options.weapon.system?.stat?.toLowerCase() + if (statKey && actor.system?.stats?.[statKey]) { + options.rollItem = actor.system.stats[statKey] + } } break default: @@ -200,25 +231,6 @@ export default class HamalronTirageTarot { }, rejectClose: false, // Click on Close button will not launch an error render: (event, dialog) => { - $(".roll-stat-advantages").change(event => { - options.nbAdvantages = Number(event.target.value) - HamalronTirageTarot.updateFullFormula(options) - }) - $(".roll-stat-disadvantages").change(event => { - options.nbDisadvantages = Number(event.target.value) - HamalronTirageTarot.updateFullFormula(options) - }) - $(".select-combat-option").change(event => { - let field = $(event.target).data("field") - let modifier = SYSTEM.ATTACK_MODIFIERS[field] - if (event.target.checked) { - options.numericModifier += modifier - } else { - options.numericModifier -= modifier - } - HamalronTirageTarot.updateFullFormula(options) - }) - // Gestion de la sélection de carte pour les compétences if (options.rollType === "competence") { let selectedCardId = null @@ -342,16 +354,11 @@ export default class HamalronTirageTarot { if (selectedCard) { cardSymboleMatch = selectedCard.system.symbole === options.rollItem.system.carteSymbole - // Vérifier si c'est une carte de succès (les atouts ne peuvent jamais être des cartes de succès) - // Pour qu'une carte soit une carte de succès : - // 1. Elle ne doit pas être un atout - // 2. Son symbole doit correspondre à celui de la compétence - // 3. Sa valeur doit correspondre à celle d'une carte de succès pour ce symbole if (actor.system.cartesSucces && selectedCard.system.type !== "atout") { const competenceSymbole = options.rollItem.system.carteSymbole isAutomaticSuccess = selectedCard.system.symbole === competenceSymbole && actor.system.cartesSucces[competenceSymbole] && - selectedCard.system.valeur === actor.system.cartesSucces[competenceSymbole].value.toString() + String(selectedCard.system.valeur) === String(actor.system.cartesSucces[competenceSymbole].value) } } } @@ -359,7 +366,7 @@ export default class HamalronTirageTarot { // Préparer les données pour le chat const difficultyData = SYSTEM.DIFFICULTY_CHOICES[options.selectedDifficulty || "moyenne"] const isSousTensionOppose = options.selectedDifficulty === "soustension_oppose" - const isSuccess = isAutomaticSuccess || totalValue > options.targetScore + const isSuccess = isAutomaticSuccess || totalValue >= options.targetScore // Détecter si la carte est un valet const isValet = selectedCard?.system.valeur === "valet" @@ -468,21 +475,7 @@ export default class HamalronTirageTarot { if (newCards.length > 0) { const newCard = newCards[0] console.log("New card to add:", newCard) - // Trouver l'item tarot dans game.items - let originalItem = game.items.get(newCard.id) - console.log("Original item found by ID:", originalItem) - - // Si pas trouvé dans game.items, chercher dans tous les items disponibles - if (!originalItem) { - console.log("Searching for card in game.items by properties...") - originalItem = game.items.find(i => - i.type === "tarot" && - i.name === newCard.name && - i.system.symbole === newCard.symbole && - i.system.valeur === newCard.valeur - ) - console.log("Original item found by search:", originalItem) - } + const originalItem = await HamalronUtils.findTarotItem(newCard) if (originalItem) { // Cas spécial : Le Mat @@ -505,7 +498,7 @@ export default class HamalronTirageTarot { console.log("Deck is empty, cannot draw new card") } - deckManager.saveDeckState() + await deckManager.saveDeckState() deckManager.render() ui.notifications.info(`${game.i18n.localize("HAMALRON.Notifications.cardUsed")}: ${selectedCard.name}`) } @@ -673,7 +666,7 @@ export default class HamalronTirageTarot { * @returns {Promise} - A promise that resolves when the message is created. */ async toMessage(messageData = {}, { rollMode, create = true } = {}) { - super.toMessage( + return super.toMessage( { isFailure: this.resultType === "failure", actingCharName: this.actorName, @@ -682,7 +675,7 @@ export default class HamalronTirageTarot { realDamage: this.realDamage, ...messageData, }, - { rollMode: rollMode }, + { rollMode, create }, ) } diff --git a/module/models/arme.mjs b/module/models/arme.mjs index cba9b80..a6c918b 100644 --- a/module/models/arme.mjs +++ b/module/models/arme.mjs @@ -19,7 +19,6 @@ export default class HamalronArme extends foundry.abstract.TypeDataModel { prepareDerivedData() { super.prepareDerivedData(); - let actor = this.parent?.actor; } } diff --git a/module/models/armure.mjs b/module/models/armure.mjs index a6df96a..f287b12 100644 --- a/module/models/armure.mjs +++ b/module/models/armure.mjs @@ -9,7 +9,7 @@ export default class HamalronArmure extends foundry.abstract.TypeDataModel { schema.description = new fields.HTMLField({ required: true, textSearch: true }) schema.protection = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }) - schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 }) + schema.cout = new fields.NumberField({ required: true, initial: 0, min: 0 }) return schema } diff --git a/module/models/equipement.mjs b/module/models/equipement.mjs index 668e38e..0b37cb6 100644 --- a/module/models/equipement.mjs +++ b/module/models/equipement.mjs @@ -9,7 +9,7 @@ export default class HamalronEquipement extends foundry.abstract.TypeDataModel { schema.description = new fields.HTMLField({ required: true, textSearch: true }) schema.quantity = new fields.NumberField({ required: true, initial: 1, min: 0, integer: true }) - schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 }) + schema.cout = new fields.NumberField({ required: true, initial: 0, min: 0 }) return schema } diff --git a/module/models/langue.mjs b/module/models/langue.mjs index 9d9e13e..9c3e76e 100644 --- a/module/models/langue.mjs +++ b/module/models/langue.mjs @@ -1,6 +1,6 @@ import { SYSTEM } from "../config/system.mjs"; -export default class HamalronFaction extends foundry.abstract.TypeDataModel { +export default class HamalronLangue extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields; const schema = {}; @@ -12,5 +12,5 @@ export default class HamalronFaction extends foundry.abstract.TypeDataModel { } /** @override */ - static LOCALIZATION_PREFIXES = ["HAMALRON.Faction"]; + static LOCALIZATION_PREFIXES = ["HAMALRON.Langue"]; } \ No newline at end of file diff --git a/module/models/personnage.mjs b/module/models/personnage.mjs index 9e70596..43078d3 100644 --- a/module/models/personnage.mjs +++ b/module/models/personnage.mjs @@ -66,17 +66,13 @@ export default class HamalronPersonnage extends foundry.abstract.TypeDataModel { * @returns {Promise