Files
fvtt-donjon-et-cie/modules/donjon-et-cie-main.mjs
T
2026-05-01 00:37:01 +02:00

225 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 20252026 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 = `<i class="fa-solid fa-box-open" inert></i><span>${game.i18n.localize("DNC.Macro.MissionPack.SidebarButton")}</span>`;
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);
});