// src/config/constants.js var SYSTEM_ID = "fvtt-chroniques-de-l-etrange"; var ACTOR_TYPES = { character: "character", npc: "npc", tinji: "tinji", loksyu: "loksyu" }; var ITEM_TYPES = { item: "item", kungfu: "kungfu", spell: "spell", supernatural: "supernatural" }; var SUBTYPES = { weapon: { id: "weapon", label: "CDE.Weapon" }, armor: { id: "armor", label: "CDE.Armor" }, sanhei: { id: "sanhei", label: "CDE.Sanhei" }, other: { id: "other", label: "CDE.Other" } }; var MAGICS = { internalcinnabar: { id: "internalcinnabar", background: "linear-grey", label: "CDE.InternalCinnabar", aspectlabel: "CDE.Metal", speciality: { essence: { label: "CDE.Essence", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" }, mind: { label: "CDE.Mind", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" }, purification: { label: "CDE.Purification", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" }, manipulation: { label: "CDE.Manipulation", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" }, aura: { label: "CDE.Aura", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" } } }, alchemy: { id: "alchemy", background: "linear-blue", label: "CDE.Alchemy", aspectlabel: "CDE.Water", speciality: { acupuncture: { label: "CDE.Acupuncture", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" }, elixirs: { label: "CDE.Elixirs", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" }, poisons: { label: "CDE.Poisons", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" }, arsenal: { label: "CDE.Arsenal", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" }, potions: { label: "CDE.Potions", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" } } }, masteryoftheway: { id: "masteryoftheway", background: "linear-brown", label: "CDE.MasteryOfTheWay", aspectlabel: "CDE.Earth", speciality: { curse: { label: "CDE.Curse", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" }, transfiguration: { label: "CDE.Transfiguration", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" }, necromancy: { label: "CDE.Necromancy", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" }, climatecontrol: { label: "CDE.ClimateControl", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" }, goldenmagic: { label: "CDE.GoldenMagic", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" } } }, exorcism: { id: "exorcism", background: "linear-red", label: "CDE.Exorcism", aspectlabel: "CDE.Fire", speciality: { invocation: { label: "CDE.Invocation", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" }, tracking: { label: "CDE.Tracking", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" }, protection: { label: "CDE.Protection", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" }, punishment: { label: "CDE.Punishment", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" }, domination: { label: "CDE.Domination", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" } } }, geomancy: { id: "geomancy", background: "linear-green", label: "CDE.Geomancy", aspectlabel: "CDE.Wood", speciality: { neutralization: { label: "CDE.Neutralization", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" }, divination: { label: "CDE.Divination", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" }, earthlyprayer: { label: "CDE.EarthlyPrayer", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" }, heavenlyprayer: { label: "CDE.HeavenlyPrayer", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" }, fungseoi: { label: "CDE.Fungseoi", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" } } } }; var TEMPLATE_PARTIALS = [ "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-skills.html", "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-magics.html", "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-nghang.html", "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-treasures.html", "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-items.html", "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-kungfus.html", "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-spells.html", "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-supernaturals.html", "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-spells.html", "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-kungfus.html", "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-items.html" ]; // src/config/localize.js function preLocalizeConfig() { const localizeConfigObject = (obj, keys) => { for (const o of Object.values(obj)) { for (const key of keys) { o[key] = game.i18n.localize(o[key]); } } }; localizeConfigObject(SUBTYPES, ["label"]); Object.values(MAGICS).forEach((magic) => { magic.label = game.i18n.localize(magic.label); magic.aspectlabel = game.i18n.localize(magic.aspectlabel); Object.values(magic.speciality).forEach((spec) => { spec.label = game.i18n.localize(spec.label); spec.labelelement = game.i18n.localize(spec.labelelement); }); }); } // src/config/runtime.js function configureRuntime() { CONFIG.Actor.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/actor-banner.webp"; CONFIG.Adventure.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/adventure-banner.webp"; CONFIG.Cards.compendiumBanner = "ui/banners/cards-banner.webp"; CONFIG.Item.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/item-banner.webp"; CONFIG.JournalEntry.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/journalentry-banner.webp"; CONFIG.Macro.compendiumBanner = "ui/banners/macro-banner.webp"; CONFIG.Playlist.compendiumBanner = "ui/banners/playlist-banner.webp"; CONFIG.RollTable.compendiumBanner = "ui/banners/rolltable-banner.webp"; CONFIG.Scene.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/scene-banner.webp"; } // src/data/actors/character.js var CharacterDataModel = class extends foundry.abstract.TypeDataModel { static defineSchema() { const { fields } = foundry.data; const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra }); const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial }); const boolField = (initial = false) => new fields.BooleanField({ required: true, initial }); const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true }); const aspectField = (label, chinese) => new fields.SchemaField({ chinese: stringField(chinese), label: stringField(label), value: numberField(15, { min: 0 }) }); const skillField = (label) => new fields.SchemaField({ label: stringField(label), specialities: stringField(""), value: numberField(0, { min: 0 }) }); const resourceField = (label) => new fields.SchemaField({ label: stringField(label), specialities: stringField(""), value: numberField(0, { min: 0 }), debt: boolField(false) }); const componentField = () => new fields.SchemaField({ value: stringField("") }); const magicSpecialityField = () => new fields.SchemaField({ check: boolField(false) }); const magicField = () => new fields.SchemaField({ visible: boolField(true), value: numberField(0, { min: 0 }), speciality: new fields.SchemaField({ essence: magicSpecialityField(), mind: magicSpecialityField(), purification: magicSpecialityField(), manipulation: magicSpecialityField(), aura: magicSpecialityField(), acupuncture: magicSpecialityField(), elixirs: magicSpecialityField(), poisons: magicSpecialityField(), arsenal: magicSpecialityField(), potions: magicSpecialityField(), curse: magicSpecialityField(), transfiguration: magicSpecialityField(), necromancy: magicSpecialityField(), climatecontrol: magicSpecialityField(), goldenmagic: magicSpecialityField(), invocation: magicSpecialityField(), tracking: magicSpecialityField(), protection: magicSpecialityField(), punishment: magicSpecialityField(), domination: magicSpecialityField(), neutralization: magicSpecialityField(), divination: magicSpecialityField(), earthlyprayer: magicSpecialityField(), heavenlyprayer: magicSpecialityField(), fungseoi: magicSpecialityField() }) }); const treasureBranch = () => new fields.SchemaField({ value: numberField(0, { min: 0 }), max: numberField(0, { min: 0 }), min: numberField(0, { min: 0 }) }); const treasureLevel = () => new fields.SchemaField({ san: treasureBranch(), zing: treasureBranch() }); const schema = { concept: stringField(""), guardian: stringField("0"), initiative: numberField(1, { min: 0 }), anti_initiative: numberField(24, { min: 0 }), description: htmlField(""), prefs: new fields.SchemaField({ typeofthrow: new fields.SchemaField({ check: boolField(true), choice: stringField("0") }) }), prompt: new fields.SchemaField({ typeofthrow: new fields.SchemaField({ check: boolField(true), choice: stringField("0") }), configure: new fields.SchemaField({ numberofdice: numberField(0), aspect: numberField(0), bonus: numberField(0), bonusauspiciousdice: numberField(0), typeofthrow: numberField(0), aspectskill: numberField(0), bonusmalusskill: numberField(0), aspectspeciality: numberField(0), rolldifficulty: numberField(0), bonusmalusspeciality: numberField(0) }) }), aspect: new fields.SchemaField({ fire: aspectField("CDE.Fire", "\u328B"), earth: aspectField("CDE.Earth", "\u328F"), metal: aspectField("CDE.Metal", "\u328E"), water: aspectField("CDE.Water", "\u328C"), wood: aspectField("CDE.Wood", "\u328D") }), skills: new fields.SchemaField({ art: skillField("CDE.Art"), investigation: skillField("CDE.Investigation"), erudition: skillField("CDE.Erudition"), knavery: skillField("CDE.Knavery"), wordliness: skillField("CDE.Wordliness"), prowess: skillField("CDE.Prowess"), sciences: skillField("CDE.Sciences"), technologies: skillField("CDE.Technologies"), kungfu: skillField("CDE.KungFu"), rangedcombat: skillField("CDE.RangedCombat") }), resources: new fields.SchemaField({ supply: resourceField("CDE.Supply"), inquiry: resourceField("CDE.Inquiry"), influence: resourceField("CDE.Influence") }), component: new fields.SchemaField({ one: componentField(), two: componentField(), three: componentField(), four: componentField(), five: componentField(), six: componentField(), seven: componentField(), eight: componentField(), nine: componentField(), zero: componentField() }), magics: new fields.SchemaField({ internalcinnabar: magicField(), alchemy: magicField(), masteryoftheway: magicField(), exorcism: magicField(), geomancy: magicField() }), threetreasures: new fields.SchemaField({ heiyang: new fields.SchemaField({ value: numberField(0, { min: 0 }) }), heiyin: new fields.SchemaField({ value: numberField(0, { min: 0 }) }), dicelevel: new fields.SchemaField({ level0d: treasureLevel(), level1d: treasureLevel(), level2d: treasureLevel() }) }), experience: new fields.SchemaField({ value: numberField(0, { min: 0 }), max: numberField(0, { min: 0 }), min: numberField(0, { min: 0 }) }) }; return schema; } }; // src/data/actors/npc.js var NpcDataModel = class extends foundry.abstract.TypeDataModel { static defineSchema() { const { fields } = foundry.data; const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra }); const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial }); const boolField = (initial = false) => new fields.BooleanField({ required: true, initial }); const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true }); const aptitudeField = () => new fields.SchemaField({ value: numberField(0, { min: 0 }), speciality: stringField("") }); const trackedField = () => new fields.SchemaField({ value: numberField(0, { min: 0 }), calcul: numberField(0, { min: 0 }), note: stringField("") }); return { type: stringField(""), levelofthreat: numberField(0, { min: 0 }), powerofnuisance: numberField(0, { min: 0 }), initiative: numberField(1, { min: 0 }), anti_initiative: numberField(24, { min: 0 }), aptitudes: new fields.SchemaField({ physical: aptitudeField(), martial: aptitudeField(), mental: aptitudeField(), social: aptitudeField(), spiritual: aptitudeField() }), vitality: trackedField(), hei: trackedField(), description: htmlField(""), prefs: new fields.SchemaField({ typeofthrow: new fields.SchemaField({ check: boolField(false), choice: stringField("0") }) }) }; } }; // src/data/actors/tinji.js var TinjiDataModel = class extends foundry.abstract.TypeDataModel { static defineSchema() { const { fields } = foundry.data; const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra }); const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true }); return { value: numberField(0, { min: 0 }), description: htmlField("") }; } }; // src/data/actors/loksyu.js var LoksyuDataModel = class extends foundry.abstract.TypeDataModel { static defineSchema() { const { fields } = foundry.data; const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra }); const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true }); const polarity = () => new fields.SchemaField({ yin: new fields.SchemaField({ value: numberField(0, { min: 0 }) }), yang: new fields.SchemaField({ value: numberField(0, { min: 0 }) }) }); return { fire: polarity(), earth: polarity(), metal: polarity(), water: polarity(), wood: polarity(), description: htmlField("") }; } }; // src/data/items/item.js var EquipmentDataModel = class extends foundry.abstract.TypeDataModel { static defineSchema() { const { fields } = foundry.data; const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra }); const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial }); const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true }); return { subtype: stringField(""), reference: stringField(""), description: htmlField(""), quantity: numberField(1, { min: 0 }), weight: numberField(0, { min: 0 }), protection: stringField(""), damage: stringField(""), range: stringField(""), notes: htmlField("") }; } }; // src/data/items/kungfu.js var KungfuDataModel = class extends foundry.abstract.TypeDataModel { static defineSchema() { const { fields } = foundry.data; const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial }); const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true }); const boolField = (initial = false) => new fields.BooleanField({ required: true, initial }); const techniqueField = () => new fields.SchemaField({ check: boolField(false), name: stringField(""), activation: stringField(""), technique: htmlField("") }); return { reference: stringField(""), description: htmlField(""), orientation: stringField(""), aspect: stringField(""), skill: stringField(""), speciality: stringField(""), style: stringField(""), techniques: new fields.SchemaField({ technique1: techniqueField(), technique2: techniqueField(), technique3: techniqueField() }), notes: htmlField("") }; } }; // src/data/items/spell.js var SpellDataModel = class extends foundry.abstract.TypeDataModel { static defineSchema() { const { fields } = foundry.data; const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial }); const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true }); return { reference: stringField(""), description: htmlField(""), specialityname: stringField(""), associatedelement: stringField(""), hei: stringField(""), realizationtimeritual: stringField(""), realizationtimeaccelerated: stringField(""), flashback: stringField(""), components: htmlField(""), effects: htmlField(""), examples: htmlField(""), notes: htmlField("") }; } }; // src/data/items/supernatural.js var SupernaturalDataModel = class extends foundry.abstract.TypeDataModel { static defineSchema() { const { fields } = foundry.data; const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial }); const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true }); return { reference: stringField(""), description: htmlField(""), notes: htmlField("") }; } }; // src/documents/chat-message.js var CDEMessage = class extends ChatMessage { async renderHTML({ canDelete, canClose = false, ...rest } = {}) { const html = await super.renderHTML({ canDelete, canClose, ...rest }); this.#enrichChatCard(html); return html; } getAssociatedActor() { if (this.speaker.scene && this.speaker.token) { const scene = game.scenes.get(this.speaker.scene); const token = scene?.tokens.get(this.speaker.token); if (token) return token.actor; } return game.actors.get(this.speaker.actor); } #enrichChatCard(html) { const actor = this.getAssociatedActor(); let img; let nameText; if (this.isContentVisible) { img = actor?.img ?? this.author.avatar; nameText = this.alias; } else { img = this.author.avatar; nameText = this.author.name; } const avatar = document.createElement("a"); avatar.classList.add("avatar"); if (actor) avatar.dataset.uuid = actor.uuid; const avatarImg = document.createElement("img"); Object.assign(avatarImg, { src: img, alt: nameText }); avatar.append(avatarImg); const name = document.createElement("span"); name.classList.add("name-stacked"); const title = document.createElement("span"); title.classList.add("title"); title.append(nameText); name.append(title); const sender = html.querySelector(".message-sender"); sender?.replaceChildren(avatar, name); } }; // src/documents/actor.js var CDEActor = class extends Actor { getRollData() { const data = this.toObject(false).system; return data; } prepareBaseData() { super.prepareBaseData(); if (this.type === ACTOR_TYPES.character) { this.system.anti_initiative = 25 - (this.system.initiative ?? 0); } if (this.type === ACTOR_TYPES.npc) { this.system.vitality.calcul = (this.system.aptitudes.physical.value ?? 0) * 4; this.system.hei.calcul = (this.system.aptitudes.spiritual.value ?? 0) * 4; this.system.anti_initiative = 25 - (this.system.initiative ?? 0); } } }; // src/documents/item.js var CDEItem = class extends Item { get isWeapon() { return this.system.subtype === "weapon"; } get isArmor() { return this.system.subtype === "armor"; } get isSanhei() { return this.system.subtype === "sanhei"; } get isOther() { return this.system.subtype === "other"; } }; // src/ui/dice.js var DIGIT_LABELS = [ "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-1.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-2.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-3.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-4.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-5.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-6.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-7.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-8.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-9.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-10.webp" ]; var CLASSIC_LABELS = [ "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-1.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-2.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-3.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-4.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-5.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-6.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-7.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-8.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-9.webp", "systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-10.webp" ]; function registerDice() { Hooks.once("diceSoNiceReady", (dice3d) => { dice3d.addColorset( { name: "cde", description: "CdE", foreground: "#000000", background: "#ffffff", edge: "#ffffff", font: "DeliusUnicase", texture: "ice", material: "plastic" }, "preferred" ); dice3d.addSystem({ id: "fvtt-chroniques-de-l-etrangedigit", name: "Chroniques de l'\xE9trange digits" }, "preferred"); dice3d.addDicePreset({ type: "d10", labels: DIGIT_LABELS, system: "fvtt-chroniques-de-l-etrangedigit" }); dice3d.addSystem({ id: "fvtt-chroniques-de-l-etrange", name: "Chroniques de l'\xE9trange" }, "preferred"); dice3d.addDicePreset({ type: "d10", labels: CLASSIC_LABELS, system: "fvtt-chroniques-de-l-etrange" }); }); } // src/ui/helpers.js function registerHandlebarsHelpers() { const { Handlebars } = globalThis; if (!Handlebars) return; Handlebars.registerHelper("select", function(selected, options) { const escapedValue = RegExp.escape(Handlebars.escapeExpression(selected)); const rgx = new RegExp(` value=["']${escapedValue}["']`); const html = options.fn(this); return html.replace(rgx, "$& selected"); }); Handlebars.registerHelper("getMagicBackground", function(magic) { return game.i18n.localize(MAGICS[magic]?.background ?? ""); }); Handlebars.registerHelper("getMagicLabel", function(magic) { return game.i18n.localize(MAGICS[magic]?.label ?? ""); }); Handlebars.registerHelper("getMagicAspectLabel", function(magic) { return game.i18n.localize(MAGICS[magic]?.aspectlabel ?? ""); }); Handlebars.registerHelper("getMagicSpecialityLabel", function(magic, speciality) { return game.i18n.localize(MAGICS[magic]?.speciality?.[speciality]?.label ?? ""); }); Handlebars.registerHelper("getMagicSpecialityClassIcon", function(magic, speciality) { return MAGICS[magic]?.speciality?.[speciality]?.classicon ?? ""; }); Handlebars.registerHelper("getMagicSpecialityIcon", function(magic, speciality) { return MAGICS[magic]?.speciality?.[speciality]?.icon ?? ""; }); Handlebars.registerHelper("getMagicSpecialityElementIcon", function(magic, speciality) { return MAGICS[magic]?.speciality?.[speciality]?.elementicon ?? ""; }); Handlebars.registerHelper("getMagicSpecialityLabelIcon", function(magic, speciality) { return MAGICS[magic]?.speciality?.[speciality]?.labelicon ?? ""; }); Handlebars.registerHelper("getMagicSpecialityLabelElement", function(magic, speciality) { return game.i18n.localize(MAGICS[magic]?.speciality?.[speciality]?.labelelement ?? ""); }); } // src/ui/templates.js async function preloadPartials() { return loadTemplates(TEMPLATE_PARTIALS); } // src/ui/sheets/actors/base.js var { HandlebarsApplicationMixin } = foundry.applications.api; var CDEBaseActorSheet = class _CDEBaseActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) { static DEFAULT_OPTIONS = { classes: ["fvtt-chroniques-de-l-etrange", "actor"], position: { width: 920, height: "auto" }, window: { resizable: true }, form: { submitOnChange: true }, dragDrop: [{ dragSelector: ".item, [data-drag='true']", dropSelector: null }], actions: { create: _CDEBaseActorSheet.#onItemCreate, edit: _CDEBaseActorSheet.#onItemEdit, delete: _CDEBaseActorSheet.#onItemDelete } }; tabGroups = { primary: "description" }; async _prepareContext() { const descriptionHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true }); const cssClass = this.options.classes?.join(" ") ?? ""; return { actor: this.document, system: this.document.system, systemData: this.document.system, items: this.document.items.contents, descriptionHTML, editable: this.isEditable, cssClass }; } async _onFirstRender(context, options) { await super._onFirstRender(context, options); for (const [group, tab] of Object.entries(this.tabGroups)) { this.changeTab(tab, group, { force: true }); } } _onRender(context, options) { for (const [group, tab] of Object.entries(this.tabGroups)) { this.changeTab(tab, group, { force: true }); } } static async #onItemCreate(event, target) { const type = target.dataset.type ?? "item"; const cls = getDocumentClass("Item"); const labels = { item: "CDE.ItemNew", kungfu: "CDE.KFNew", spell: "CDE.SpellNew", supernatural: "CDE.SupernaturalNew" }; const name = game.i18n.localize(labels[type] ?? "CDE.ItemNew"); return cls.create({ name, type }, { parent: this.document }); } static #onItemEdit(event, target) { const itemId = target.closest(".item")?.dataset.itemId; const item = this.document.items.get(itemId); if (item) item.sheet.render(true); } static #onItemDelete(event, target) { const itemId = target.closest(".item")?.dataset.itemId; const item = this.document.items.get(itemId); if (item) item.delete(); } }; // src/ui/sheets/actors/character.js var CDECharacterSheet = class extends CDEBaseActorSheet { static DEFAULT_OPTIONS = { classes: ["character"] }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-character-sheet.html" } }; tabGroups = { primary: "description" }; async _prepareContext() { const context = await super._prepareContext(); 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.CDE = { MAGICS, SUBTYPES }; return context; } _onRender(context, options) { super._onRender?.(context, options); this.#bindInitiativeControls(); this.#bindPrefs(); } #bindInitiativeControls() { const buttons = this.element?.querySelectorAll(".click-initiative"); if (!buttons?.length) return; buttons.forEach((button) => { button.addEventListener("click", async () => { const action = button.dataset.libelId; let initiative = this.document.system.initiative ?? 1; if (action === "plus") { initiative = initiative >= 24 ? 1 : initiative + 1; await this.document.update({ "system.initiative": initiative }); return; } if (action === "minus") { initiative = initiative <= 1 ? 24 : initiative - 1; await this.document.update({ "system.initiative": initiative }); return; } if (action === "create") { const html = `
`; const value = await Dialog.prompt({ title: game.i18n.localize("CDE.TurnOrder"), content: html, label: game.i18n.localize("CDE.Validate"), callback: (dlg) => { const input = dlg.querySelector("input[name='initiative']"); return Number(input?.value ?? initiative); } }); if (Number.isFinite(value)) { const sanitized = foundry.utils.clamp(Number(value), 1, 24); await this.document.update({ "system.initiative": sanitized }); } } }); }); } #bindPrefs() { const button = this.element?.querySelector(".click-prefs"); if (!button) return; button.addEventListener("click", async () => { const current = this.document.system.prefs?.typeofthrow ?? { choice: "0", check: true }; const html = `
`; const prefs = await Dialog.prompt({ 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 }; } }); if (prefs) { await this.document.update({ "system.prefs.typeofthrow.choice": String(prefs.choice), "system.prefs.typeofthrow.check": !!prefs.check }); } }); } }; // src/ui/sheets/actors/npc.js var CDENpcSheet = class extends CDEBaseActorSheet { static DEFAULT_OPTIONS = { classes: ["npc"] }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-npc-sheet.html" } }; tabGroups = { primary: "description" }; 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"); return context; } _onRender(context, options) { super._onRender?.(context, options); this.#bindInitiativeControls(); } #bindInitiativeControls() { const buttons = this.element?.querySelectorAll(".click-initiative-npc"); if (!buttons?.length) return; buttons.forEach((button) => { button.addEventListener("click", async () => { const action = button.dataset.libelId; let initiative = this.document.system.initiative ?? 1; if (action === "plus") { initiative = initiative >= 24 ? 1 : initiative + 1; await this.document.update({ "system.initiative": initiative }); return; } if (action === "minus") { initiative = initiative <= 1 ? 24 : initiative - 1; await this.document.update({ "system.initiative": initiative }); return; } if (action === "create") { const html = `
`; const value = await Dialog.prompt({ title: game.i18n.localize("CDE.TurnOrder"), content: html, label: game.i18n.localize("CDE.Validate"), callback: (dlg) => Number(dlg.querySelector("input[name='initiative']")?.value ?? initiative) }); if (Number.isFinite(value)) { const sanitized = foundry.utils.clamp(Number(value), 1, 24); await this.document.update({ "system.initiative": sanitized }); } } }); }); } }; // src/ui/sheets/actors/tinji.js var CDETinjiSheet = class extends CDEBaseActorSheet { static DEFAULT_OPTIONS = { classes: ["tinji"] }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-tinji-sheet.html" } }; tabGroups = { primary: "tinji" }; }; // src/ui/sheets/actors/loksyu.js var CDELoksyuSheet = class extends CDEBaseActorSheet { static DEFAULT_OPTIONS = { classes: ["loksyu"] }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-loksyu-sheet.html" } }; tabGroups = { primary: "loksyu" }; }; // src/ui/sheets/items/base.js var { HandlebarsApplicationMixin: HandlebarsApplicationMixin2 } = foundry.applications.api; var CDEBaseItemSheet = class extends HandlebarsApplicationMixin2(foundry.applications.sheets.ItemSheetV2) { static DEFAULT_OPTIONS = { classes: ["fvtt-chroniques-de-l-etrange", "item"], position: { width: 520, height: "auto" }, window: { resizable: true }, form: { submitOnChange: true }, actions: {} }; tabGroups = { primary: "description" }; 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 }); return { item: this.document, system: this.document.system, systemData: this.document.system, editable: this.isEditable, cssClass, enrichedDescription, enrichedNotes, descriptionHTML: enrichedDescription, notesHTML: enrichedNotes }; } async _onFirstRender(context, options) { await super._onFirstRender(context, options); for (const [group, tab] of Object.entries(this.tabGroups)) { this.changeTab(tab, group, { force: true }); } } _onRender(context, options) { for (const [group, tab] of Object.entries(this.tabGroups)) { this.changeTab(tab, group, { force: true }); } } }; // src/ui/sheets/items/item.js var CDEItemSheet = class extends CDEBaseItemSheet { static DEFAULT_OPTIONS = { classes: ["equipment"], position: { width: 620, height: 580 } }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-item-sheet.html" } }; async _prepareContext() { const context = await super._prepareContext(); context.subtypes = SUBTYPES; context.isWeapon = this.document.isWeapon; context.isArmor = this.document.isArmor; context.isSanhei = this.document.isSanhei; context.isOther = this.document.isOther; return context; } }; // src/ui/sheets/items/kungfu.js var CDEKungfuSheet = class extends CDEBaseItemSheet { static DEFAULT_OPTIONS = { classes: ["kungfu"], position: { width: 720, height: 680 } }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-kungfu-sheet.html" } }; async _prepareContext() { const context = await super._prepareContext(); const techniques = this.document.system.techniques ?? {}; const enrich = (value) => foundry.applications.ux.TextEditor.implementation.enrichHTML(value ?? "", { async: true }); context.descriptionTechnique1HTML = await enrich(techniques.technique1?.technique); context.descriptionTechnique2HTML = await enrich(techniques.technique2?.technique); context.descriptionTechnique3HTML = await enrich(techniques.technique3?.technique); return context; } }; // src/ui/sheets/items/spell.js var CDESpellSheet = class extends CDEBaseItemSheet { static DEFAULT_OPTIONS = { classes: ["spell"], position: { width: 660, height: 680 } }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-spell-sheet.html" } }; async _prepareContext() { const context = await super._prepareContext(); const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true }); context.spellDescriptionHTML = await enrich(this.document.system.description); context.componentsDescriptionHTML = await enrich(this.document.system.components); context.effectsDescriptionHTML = await enrich(this.document.system.effects); context.examplesDescriptionHTML = await enrich(this.document.system.examples); return context; } }; // src/ui/sheets/items/supernatural.js var CDESupernaturalSheet = class extends CDEBaseItemSheet { static DEFAULT_OPTIONS = { classes: ["supernatural"], position: { width: 560, height: 520 } }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-supernatural-sheet.html" } }; }; // src/migration.js var MIGRATION_VERSION = "3.0.0"; function registerSettings() { game.settings.register(SYSTEM_ID, "migrationVersion", { name: "Migration version", scope: "world", config: false, type: String, default: "0.0.0" }); } async function migrateIfNeeded() { const current = game.system.version ?? MIGRATION_VERSION; const stored = game.settings.get(SYSTEM_ID, "migrationVersion") ?? "0.0.0"; if (!isNewerVersion(current, stored)) return; ui.notifications.info(`CHRONIQUESDELETRANGE | Migration vers ${current} en cours...`, { permanent: true }); await migrateActors(); await migrateItems(); await migrateCompendiumActors(); await migrateCompendiumItems(); await game.settings.set(SYSTEM_ID, "migrationVersion", current); ui.notifications.info(`CHRONIQUESDELETRANGE | Migration vers ${current} termin\xE9e.`); } async function migrateActors() { const updates = []; for (const actor of game.actors.contents) { const updateData = migrateActorData(actor); if (Object.keys(updateData).length > 0) { updates.push(actor.update(updateData, { enforceTypes: false })); } } await Promise.all(updates); } async function migrateCompendiumActors() { const packs = game.packs.filter((p) => p.documentName === "Actor" && p.metadata.system === SYSTEM_ID); for (const pack of packs) { const content = await pack.getDocuments(); for (const actor of content) { const updateData = migrateActorData(actor); if (Object.keys(updateData).length > 0) { await actor.update(updateData, { pack: pack.collection, enforceTypes: false }); } } } } async function migrateItems() { const updates = []; for (const item of game.items.contents) { const updateData = migrateItemData(item); if (Object.keys(updateData).length > 0) { updates.push(item.update(updateData, { enforceTypes: false })); } } await Promise.all(updates); } async function migrateCompendiumItems() { const packs = game.packs.filter((p) => p.documentName === "Item" && p.metadata.system === SYSTEM_ID); for (const pack of packs) { const content = await pack.getDocuments(); for (const item of content) { const updateData = migrateItemData(item); if (Object.keys(updateData).length > 0) { await item.update(updateData, { pack: pack.collection, enforceTypes: false }); } } } } function migrateActorData(actor) { const updateData = {}; const system = actor.system ?? {}; const actorType = actor.type; const legacyMagic = system.magics?.masteryofthway; if (legacyMagic && !system.magics?.masteryoftheway) { updateData["system.magics.masteryoftheway"] = legacyMagic; updateData["system.magics.-=masteryofthway"] = null; } if ((actorType === "character" || actorType === "npc") && !system.prefs?.typeofthrow) { const defaultCheck = actorType === "character"; updateData["system.prefs.typeofthrow"] = { check: defaultCheck, choice: "0" }; } return updateData; } function migrateItemData(item) { const updateData = {}; const system = item.system ?? {}; return updateData; } // src/system.js Hooks.once("i18nInit", preLocalizeConfig); Hooks.once("init", async () => { console.info(`CHRONIQUESDELETRANGE | Initializing ${SYSTEM_ID}`); registerSettings(); game.system.CONST = { MAGICS, SUBTYPES }; CONFIG.Actor.systemDataModels = { [ACTOR_TYPES.character]: CharacterDataModel, [ACTOR_TYPES.npc]: NpcDataModel, [ACTOR_TYPES.tinji]: TinjiDataModel, [ACTOR_TYPES.loksyu]: LoksyuDataModel }; CONFIG.Item.systemDataModels = { [ITEM_TYPES.item]: EquipmentDataModel, [ITEM_TYPES.kungfu]: KungfuDataModel, [ITEM_TYPES.spell]: SpellDataModel, [ITEM_TYPES.supernatural]: SupernaturalDataModel }; CONFIG.Actor.documentClass = CDEActor; CONFIG.Item.documentClass = CDEItem; CONFIG.ChatMessage.documentClass = CDEMessage; configureRuntime(); DocumentSheetConfig.unregisterSheet(Actor, "core", ActorSheet); DocumentSheetConfig.unregisterSheet(Item, "core", ItemSheet); DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDECharacterSheet, { types: [ACTOR_TYPES.character], makeDefault: true, label: "CDE Character Sheet (V2)" }); DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDENpcSheet, { types: [ACTOR_TYPES.npc], makeDefault: true, label: "CDE NPC Sheet (V2)" }); DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDETinjiSheet, { types: [ACTOR_TYPES.tinji], makeDefault: true, label: "CDE Tinji Sheet (V2)" }); DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDELoksyuSheet, { types: [ACTOR_TYPES.loksyu], makeDefault: true, label: "CDE Loksyu Sheet (V2)" }); DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEItemSheet, { types: [ITEM_TYPES.item], makeDefault: true, label: "CDE Item Sheet (V2)" }); DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEKungfuSheet, { types: [ITEM_TYPES.kungfu], makeDefault: true, label: "CDE KungFu Sheet (V2)" }); DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESpellSheet, { types: [ITEM_TYPES.spell], makeDefault: true, label: "CDE Spell Sheet (V2)" }); DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESupernaturalSheet, { types: [ITEM_TYPES.supernatural], makeDefault: true, label: "CDE Supernatural Sheet (V2)" }); await preloadPartials(); registerHandlebarsHelpers(); registerDice(); Hooks.on("renderSettings", (_app, html) => injectCompendiumLink(html)); console.info(`CHRONIQUESDELETRANGE | Initialized`); }); 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(); }); function injectCompendiumLink(html) { const header = html[0]?.querySelector?.("h4.divider"); if (!header) return; const section = document.createElement("section"); section.classList.add("settings", "flexcol"); section.innerHTML = `
Guide d'installation

Rendez-vous sur le site de l'\xE9diteur, t\xE9l\xE9chargez les PDF contenant les liens vers les compendia, puis ajoutez leurs manifestes dans Foundry.

`; section.querySelector("button[data-action='open-cde-link']")?.addEventListener("click", () => { window.open("https://antre-monde.com/les-chroniques-de-letrengae/", "_blank"); }); header.parentNode.insertBefore(section, header); } //# sourceMappingURL=system.js.map