/** * Donjon & Cie - Systeme FoundryVTT * * Donjon & Cie est un jeu de role edite par John Doe. * Ce systeme FoundryVTT est une implementation independante et n'est pas * affilie a John Doe. * * @author LeRatierBretonnien * @copyright 2025–2026 LeRatierBretonnien * @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/ */ import { DONJON_ET_CIE } from "./donjon-et-cie-config.mjs"; import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs"; import { DonjonEtCieActor } from "./donjon-et-cie-actor.mjs"; import { DonjonEtCieItem } from "./donjon-et-cie-item.mjs"; import * as models from "./models/index.mjs"; import * as sheets from "./applications/sheets/_module.mjs"; import { DonjonEtCieRollDialog } from "./applications/donjon-et-cie-roll-dialog.mjs"; import { DonjonEtCieRolls } from "./donjon-et-cie-rolls.mjs"; import { DonjonEtCieMacros } from "./donjon-et-cie-macros.mjs"; const WELCOME_MESSAGE_SETTING = "welcomeMessageVersion"; function injectActorDirectoryMissionPackButton(app, element) { if (!game.user.isGM) return; const root = app?.element ?? element?.[0] ?? element; if (!(root instanceof HTMLElement)) return; const headerActions = root.querySelector(".directory-header .header-actions"); if (!(headerActions instanceof HTMLElement)) return; if (headerActions.querySelector(".dnc-mission-pack-button")) return; const button = document.createElement("button"); button.type = "button"; button.className = "dnc-mission-pack-button"; button.title = game.i18n.localize("DNC.Macro.MissionPack.SidebarButton"); button.setAttribute("aria-label", game.i18n.localize("DNC.Macro.MissionPack.SidebarButton")); button.innerHTML = `${game.i18n.localize("DNC.Macro.MissionPack.SidebarButton")}`; button.addEventListener("click", () => { void game.system.donjonEtCie.macros.openMissionPackDialog(); }); headerActions.append(button); } function onChatActionClick(event) { const button = event.target.closest("[data-action='rollChatDamage'], [data-action='rollSpellChaos'], [data-action='applyDamage']"); if (!(button instanceof HTMLElement)) return; event.preventDefault(); void (async () => { if (button.dataset.action === "rollSpellChaos") { const actorUuid = button.dataset.actorUuid; const itemUuid = button.dataset.itemUuid; if (!actorUuid || !itemUuid) return; const [actor, item] = await Promise.all([fromUuid(actorUuid), fromUuid(itemUuid)]); return DonjonEtCieRolls.rollSpellChaos(actor, item); } if (button.dataset.action === "applyDamage") { const card = button.closest(".dnc-chat-card-damage"); const select = card?.querySelector("[data-role='damage-target']"); const targetUuid = select instanceof HTMLSelectElement ? select.value : ""; if (!targetUuid) { ui.notifications.warn(game.i18n.localize("DNC.Chat.SelectTarget")); return null; } const target = await fromUuid(targetUuid); if (!target) { ui.notifications.warn(game.i18n.localize("DNC.Chat.TargetUnavailable")); return null; } return DonjonEtCieRolls.applyDamage(target, { damage: Number(button.dataset.damage ?? 0), useArmor: button.dataset.useArmor === "true", sourceLabel: button.dataset.sourceLabel ?? "" }); } const itemUuid = button.dataset.itemUuid; if (!itemUuid) return; const item = await fromUuid(itemUuid); return item?.rollDamage?.(); })(); } function registerSystemSettings() { game.settings.register("fvtt-donjon-et-cie", WELCOME_MESSAGE_SETTING, { name: "Version du message de bienvenue", hint: "Usage interne pour eviter de republier le message de bienvenue a chaque chargement.", scope: "world", config: false, type: String, default: "" }); } async function getHelpJournalLink() { const pack = [...game.packs.values()].find((candidate) => candidate.metadata.name === "system-help"); if (!pack) return null; const index = await pack.getIndex(); const entry = index.find((document) => document.name === "Aide du systeme"); if (!entry?._id) return null; const journal = await pack.getDocument(entry._id); if (!journal?.uuid) return null; return `@UUID[${journal.uuid}]{${game.i18n.localize("DNC.Welcome.HelpLinkLabel")}}`; } async function maybeCreateWelcomeMessage() { if (!game.user.isGM) return; const currentVersion = String(game.system.version ?? ""); const shownVersion = String(game.settings.get("fvtt-donjon-et-cie", WELCOME_MESSAGE_SETTING) ?? ""); if (shownVersion === currentVersion) return; const helpJournalLink = await getHelpJournalLink(); const content = await foundry.applications.handlebars.renderTemplate( "systems/fvtt-donjon-et-cie/templates/chat/welcome-card.hbs", { title: game.i18n.localize("DNC.Welcome.Title"), subtitle: game.i18n.format("DNC.Welcome.Subtitle", { version: currentVersion }), intro: game.i18n.localize("DNC.Welcome.Intro"), bullets: [ game.i18n.localize("DNC.Welcome.BulletActors"), game.i18n.localize("DNC.Welcome.BulletItems"), game.i18n.localize("DNC.Welcome.BulletMissionPack") ], helpLabel: game.i18n.localize("DNC.Welcome.HelpLabel"), helpLink: helpJournalLink, helpFallback: game.i18n.localize("DNC.Welcome.HelpFallback"), footer: game.i18n.localize("DNC.Welcome.Footer"), creditsLabel: game.i18n.localize("DNC.Welcome.CreditsLabel"), creditsText: game.i18n.localize("DNC.Welcome.CreditsText"), officialLabel: game.i18n.localize("DNC.Welcome.OfficialLabel"), officialUrl: "https://johndoe-rpg.com/catalogue/donjon-cie/", officialLinkText: game.i18n.localize("DNC.Welcome.OfficialLinkText") } ); await ChatMessage.create({ speaker: { alias: game.system.title }, user: game.user.id, content: await TextEditor.enrichHTML(content, { async: true }) }); await game.settings.set("fvtt-donjon-et-cie", WELCOME_MESSAGE_SETTING, currentVersion); } Hooks.once("init", async () => { const startupBanner = `▗▄▄▄ ▗▄▖ ▗▖ ▗▖ ▗▖ ▗▄▖ ▗▖ ▗▖ ▗▄▄▄▖▗▄▄▄▖ ▗▄▄▖▗▄▄▄▖▗▞▀▚▖ ▐▌ █ ▐▌ ▐▌▐▛▚▖▐▌ ▐▌▐▌ ▐▌▐▛▚▖▐▌ ▐▌ █ ▐▌ █ ▐▛▀▀▘ ▐▌ █ ▐▌ ▐▌▐▌ ▝▜▌ ▐▌▐▌ ▐▌▐▌ ▝▜▌ ▐▛▀▀▘ █ ▐▌ █ ▝▚▄▄▖ ▐▙▄▄▀ ▝▚▄▞▘▐▌ ▐▌▗▄▄▞▘▝▚▄▞▘▐▌ ▐▌ ▐▙▄▄▖ █ ▝▚▄▄▖▗▄█▄▖ `; console.log(`%c${startupBanner}`, "font-family: monospace; white-space: pre; line-height: 1.1;"); console.log("Initialisation du systeme Donjon & Cie"); registerSystemSettings(); await DonjonEtCieUtility.preloadHandlebarsTemplates(); CONFIG.Combat.initiative = { formula: "1d20 + @system.caracteristiques.dexterite.value + @system.combat.initiativeBonus", decimals: 0 }; CONFIG.Actor.documentClass = DonjonEtCieActor; CONFIG.Actor.dataModels = { employe: models.EmployeDataModel, pnj: models.PnjDataModel }; CONFIG.Item.documentClass = DonjonEtCieItem; CONFIG.Item.dataModels = { trait: models.TraitDataModel, langue: models.LangueDataModel, capacite: models.CapaciteDataModel, sortilege: models.SortilegeDataModel, arme: models.ArmeDataModel, armure: models.ArmureDataModel, equipement: models.EquipementDataModel, consommable: models.ConsommableDataModel, entrainement: models.EntrainementDataModel }; game.system.donjonEtCie = { config: DONJON_ET_CIE, models, sheets, rolls: DonjonEtCieRolls, dialogs: DonjonEtCieRollDialog, utility: DonjonEtCieUtility, macros: DonjonEtCieMacros }; foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet); foundry.documents.collections.Actors.registerSheet("fvtt-donjon-et-cie", sheets.DonjonEtCieEmployeSheet, { types: ["employe"], makeDefault: true }); foundry.documents.collections.Actors.registerSheet("fvtt-donjon-et-cie", sheets.DonjonEtCiePNJSheet, { types: ["pnj"], makeDefault: true }); foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet); for (const type of ["trait", "langue", "capacite", "sortilege", "arme", "armure", "equipement", "consommable", "entrainement"]) { foundry.documents.collections.Items.registerSheet("fvtt-donjon-et-cie", sheets.DonjonEtCieItemSheet, { types: [type], makeDefault: true }); } }); Hooks.once("ready", () => { document.addEventListener("click", onChatActionClick); void maybeCreateWelcomeMessage(); }); Hooks.on("renderActorDirectory", (app, element) => { injectActorDirectoryMissionPackButton(app, element); });