// 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", weapon: "weapon", armor: "armor", sanhei: "sanhei", ingredient: "ingredient" }; 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: numberField(0, { min: 0, max: 5 }), 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 }), max: numberField(0, { min: 0 }) }), heiyin: new fields.SchemaField({ value: numberField(0, { min: 0 }), max: 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(""), threat: numberField(0, { min: 0, max: 4 }), // profane(0) | apprentice(1) | initiate(2) | accomplished(3) | renowned(4) nuisance: numberField(0, { min: 0, max: 5 }), // figurant(0) | minion(1) | adversary(2) | ally(3) | boss(4) | divinity(5) 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 { reference: stringField(""), description: htmlField(""), quantity: numberField(1, { min: 0 }), weight: numberField(0, { min: 0 }), 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("action-attack"), // action-attack | action-defense | action-aid | action-attack-defense | reaction | dice | damage-inflicted | damage-received technique: htmlField("") }); return { reference: stringField(""), description: htmlField(""), orientation: stringField("yin"), // yin | yang | yinyang aspect: stringField("metal"), // metal | eau | terre | feu | bois skill: stringField("kungfu"), // kungfu | rangedcombat 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("metal"), // metal | eau | terre | feu | bois hei: stringField(""), realizationtimeritual: stringField(""), realizationtimeaccelerated: stringField(""), flashback: stringField(""), components: htmlField(""), effects: htmlField(""), examples: htmlField(""), notes: htmlField(""), discipline: stringField("cinabre"), heiType: stringField("yin"), heiCost: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 1 }), difficulty: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 1 }) }; } }; // 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(""), heiType: stringField("yin"), heiCost: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 0 }), trigger: stringField(""), effects: htmlField("") }; } }; // src/data/items/weapon.js var WeaponDataModel = 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 intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts }); return { reference: stringField(""), description: htmlField(""), weaponType: stringField("melee"), material: stringField(""), damageAspect: stringField("metal"), damageBase: intField(1), range: stringField("contact"), // contact | courte | mediane | longue | extreme obtainLevel: intField(0, { min: 0, max: 5 }), obtainDifficulty: intField(0, { min: 0, max: 3 }), quantity: intField(1), notes: htmlField("") }; } }; // src/data/items/armor.js var ArmorDataModel = 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 intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts }); return { reference: stringField(""), description: htmlField(""), protectionValue: intField(0), domain: stringField(""), obtainLevel: intField(0, { min: 0, max: 5 }), obtainDifficulty: intField(0, { min: 0, max: 3 }), quantity: intField(1), notes: htmlField("") }; } }; // src/data/items/sanhei.js var SanheiDataModel = 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 intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts }); const propertySchema = () => new fields.SchemaField({ name: stringField(""), heiCost: intField(0), heiType: stringField("yin"), description: htmlField("") }); return { reference: stringField(""), description: htmlField(""), heiType: stringField("yin"), properties: new fields.SchemaField({ prop1: propertySchema(), prop2: propertySchema(), prop3: propertySchema() }), notes: htmlField("") }; } }; // src/data/items/ingredient.js var IngredientDataModel = 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 intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts }); return { reference: stringField(""), description: htmlField(""), school: stringField("all"), obtainLevel: intField(0, { min: 0, max: 5 }), obtainDifficulty: intField(0, { min: 0, max: 3 }), quantity: intField(1), 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/initiative.js var PC_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-prompt.html"; var NPC_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-prompt-npc.html"; var RESULT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-result.html"; function buildPCOptions(sys) { const sk = sys.skills ?? {}; const rs = sys.resources ?? {}; const mg = sys.magics ?? {}; return [ { key: "art", label: game.i18n.localize("CDE.Art"), value: sk.art?.value ?? 0 }, { key: "investigation", label: game.i18n.localize("CDE.Investigation"), value: sk.investigation?.value ?? 0 }, { key: "erudition", label: game.i18n.localize("CDE.Erudition"), value: sk.erudition?.value ?? 0 }, { key: "knavery", label: game.i18n.localize("CDE.Knavery"), value: sk.knavery?.value ?? 0 }, { key: "wordliness", label: game.i18n.localize("CDE.Wordliness"), value: sk.wordliness?.value ?? 0 }, { key: "prowess", label: game.i18n.localize("CDE.Prowess"), value: sk.prowess?.value ?? 0 }, { key: "sciences", label: game.i18n.localize("CDE.Sciences"), value: sk.sciences?.value ?? 0 }, { key: "technologies", label: game.i18n.localize("CDE.Technologies"), value: sk.technologies?.value ?? 0 }, { key: "kungfu", label: game.i18n.localize("CDE.KungFu"), value: sk.kungfu?.value ?? 0 }, { key: "rangedcombat", label: game.i18n.localize("CDE.RangedCombat"), value: sk.rangedcombat?.value ?? 0 }, { key: "supply", label: game.i18n.localize("CDE.Supply"), value: rs.supply?.value ?? 0 }, { key: "inquiry", label: game.i18n.localize("CDE.Inquiry"), value: rs.inquiry?.value ?? 0 }, { key: "influence", label: game.i18n.localize("CDE.Influence"), value: rs.influence?.value ?? 0 }, { key: "internalcinnabar", label: game.i18n.localize("CDE.InternalCinnabar"), value: mg.internalcinnabar?.value ?? 0 }, { key: "alchemy", label: game.i18n.localize("CDE.Alchemy"), value: mg.alchemy?.value ?? 0 }, { key: "masteryoftheway", label: game.i18n.localize("CDE.MasteryOfTheWay"), value: mg.masteryoftheway?.value ?? 0 }, { key: "exorcism", label: game.i18n.localize("CDE.Exorcism"), value: mg.exorcism?.value ?? 0 }, { key: "geomancy", label: game.i18n.localize("CDE.Geomancy"), value: mg.geomancy?.value ?? 0 } ]; } function buildNPCOptions(sys) { const ap = sys.aptitudes ?? {}; return [ { key: "physical", label: game.i18n.localize("CDE.Physical"), value: ap.physical?.value ?? 0 }, { key: "martial", label: game.i18n.localize("CDE.Martial"), value: ap.martial?.value ?? 0 }, { key: "mental", label: game.i18n.localize("CDE.Mental"), value: ap.mental?.value ?? 0 }, { key: "social", label: game.i18n.localize("CDE.Social"), value: ap.social?.value ?? 0 }, { key: "spiritual", label: game.i18n.localize("CDE.Spiritual"), value: ap.spiritual?.value ?? 0 } ]; } function readInitFields(dialog) { const root = dialog.element ?? dialog; const selectedKey = root.querySelector("select[name='firstaction']")?.value ?? ""; const modifier = parseInt(root.querySelector("input[name='modifier']")?.value ?? 0) || 0; return { selectedKey, modifier }; } async function sendInitChatMessage({ actor, baseName, baseValue, actionName, actionValue, modifier, initiative, antiInitiative }) { const html = await foundry.applications.handlebars.renderTemplate(RESULT_TEMPLATE, { actorName: actor.name, actorImg: actor.img, baseName, baseValue, actionName, actionValue, modifier, hasModifier: modifier !== 0, initiative, antiInitiative }); await ChatMessage.create({ user: game.user.id, speaker: ChatMessage.getSpeaker({ actor }), content: html }); } async function rollInitiativePC(actor) { const sys = actor.system; const prowess = sys.skills?.prowess?.value ?? 0; const options = buildPCOptions(sys); const baseName = game.i18n.localize("CDE.Prowess"); const content = await foundry.applications.handlebars.renderTemplate(PC_PROMPT_TEMPLATE, { prowessValue: prowess, options, modifier: 0 }); const result = await foundry.applications.api.DialogV2.prompt({ window: { title: game.i18n.localize("CDE.InitiativeRoll") }, content, rejectClose: false, ok: { label: game.i18n.localize("CDE.Validate"), callback: (_ev, _btn, dialog) => readInitFields(dialog) } }); if (!result) return; const { selectedKey, modifier } = result; const selected = options.find((o) => o.key === selectedKey) ?? options[0]; const rawValue = prowess + selected.value + modifier; const initiative = Math.max(1, Math.min(24, rawValue)); const antiInit = 25 - initiative; await actor.update({ "system.initiative": initiative }); await sendInitChatMessage({ actor, baseName, baseValue: prowess, actionName: selected.label, actionValue: selected.value, modifier, initiative, antiInitiative: antiInit }); } async function rollInitiativeNPC(actor) { const sys = actor.system; const physical = sys.aptitudes?.physical?.value ?? 0; const options = buildNPCOptions(sys); const baseName = game.i18n.localize("CDE.Physical"); const content = await foundry.applications.handlebars.renderTemplate(NPC_PROMPT_TEMPLATE, { physicalValue: physical, options, modifier: 0 }); const result = await foundry.applications.api.DialogV2.prompt({ window: { title: game.i18n.localize("CDE.InitiativeRoll") }, content, rejectClose: false, ok: { label: game.i18n.localize("CDE.Validate"), callback: (_ev, _btn, dialog) => readInitFields(dialog) } }); if (!result) return; const { selectedKey, modifier } = result; const selected = options.find((o) => o.key === selectedKey) ?? options[0]; const rawValue = physical + selected.value + modifier; const initiative = Math.max(1, Math.min(24, rawValue)); const antiInit = 25 - initiative; await actor.update({ "system.initiative": initiative }); await sendInitChatMessage({ actor, baseName, baseValue: physical, actionName: selected.label, actionValue: selected.value, modifier, initiative, antiInitiative: antiInit }); } // src/ui/rolling.js var RESULT_TEMPLATE2 = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-dice-result.html"; var SKILL_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-skill-dice-prompt.html"; var SKILL_SPECIAL_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-skill-special-dice-prompt.html"; var MAGIC_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-magic-dice-prompt.html"; var WEAPON_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-weapon-dice-prompt.html"; var LABELELEMENT_TO_ASPECT = { "CDE.Metal": "metal", "CDE.Water": "water", "CDE.Earth": "earth", "CDE.Fire": "fire", "CDE.Wood": "wood" }; var ASPECT_NAMES = ["metal", "water", "earth", "fire", "wood"]; var ASPECT_LABELS = { metal: "CDE.Metal", water: "CDE.Water", earth: "CDE.Earth", fire: "CDE.Fire", wood: "CDE.Wood" }; var ASPECT_ICONS = { metal: "systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", water: "systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", earth: "systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", fire: "systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", wood: "systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png" }; var ASPECT_FACES = { metal: [3, 8], water: [1, 6], earth: [0, 5], // 0 = face "10" fire: [2, 7], wood: [4, 9] }; var WU_XING_CYCLE = { wood: ["wood", "fire", "water", "earth", "metal"], fire: ["fire", "earth", "wood", "metal", "water"], earth: ["earth", "metal", "fire", "water", "wood"], metal: ["metal", "water", "earth", "wood", "fire"], water: ["water", "wood", "metal", "fire", "earth"] }; var RANGE_MALUS = { contact: 0, courte: 0, mediane: -1, longue: -2, extreme: -3 }; var WEAPON_TYPE_SKILL = { melee: "kungfu", thrown: "rangedcombat", ranged: "rangedcombat", firearm: "rangedcombat" }; var WEAPON_ASPECT_INDEX = { metal: 0, eau: 1, water: 1, terre: 2, earth: 2, feu: 3, fire: 3, bois: 4, wood: 4 }; function countFaces(rollResults) { const counts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 0: 0 }; for (const die of rollResults) { const face = die.result === 10 ? 0 : die.result; counts[face]++; } return counts; } function computeWuXingResults(faces, aspectName, bonusAuspicious = 0) { const cycle = WU_XING_CYCLE[aspectName]; if (!cycle) return null; const [succAspect, ausAspect, noxAspect, lokAspect, tinAspect] = cycle; const [succYin, succYang] = ASPECT_FACES[succAspect]; const [ausYin, ausYang] = ASPECT_FACES[ausAspect]; const [noxYin, noxYang] = ASPECT_FACES[noxAspect]; const [lokYin, lokYang] = ASPECT_FACES[lokAspect]; const [tinYin, tinYang] = ASPECT_FACES[tinAspect]; const yin = game.i18n.localize("CDE.Yin"); const yang = game.i18n.localize("CDE.Yang"); return { successesdice: faces[succYin] + faces[succYang], auspiciousdice: faces[ausYin] + faces[ausYang] + bonusAuspicious, noxiousdice: faces[noxYin] + faces[noxYang], loksyudice: faces[lokYin] + faces[lokYang], loksyurepartition: `[${yin}(${faces[lokYin]}) ${yang}(${faces[lokYang]})]`, tinjidice: faces[tinYin] + faces[tinYang] }; } function readField(dlg, name) { const el = dlg.querySelector(`[name="${name}"]`); if (!el) return null; return el.type === "checkbox" ? el.checked : el.value; } async function showRollPrompt({ title, template, data, fields }) { const content = await foundry.applications.handlebars.renderTemplate(template, data); return foundry.applications.api.DialogV2.prompt({ window: { title }, content, rejectClose: false, ok: { label: game.i18n.localize("CDE.Validate"), callback: (event, button, dialog) => { const root = dialog.element ?? dialog; const result = {}; for (const field of fields) { result[field] = readField(root, field); } return result; } } }); } async function showSkillPrompt(params) { return showRollPrompt({ title: params.title, template: params.isSpecial ? SKILL_SPECIAL_PROMPT_TEMPLATE : SKILL_PROMPT_TEMPLATE, data: { numberofdice: params.numberofdice, aspect: Number(params.aspect ?? 0), bonusmalus: params.bonusmalus ?? 0, woundmalus: params.woundmalus ?? 0, bonusauspiciousdice: params.bonusauspiciousdice ?? 0, typeofthrow: Number(params.typeofthrow ?? 0) }, fields: ["aspect", "bonusmalus", "woundmalus", "bonusauspiciousdice", "typeofthrow"] }); } async function showMagicPrompt(params) { return showRollPrompt({ title: params.title, template: MAGIC_PROMPT_TEMPLATE, data: { numberofdice: params.numberofdice ?? 0, aspectskill: Number(params.aspectskill ?? 0), bonusmalusskill: params.bonusmalusskill ?? 0, bonusauspiciousdice: params.bonusauspiciousdice ?? 0, aspectspeciality: Number(params.aspectspeciality ?? 0), rolldifficulty: params.rolldifficulty ?? 1, bonusmalusspeciality: params.bonusmalusspeciality ?? 0, heispend: params.heispend ?? 0, typeofthrow: Number(params.typeofthrow ?? 0) }, fields: [ "aspectskill", "bonusmalusskill", "bonusauspiciousdice", "aspectspeciality", "rolldifficulty", "bonusmalusspeciality", "heispend", "typeofthrow" ] }); } async function showWeaponPrompt(params) { return showRollPrompt({ title: params.title, template: WEAPON_PROMPT_TEMPLATE, data: { numberofdice: params.numberofdice ?? 0, weaponName: params.weaponName ?? "", weaponTypeLabel: params.weaponTypeLabel ?? "CDE.Weapon", weaponAspectIcon: params.weaponAspectIcon ?? "", weaponAspectLabel: params.weaponAspectLabel ?? "", damageBase: params.damageBase ?? 1, weaponskill: params.weaponskill ?? "kungfu", aspect: Number(params.aspect ?? 0), effectiverange: params.effectiverange ?? "contact", bonusmalus: params.bonusmalus ?? 0, woundmalus: params.woundmalus ?? 0, bonusauspiciousdice: params.bonusauspiciousdice ?? 0, typeofthrow: Number(params.typeofthrow ?? 0) }, fields: [ "weaponskill", "aspect", "effectiverange", "bonusmalus", "woundmalus", "bonusauspiciousdice", "typeofthrow" ] }); } async function sendResultMessage(actor, resultData, roll, rollMode) { const html = await foundry.applications.handlebars.renderTemplate(RESULT_TEMPLATE2, resultData); return ChatMessage.create({ user: game.user.id, speaker: ChatMessage.getSpeaker({ actor }), content: html, rolls: [roll], rollMode }); } var ROLL_MODES = ["roll", "gmroll", "blindroll", "selfroll"]; async function rollForActor(actor, rollKey) { const parts = rollKey.split("-"); const skillLibel = parts[0]; const typeLibel = parts[1]; const specialLibel = parts[2] ?? null; const sys = actor.system; const typeOfThrow = Number(sys.prefs?.typeofthrow?.choice ?? 0); let numberofdice = 0; let title = ""; let isSpecial = false; let isMagic = false; let isMagicSpecial = false; let kfDefaultAspect = -1; const MAGIC_I18N_KEYS = { internalcinnabar: "CDE.InternalCinnabar", alchemy: "CDE.Alchemy", masteryoftheway: "CDE.MasteryOfTheWay", exorcism: "CDE.Exorcism", geomancy: "CDE.Geomancy" }; switch (typeLibel) { case "aspect": numberofdice = sys.aspect[skillLibel]?.value ?? 0; title = game.i18n.localize(sys.aspect[skillLibel]?.label ?? "CDE.Roll"); break; case "skill": numberofdice = sys.skills[skillLibel]?.value ?? 0; title = game.i18n.localize(sys.skills[skillLibel]?.label ?? "CDE.Roll"); break; case "special": numberofdice = sys.skills[skillLibel]?.value ?? 0; title = game.i18n.localize(sys.skills[skillLibel]?.label ?? "CDE.Roll"); title += ` [${game.i18n.localize("CDE.Speciality")}]`; isSpecial = true; if (!sys.skills[skillLibel]?.specialities) { ui.notifications.warn(game.i18n.localize("CDE.Error2")); return; } break; case "resource": numberofdice = sys.resources[skillLibel]?.value ?? 0; title = game.i18n.localize(sys.resources[skillLibel]?.label ?? "CDE.Roll"); break; case "field": numberofdice = sys.resources[skillLibel]?.value ?? 0; title = game.i18n.localize(sys.resources[skillLibel]?.label ?? "CDE.Roll"); title += ` [${game.i18n.localize("CDE.Field")}]`; isSpecial = true; if (!sys.resources[skillLibel]?.specialities) { ui.notifications.warn(game.i18n.localize("CDE.Error4")); return; } break; case "magic": numberofdice = sys.magics[skillLibel]?.value ?? 0; isMagic = true; title = game.i18n.localize(MAGIC_I18N_KEYS[skillLibel] ?? "CDE.Magics"); break; case "magicspecial": numberofdice = sys.magics[skillLibel]?.value ?? 0; isMagicSpecial = true; isMagic = true; if (!sys.magics[skillLibel]?.speciality?.[specialLibel]?.check) { ui.notifications.warn(game.i18n.localize("CDE.Error6")); return; } title = `${game.i18n.localize(MAGIC_I18N_KEYS[skillLibel] ?? "CDE.Magics")} [${game.i18n.localize(game.system.CONST?.MAGICS?.[skillLibel]?.speciality?.[specialLibel]?.label ?? "")}]`; break; case "itemkungfu": { const kfItem = actor.items.get(skillLibel); if (!kfItem) { ui.notifications.warn(game.i18n.localize("CDE.Error0")); return; } const kfSkill = kfItem.system.skill ?? "kungfu"; numberofdice = sys.skills[kfSkill]?.value ?? 0; title = `${kfItem.name} [${game.i18n.localize(sys.skills[kfSkill]?.label ?? "CDE.KungFu")}]`; kfDefaultAspect = ASPECT_NAMES.indexOf(kfItem.system.aspect ?? "metal"); if (kfDefaultAspect < 0) kfDefaultAspect = 0; break; } case "itemweapon": { const wpItem = actor.items.get(skillLibel); if (!wpItem) { ui.notifications.warn(game.i18n.localize("CDE.Error0")); return; } const wpType = wpItem.system.weaponType ?? "melee"; const wpSkill = WEAPON_TYPE_SKILL[wpType] ?? "kungfu"; numberofdice = sys.skills[wpSkill]?.value ?? 0; const wpAspectRaw = wpItem.system.damageAspect ?? "metal"; const wpAspectIdx = WEAPON_ASPECT_INDEX[wpAspectRaw] ?? 0; const wpRange = wpItem.system.range ?? "contact"; const WEAPON_TYPE_LABELS = { melee: "CDE.WeaponMelee", thrown: "CDE.WeaponThrown", ranged: "CDE.WeaponRanged", firearm: "CDE.WeaponFirearm" }; const wParams = await showWeaponPrompt({ title: `${wpItem.name} [${game.i18n.localize(sys.skills[wpSkill]?.label ?? "CDE.WeaponRoll")}]`, numberofdice, weaponName: wpItem.name, weaponTypeLabel: WEAPON_TYPE_LABELS[wpType] ?? "CDE.Weapon", weaponAspectIcon: ASPECT_ICONS[ASPECT_NAMES[wpAspectIdx]] ?? "", weaponAspectLabel: game.i18n.localize(ASPECT_LABELS[ASPECT_NAMES[wpAspectIdx]] ?? ""), damageBase: wpItem.system.damageBase ?? 1, weaponskill: wpSkill, aspect: wpAspectIdx, effectiverange: wpRange, bonusmalus: 0, woundmalus: 0, bonusauspiciousdice: 0, typeofthrow: typeOfThrow }); if (!wParams) return; const wpChosenSkill = wParams.weaponskill ?? wpSkill; const wpSkillDice = sys.skills[wpChosenSkill]?.value ?? 0; const wpAspFinal = Number(wParams.aspect ?? wpAspectIdx); const wpAspectDice = sys.aspect[ASPECT_NAMES[wpAspFinal]]?.value ?? 0; const wpRangeMalus = RANGE_MALUS[wParams.effectiverange ?? "contact"] ?? 0; const wpBonusMalus = Number(wParams.bonusmalus ?? 0); const wpWoundMalus = Number(wParams.woundmalus ?? 0); const wpBonusAusp = Number(wParams.bonusauspiciousdice ?? 0); const wpThrowMode = Number(wParams.typeofthrow ?? 0); const wpDamageBase = wpItem.system.damageBase ?? 1; const wpTotalDice = wpSkillDice + wpAspectDice + wpRangeMalus + wpBonusMalus - wpWoundMalus; if (wpTotalDice <= 0) { ui.notifications.warn(game.i18n.localize("CDE.Error0")); return; } const wpRoll = new Roll(`${wpTotalDice}d10`); await wpRoll.evaluate(); const wpAspectName = ASPECT_NAMES[wpAspFinal] ?? "metal"; const wpFaces = countFaces(wpRoll.dice[0]?.results ?? []); const wpResults = computeWuXingResults(wpFaces, wpAspectName, wpBonusAusp); if (!wpResults) return; const wpModParts = []; if (wpRangeMalus !== 0) wpModParts.push(`${wpRangeMalus} ${game.i18n.localize("CDE.RangePenalty")}`); if (wpBonusMalus !== 0) wpModParts.push(`${wpBonusMalus > 0 ? "+" : ""}${wpBonusMalus} ${game.i18n.localize("CDE.BonusMalus")}`); if (wpWoundMalus !== 0) wpModParts.push(`-${wpWoundMalus} ${game.i18n.localize("CDE.WoundMalus")}`); if (wpBonusAusp !== 0) wpModParts.push(`+${wpBonusAusp} ${game.i18n.localize("CDE.BonusAuspiciousDice")}`); const wpMsg = await sendResultMessage(actor, { rollLabel: `${wpItem.name}`, aspectName: wpAspectName, aspectLabel: game.i18n.localize(ASPECT_LABELS[wpAspectName] ?? ""), aspectIcon: ASPECT_ICONS[wpAspectName] ?? "", totalDice: wpTotalDice, modifiersText: wpModParts.length ? wpModParts.join(" \xB7 ") : "", spellPower: null, rollDifficulty: null, actorName: actor.name ?? "", actorImg: actor.img ?? "", // weapon-specific weaponName: wpItem.name, damageBase: wpDamageBase, totalDamage: wpResults.successesdice * wpDamageBase, ...wpResults, aspect: wpAspectName, d1: wpFaces[1], d2: wpFaces[2], d3: wpFaces[3], d4: wpFaces[4], d5: wpFaces[5], d6: wpFaces[6], d7: wpFaces[7], d8: wpFaces[8], d9: wpFaces[9], d0: wpFaces[0] }, wpRoll, ROLL_MODES[wpThrowMode] ?? "roll"); if (game.modules.get("dice-so-nice")?.active && wpMsg?.id) { await game.dice3d.waitFor3DAnimationByMessageID(wpMsg.id); } return; } default: ui.notifications.warn(`Unknown roll type: ${typeLibel}`); return; } if (numberofdice <= 0 && typeLibel !== "aspect" && typeLibel !== "itemkungfu" && !isMagic) { ui.notifications.warn(game.i18n.localize("CDE.Error0")); return; } const MAGIC_ASPECTS = { internalcinnabar: 0, // metal alchemy: 1, // water masteryoftheway: 2, // earth exorcism: 3, // fire geomancy: 4 // wood }; let defaultAspect = typeLibel === "aspect" ? ["metal", "water", "earth", "fire", "wood"].indexOf(skillLibel) : 0; if (isMagic && MAGIC_ASPECTS[skillLibel] !== void 0) { defaultAspect = MAGIC_ASPECTS[skillLibel]; } if (kfDefaultAspect >= 0) { defaultAspect = kfDefaultAspect; } let defaultSpecialAspect = 0; if (isMagicSpecial && specialLibel) { const specialCfg = game.system.CONST?.MAGICS?.[skillLibel]?.speciality?.[specialLibel]; const aspectName = LABELELEMENT_TO_ASPECT[specialCfg?.labelelement]; if (aspectName) { defaultSpecialAspect = ASPECT_NAMES.indexOf(aspectName); } } let params; if (isMagic) { params = await showMagicPrompt({ title, numberofdice, aspectskill: defaultAspect, bonusmalusskill: 0, bonusauspiciousdice: 0, aspectspeciality: defaultSpecialAspect, rolldifficulty: 1, bonusmalusspeciality: 0, heispend: 0, typeofthrow: typeOfThrow }); } else { params = await showSkillPrompt({ title, numberofdice, aspect: defaultAspect, bonusmalus: 0, woundmalus: 0, bonusauspiciousdice: 0, typeofthrow: typeOfThrow, isSpecial }); } if (!params) return; let aspectIndex, bonusMalus, bonusAuspicious, throwMode; let spellAspectIndex = null; let rollDifficulty = 1; if (isMagic) { const skillAspectIndex = Number(params.aspectskill ?? 0); spellAspectIndex = Number(params.aspectspeciality ?? skillAspectIndex); aspectIndex = skillAspectIndex; bonusMalus = Number(params.bonusmalusskill ?? 0); bonusAuspicious = Number(params.bonusauspiciousdice ?? 0); rollDifficulty = Math.max(1, Number(params.rolldifficulty ?? 1)); throwMode = Number(params.typeofthrow ?? 0); const aspectDice = sys.aspect[ASPECT_NAMES[aspectIndex]]?.value ?? 0; const bonusSpec = Number(params.bonusmalusspeciality ?? 0); const heiDice = Number(params.heispend ?? 0); numberofdice = numberofdice + aspectDice + bonusMalus + 1 + bonusSpec + heiDice; } else { aspectIndex = Number(params.aspect ?? 0); bonusMalus = Number(params.bonusmalus ?? 0); const woundMalus = Number(params.woundmalus ?? 0); bonusAuspicious = Number(params.bonusauspiciousdice ?? 0); throwMode = Number(params.typeofthrow ?? 0); const aspectDice = typeLibel !== "aspect" ? sys.aspect[ASPECT_NAMES[aspectIndex]]?.value ?? 0 : 0; numberofdice = numberofdice + aspectDice + bonusMalus - woundMalus; if (isSpecial) numberofdice += 1; } if (numberofdice <= 0) { ui.notifications.warn(game.i18n.localize("CDE.Error0")); return; } const roll = new Roll(`${numberofdice}d10`); await roll.evaluate(); const rollModeKey = ROLL_MODES[throwMode] ?? "roll"; const wuXingAspectName = spellAspectIndex !== null ? ASPECT_NAMES[spellAspectIndex] : ASPECT_NAMES[aspectIndex]; const allResults = roll.dice[0]?.results ?? []; const faces = countFaces(allResults); const results = computeWuXingResults(faces, wuXingAspectName, bonusAuspicious); if (!results) return; const spellPower = isMagic ? results.successesdice * rollDifficulty : null; const modParts = []; if (isMagic) { const bm = Number(params.bonusmalusskill ?? 0); const bs = Number(params.bonusmalusspeciality ?? 0); const hs = Number(params.heispend ?? 0); const ba = Number(params.bonusauspiciousdice ?? 0); if (bm !== 0) modParts.push(`${bm > 0 ? "+" : ""}${bm} ${game.i18n.localize("CDE.BonusMalus")}`); if (bs !== 0) modParts.push(`${bs > 0 ? "+" : ""}${bs} ${game.i18n.localize("CDE.SpellBonus")}`); if (ba !== 0) modParts.push(`+${ba} ${game.i18n.localize("CDE.BonusAuspiciousDice")}`); if (hs !== 0) modParts.push(`${hs} ${game.i18n.localize("CDE.HeiSpend")}`); if (rollDifficulty !== 1) modParts.push(`\xD7${rollDifficulty} ${game.i18n.localize("CDE.RollDifficulty")}`); } else { const bm = Number(params.bonusmalus ?? 0); const wm = Number(params.woundmalus ?? 0); const ba = Number(params.bonusauspiciousdice ?? 0); if (bm !== 0) modParts.push(`${bm > 0 ? "+" : ""}${bm} ${game.i18n.localize("CDE.BonusMalus")}`); if (wm !== 0) modParts.push(`-${wm} ${game.i18n.localize("CDE.WoundMalus")}`); if (ba !== 0) modParts.push(`+${ba} ${game.i18n.localize("CDE.BonusAuspiciousDice")}`); } const msg = await sendResultMessage(actor, { // Roll identity rollLabel: title, aspectName: wuXingAspectName, aspectLabel: game.i18n.localize(ASPECT_LABELS[wuXingAspectName] ?? ""), aspectIcon: ASPECT_ICONS[wuXingAspectName] ?? "", totalDice: numberofdice, modifiersText: modParts.length ? modParts.join(" \xB7 ") : "", // Spell power (magic only) spellPower, rollDifficulty: isMagic ? rollDifficulty : null, // Actor info actorName: actor.name ?? "", actorImg: actor.img ?? "", // Wu Xing results aspect: wuXingAspectName, ...results, // Die face counts d1: faces[1], d2: faces[2], d3: faces[3], d4: faces[4], d5: faces[5], d6: faces[6], d7: faces[7], d8: faces[8], d9: faces[9], d0: faces[0] }, roll, rollModeKey); if (game.modules.get("dice-so-nice")?.active && msg?.id) { await game.dice3d.waitFor3DAnimationByMessageID(msg.id); } } // 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: 800 }, 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" }; get title() { return this.document.name; } 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, systemFields: this.document.system.schema.fields, items: this.document.items.contents, descriptionHTML, editable: this.isEditable, cssClass }; } // Restore the active tab after every render (including re-renders from submitOnChange). // AppV2 does NOT preserve tab state natively — we must re-apply it from this.tabGroups, // which is dynamically updated by changeTab() when the user clicks a tab. _onRender(context, options) { super._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", weapon: "CDE.WeaponNew", armor: "CDE.ArmorNew", sanhei: "CDE.SanheiNew", ingredient: "CDE.IngredientNew", 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.weapons = context.items.filter((item) => item.type === "weapon"); context.armors = context.items.filter((item) => item.type === "armor"); context.sanheis = context.items.filter((item) => item.type === "sanhei"); context.ingredients = context.items.filter((item) => item.type === "ingredient"); 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(); this.#bindRollButtons(); } #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") { await rollInitiativePC(this.document); } }); }); } #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 }); } }); } #bindRollButtons() { const cells = this.element?.querySelectorAll("td.click[data-libel-id], td.click2[data-libel-id], .cde-roll-trigger[data-libel-id]"); if (!cells?.length) return; cells.forEach((cell) => { cell.addEventListener("click", (event) => { event.preventDefault(); const rollKey = cell.dataset.libelId; if (rollKey) rollForActor(this.document, rollKey); }); }); } }; // 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") { await rollInitiativeNPC(this.document); } }); }); } }; // 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: "details" }; get title() { return this.document.name; } 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, systemFields: this.document.system.schema.fields, editable: this.isEditable, cssClass, enrichedDescription, enrichedNotes, descriptionHTML: enrichedDescription, notesHTML: enrichedNotes }; } // Restore the active tab after every render (including re-renders from submitOnChange). _onRender(context, options) { super._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: 560, height: 460 } }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-item-sheet.html" } }; }; // 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" } }; async _prepareContext() { const context = await super._prepareContext(); const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true }); context.effectsHTML = await enrich(this.document.system.effects); return context; } }; // src/ui/sheets/items/weapon.js var CDEWeaponSheet = class extends CDEBaseItemSheet { static DEFAULT_OPTIONS = { classes: ["weapon"], position: { width: 580, height: 520 } }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-weapon-sheet.html" } }; }; // src/ui/sheets/items/armor.js var CDEArmorSheet = class extends CDEBaseItemSheet { static DEFAULT_OPTIONS = { classes: ["armor"], position: { width: 520, height: 460 } }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-armor-sheet.html" } }; }; // src/ui/sheets/items/sanhei.js var CDESanheiSheet = class extends CDEBaseItemSheet { static DEFAULT_OPTIONS = { classes: ["sanhei"], position: { width: 580, height: 620 } }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-sanhei-sheet.html" } }; async _prepareContext() { const context = await super._prepareContext(); const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true }); const props = this.document.system.properties; context.prop1DescriptionHTML = await enrich(props.prop1.description); context.prop2DescriptionHTML = await enrich(props.prop2.description); context.prop3DescriptionHTML = await enrich(props.prop3.description); context.propFields = this.document.system.schema.fields.properties.fields; return context; } }; // src/ui/sheets/items/ingredient.js var CDEIngredientSheet = class extends CDEBaseItemSheet { static DEFAULT_OPTIONS = { classes: ["ingredient"], position: { width: 520, height: 460 } }; static PARTS = { main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-ingredient-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 (!foundry.utils.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" }; } if (actorType === "npc") { if (system.levelofthreat !== void 0 && system.threat === void 0) { updateData["system.threat"] = system.levelofthreat; updateData["system.-=levelofthreat"] = null; } if (system.powerofnuisance !== void 0 && system.nuisance === void 0) { updateData["system.nuisance"] = system.powerofnuisance; updateData["system.-=powerofnuisance"] = null; } } if (actorType === "character" && typeof system.guardian === "string") { const guardianNum = parseInt(system.guardian, 10); if (!isNaN(guardianNum)) { updateData["system.guardian"] = guardianNum; } } 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.dataModels = { [ACTOR_TYPES.character]: CharacterDataModel, [ACTOR_TYPES.npc]: NpcDataModel, [ACTOR_TYPES.tinji]: TinjiDataModel, [ACTOR_TYPES.loksyu]: LoksyuDataModel }; CONFIG.Item.dataModels = { [ITEM_TYPES.item]: EquipmentDataModel, [ITEM_TYPES.kungfu]: KungfuDataModel, [ITEM_TYPES.spell]: SpellDataModel, [ITEM_TYPES.supernatural]: SupernaturalDataModel, [ITEM_TYPES.weapon]: WeaponDataModel, [ITEM_TYPES.armor]: ArmorDataModel, [ITEM_TYPES.sanhei]: SanheiDataModel, [ITEM_TYPES.ingredient]: IngredientDataModel }; CONFIG.Actor.documentClass = CDEActor; CONFIG.Item.documentClass = CDEItem; CONFIG.ChatMessage.documentClass = CDEMessage; configureRuntime(); foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Actor, "core", ActorSheet); foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Item, "core", ItemSheet); foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDECharacterSheet, { types: [ACTOR_TYPES.character], makeDefault: true, label: "CDE Character Sheet (V2)" }); foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDENpcSheet, { types: [ACTOR_TYPES.npc], makeDefault: true, label: "CDE NPC Sheet (V2)" }); foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDETinjiSheet, { types: [ACTOR_TYPES.tinji], makeDefault: true, label: "CDE Tinji Sheet (V2)" }); foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDELoksyuSheet, { types: [ACTOR_TYPES.loksyu], makeDefault: true, label: "CDE Loksyu Sheet (V2)" }); foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEItemSheet, { types: [ITEM_TYPES.item], makeDefault: true, label: "CDE Item Sheet (V2)" }); foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEKungfuSheet, { types: [ITEM_TYPES.kungfu], makeDefault: true, label: "CDE KungFu Sheet (V2)" }); foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESpellSheet, { types: [ITEM_TYPES.spell], makeDefault: true, label: "CDE Spell Sheet (V2)" }); foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESupernaturalSheet, { types: [ITEM_TYPES.supernatural], makeDefault: true, label: "CDE Supernatural Sheet (V2)" }); foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEWeaponSheet, { types: [ITEM_TYPES.weapon], makeDefault: true, label: "CDE Weapon Sheet (V2)" }); foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEArmorSheet, { types: [ITEM_TYPES.armor], makeDefault: true, label: "CDE Armor Sheet (V2)" }); foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESanheiSheet, { types: [ITEM_TYPES.sanhei], makeDefault: true, label: "CDE Sanhei Sheet (V2)" }); foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEIngredientSheet, { types: [ITEM_TYPES.ingredient], makeDefault: true, label: "CDE Ingredient 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 = `
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.