diff --git a/dist/system.js b/dist/system.js
deleted file mode 100644
index 43bce93..0000000
--- a/dist/system.js
+++ /dev/null
@@ -1,2619 +0,0 @@
-// src/config/constants.js
-var SYSTEM_ID = "fvtt-chroniques-de-l-etrange";
-var ACTOR_TYPES = {
- character: "character",
- npc: "npc"
-};
-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.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp", labelicon: "Yin", labelelement: "CDE.Metal" },
- mind: { label: "CDE.Mind", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp", labelicon: "Yin", labelelement: "CDE.Water" },
- purification: { label: "CDE.Purification", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
- manipulation: { label: "CDE.Manipulation", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp", labelicon: "Yang", labelelement: "CDE.Fire" },
- aura: { label: "CDE.Aura", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp", 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.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp", labelicon: "Yin", labelelement: "CDE.Metal" },
- elixirs: { label: "CDE.Elixirs", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp", labelicon: "Yin", labelelement: "CDE.Water" },
- poisons: { label: "CDE.Poisons", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
- arsenal: { label: "CDE.Arsenal", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp", labelicon: "Yang", labelelement: "CDE.Fire" },
- potions: { label: "CDE.Potions", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp", 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.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp", labelicon: "Yin", labelelement: "CDE.Metal" },
- transfiguration: { label: "CDE.Transfiguration", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp", labelicon: "Yin", labelelement: "CDE.Water" },
- necromancy: { label: "CDE.Necromancy", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
- climatecontrol: { label: "CDE.ClimateControl", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp", labelicon: "Yang", labelelement: "CDE.Fire" },
- goldenmagic: { label: "CDE.GoldenMagic", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp", 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.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp", labelicon: "Yin", labelelement: "CDE.Metal" },
- tracking: { label: "CDE.Tracking", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp", labelicon: "Yin", labelelement: "CDE.Water" },
- protection: { label: "CDE.Protection", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
- punishment: { label: "CDE.Punishment", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp", labelicon: "Yang", labelelement: "CDE.Fire" },
- domination: { label: "CDE.Domination", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp", 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.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp", labelicon: "Yin", labelelement: "CDE.Metal" },
- divination: { label: "CDE.Divination", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp", labelicon: "Yin", labelelement: "CDE.Water" },
- earthlyprayer: { label: "CDE.EarthlyPrayer", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
- heavenlyprayer: { label: "CDE.HeavenlyPrayer", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp", labelicon: "Yang", labelelement: "CDE.Fire" },
- fungseoi: { label: "CDE.Fungseoi", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp", 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",
- "systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-loksyu-app.html",
- "systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-tinji-app.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/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("internalcinnabar"),
- 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 ?? "");
- });
- Handlebars.registerHelper("getMagicAspectIcon", function(magic) {
- const icons = {
- internalcinnabar: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp",
- alchemy: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
- masteryoftheway: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
- exorcism: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",
- geomancy: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp"
- };
- return icons[magic] ?? "";
- });
- Handlebars.registerHelper("getElementIcon", function(aspect) {
- const icons = {
- metal: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp",
- water: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
- earth: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
- fire: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",
- wood: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp",
- // legacy French keys
- eau: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
- terre: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
- feu: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",
- bois: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp"
- };
- return icons[aspect] ?? "";
- });
- Handlebars.registerHelper("getOrientationIcon", function(orientation) {
- const icons = {
- yin: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp",
- yang: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp",
- yinyang: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp"
- };
- return icons[orientation] ?? "";
- });
- Handlebars.registerHelper("getOrientationLabel", function(orientation) {
- const keys = {
- yin: "CDE.OrientationYin",
- yang: "CDE.OrientationYang",
- yinyang: "CDE.OrientationYinYang"
- };
- return game.i18n.localize(keys[orientation] ?? "CDE.Orientation");
- });
- Handlebars.registerHelper("getActivationLabel", function(activation) {
- const keys = {
- "action-attack": "CDE.ActivationAttack",
- "action-defense": "CDE.ActivationDefense",
- "action-aid": "CDE.ActivationAid",
- "action-attack-defense": "CDE.ActivationAttackOrDefense",
- reaction: "CDE.ActivationReaction",
- dice: "CDE.ActivationDice",
- "damage-inflicted": "CDE.ActivationDamageInflicted",
- "damage-received": "CDE.ActivationDamageReceived"
- };
- return game.i18n.localize(keys[activation] ?? "CDE.Activation");
- });
-}
-
-// src/ui/templates.js
-async function preloadPartials() {
- return foundry.applications.handlebars.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", 10) || 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/apps/singletons.js
-var SYSTEM_ID2 = "fvtt-chroniques-de-l-etrange";
-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 ASPECT_FACES = {
- metal: [3, 8],
- water: [1, 6],
- earth: [0, 5],
- fire: [2, 7],
- wood: [4, 9]
-};
-function getLoksyuData() {
- return game.settings.get(SYSTEM_ID2, "loksyuData") ?? {
- wood: { yin: 0, yang: 0 },
- fire: { yin: 0, yang: 0 },
- earth: { yin: 0, yang: 0 },
- metal: { yin: 0, yang: 0 },
- water: { yin: 0, yang: 0 }
- };
-}
-async function setLoksyuData(data) {
- await game.settings.set(SYSTEM_ID2, "loksyuData", data);
- Hooks.callAll("cde:loksyuUpdated", data);
-}
-function getTinjiValue() {
- return game.settings.get(SYSTEM_ID2, "tinjiData") ?? 0;
-}
-async function setTinjiValue(value) {
- await game.settings.set(SYSTEM_ID2, "tinjiData", Math.max(0, value));
- Hooks.callAll("cde:tinjiUpdated", Math.max(0, value));
-}
-async function updateLoksyuFromRoll(activeAspect, faces) {
- const cycle = WU_XING_CYCLE[activeAspect];
- if (!cycle) return;
- const lokAspect = cycle[3];
- const [yinFace, yangFace] = ASPECT_FACES[lokAspect] ?? [];
- if (yinFace === void 0) return;
- const yinCount = faces[yinFace] ?? 0;
- const yangCount = faces[yangFace] ?? 0;
- if (yinCount === 0 && yangCount === 0) return;
- const data = getLoksyuData();
- const current = data[lokAspect] ?? { yin: 0, yang: 0 };
- data[lokAspect] = {
- yin: (current.yin ?? 0) + yinCount,
- yang: (current.yang ?? 0) + yangCount
- };
- await setLoksyuData(data);
-}
-async function updateTinjiFromRoll(count) {
- if (!count || count <= 0) return;
- const current = getTinjiValue();
- await setTinjiValue(current + count);
-}
-
-// 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.webp",
- water: "systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
- earth: "systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
- fire: "systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",
- wood: "systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp"
-};
-var ASPECT_FACES2 = {
- metal: [3, 8],
- water: [1, 6],
- earth: [0, 5],
- // 0 = face "10"
- fire: [2, 7],
- wood: [4, 9]
-};
-var WU_XING_CYCLE2 = {
- 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_CYCLE2[aspectName];
- if (!cycle) return null;
- const [succAspect, ausAspect, noxAspect, lokAspect, tinAspect] = cycle;
- const [succYin, succYang] = ASPECT_FACES2[succAspect];
- const [ausYin, ausYang] = ASPECT_FACES2[ausAspect];
- const [noxYin, noxYang] = ASPECT_FACES2[noxAspect];
- const [lokYin, lokYang] = ASPECT_FACES2[lokAspect];
- const [tinYin, tinYang] = ASPECT_FACES2[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,
- flags: {
- "fvtt-chroniques-de-l-etrange": { rollResult: { ...resultData } }
- }
- });
-}
-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 "aptitude":
- numberofdice = sys.aptitudes?.[skillLibel]?.value ?? 0;
- title = game.i18n.localize(`CDE.${skillLibel.charAt(0).toUpperCase() + skillLibel.slice(1)}`);
- 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(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);
- }
- if ((wpResults.loksyudice ?? 0) > 0) await updateLoksyuFromRoll(wpAspectName, wpFaces);
- if ((wpResults.tinjidice ?? 0) > 0) await updateTinjiFromRoll(wpResults.tinjidice);
- 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 = 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);
- }
- if ((results.loksyudice ?? 0) > 0) await updateLoksyuFromRoll(wuXingAspectName, faces);
- if ((results.tinjidice ?? 0) > 0) await updateTinjiFromRoll(results.tinjidice);
-}
-
-// 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,
- editImage: _CDEBaseActorSheet.#onEditImage
- }
- };
- 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");
- const systemData = {};
- if (type === "spell" && target.dataset.discipline) {
- systemData.discipline = target.dataset.discipline;
- }
- return cls.create({ name, type, system: systemData }, { parent: this.document });
- }
- static #onItemEdit(event, target) {
- const itemId = target.dataset.itemId ?? target.closest("[data-item-id]")?.dataset.itemId;
- const item = this.document.items.get(itemId);
- if (item) item.sheet.render(true);
- }
- static #onItemDelete(event, target) {
- const itemId = target.dataset.itemId ?? target.closest("[data-item-id]")?.dataset.itemId;
- const item = this.document.items.get(itemId);
- if (item) item.delete();
- }
- static async #onEditImage(event, target) {
- const attr = target.dataset.edit;
- const current = foundry.utils.getProperty(this.document, attr);
- const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {};
- const fp = new FilePicker({
- current,
- type: "image",
- redirectToRoot: img ? [img] : [],
- callback: (path) => this.document.update({ [attr]: path }),
- top: this.position.top + 40,
- left: this.position.left + 10
- });
- return fp.browse();
- }
-};
-
-// 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 };
- const spellsByDiscipline = {};
- for (const spell of context.spells) {
- const disc = spell.system?.discipline ?? "internalcinnabar";
- if (!spellsByDiscipline[disc]) spellsByDiscipline[disc] = [];
- spellsByDiscipline[disc].push(spell);
- }
- const systemMagics = context.systemData.magics ?? {};
- context.magicsDisplay = Object.fromEntries(
- Object.entries(MAGICS).map(([magicKey, magicDef]) => {
- const magicData = systemMagics[magicKey] ?? {};
- return [
- magicKey,
- {
- value: magicData.value ?? 0,
- visible: magicData.visible ?? false,
- speciality: Object.fromEntries(
- Object.keys(magicDef.speciality).map((specKey) => [
- specKey,
- { check: magicData.speciality?.[specKey]?.check ?? false }
- ])
- ),
- grimoire: spellsByDiscipline[magicKey] ?? []
- }
- ];
- })
- );
- return context;
- }
- _onRender(context, options) {
- super._onRender(context, options);
- this.#bindInitiativeControls();
- this.#bindPrefs();
- this.#bindRollButtons();
- this.#bindComponentRandomize();
- }
- #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 foundry.applications.api.DialogV2.prompt({
- window: { title: game.i18n.localize("CDE.Preferences") },
- content: html,
- rejectClose: false,
- ok: {
- label: game.i18n.localize("CDE.Validate"),
- callback: (_ev, _btn, dialog) => {
- const root = dialog.element ?? dialog;
- const choice = root.querySelector("select[name='choice']")?.value ?? "0";
- const check = root.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);
- });
- });
- }
- #bindComponentRandomize() {
- const btn = this.element?.querySelector("[data-action='randomize-component']");
- if (!btn) return;
- btn.addEventListener("click", async () => {
- const roll = new Roll("1d10");
- await roll.evaluate();
- const face = roll.total === 10 ? 0 : roll.total;
- const COMPONENT_KEYS = {
- 1: "one",
- 2: "two",
- 3: "three",
- 4: "four",
- 5: "five",
- 6: "six",
- 7: "seven",
- 8: "eight",
- 9: "nine",
- 0: "zero"
- };
- const componentKey = COMPONENT_KEYS[face];
- const componentValue = this.document.system.component?.[componentKey]?.value ?? "";
- const label = componentValue ? `${componentValue}` : `${game.i18n.localize("CDE.Component")}${face}`;
- const content = `
-
- ${game.i18n.localize("CDE.ChanceThrowResult")}
- ${label}
-
`;
- await ChatMessage.create({
- user: game.user.id,
- speaker: ChatMessage.getSpeaker({ actor: this.document }),
- content,
- rolls: [roll],
- rollMode: game.settings.get("core", "rollMode") ?? "roll"
- });
- });
- }
-};
-
-// 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.weapons = context.items.filter((item) => item.type === "weapon");
- context.armors = context.items.filter((item) => item.type === "armor");
- context.equipments = context.items.filter((item) => item.type === "item");
- return context;
- }
- _onRender(context, options) {
- super._onRender(context, options);
- this.#bindInitiativeControls();
- this.#bindRollButtons();
- }
- #bindRollButtons() {
- const cells = this.element?.querySelectorAll(".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);
- });
- });
- }
- #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/items/base.js
-var { HandlebarsApplicationMixin: HandlebarsApplicationMixin2 } = foundry.applications.api;
-var CDEBaseItemSheet = class _CDEBaseItemSheet 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: {
- editImage: _CDEBaseItemSheet.#onEditImage
- }
- };
- 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 });
- }
- }
- static async #onEditImage(event, target) {
- const attr = target.dataset.edit;
- const current = foundry.utils.getProperty(this.document, attr);
- const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {};
- const fp = new FilePicker({
- current,
- type: "image",
- redirectToRoot: img ? [img] : [],
- callback: (path) => this.document.update({ [attr]: path }),
- top: this.position.top + 40,
- left: this.position.left + 10
- });
- return fp.browse();
- }
-};
-
-// 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/ui/apps/loksyu-app.js
-var SYSTEM_ID3 = "fvtt-chroniques-de-l-etrange";
-var CDELoksyuApp = class _CDELoksyuApp extends foundry.applications.api.HandlebarsApplicationMixin(
- foundry.applications.api.ApplicationV2
-) {
- static DEFAULT_OPTIONS = {
- id: "cde-loksyu-app",
- tag: "div",
- window: {
- title: "CDE.Loksyu",
- icon: "fas fa-yin-yang",
- resizable: false
- },
- classes: ["cde-app", "cde-loksyu-standalone"],
- position: { width: 520, height: "auto" },
- actions: {
- resetElement: _CDELoksyuApp.#onResetElement,
- resetAll: _CDELoksyuApp.#onResetAll
- }
- };
- static PARTS = {
- main: {
- template: `systems/${SYSTEM_ID3}/templates/apps/cde-loksyu-app.html`
- }
- };
- /** @type {Function|null} bound hook handler */
- _updateHook = null;
- /** Singleton accessor — open or bring to front */
- static open() {
- const existing = Array.from(foundry.applications.instances.values()).find(
- (app2) => app2 instanceof _CDELoksyuApp
- );
- if (existing) {
- existing.bringToFront();
- return existing;
- }
- const app = new _CDELoksyuApp();
- app.render(true);
- return app;
- }
- async _prepareContext() {
- const sys = getLoksyuData();
- const ELEMENTS = [
- { key: "wood", nameKey: "CDE.Wood", qualKey: "CDE.WoodQualities", img: `systems/${SYSTEM_ID3}/images/cde_bois.webp` },
- { key: "fire", nameKey: "CDE.Fire", qualKey: "CDE.FireQualities", img: `systems/${SYSTEM_ID3}/images/cde_feu.webp` },
- { key: "earth", nameKey: "CDE.Earth", qualKey: "CDE.EarthQualities", img: `systems/${SYSTEM_ID3}/images/cde_terre.webp` },
- { key: "metal", nameKey: "CDE.Metal", qualKey: "CDE.MetalQualities", img: `systems/${SYSTEM_ID3}/images/cde_metal.webp` },
- { key: "water", nameKey: "CDE.Water", qualKey: "CDE.WaterQualities", img: `systems/${SYSTEM_ID3}/images/cde_eau.webp` }
- ];
- return {
- canEdit: game.user.isGM,
- elements: ELEMENTS.map((el) => ({
- ...el,
- yang: sys[el.key]?.yang ?? 0,
- yin: sys[el.key]?.yin ?? 0
- }))
- };
- }
- _onRender(context, options) {
- super._onRender(context, options);
- this.#bindInputs();
- this._updateHook = Hooks.on("cde:loksyuUpdated", () => this.render());
- }
- _onClose(options) {
- if (this._updateHook !== null) {
- Hooks.off("cde:loksyuUpdated", this._updateHook);
- this._updateHook = null;
- }
- super._onClose(options);
- }
- #bindInputs() {
- const inputs = this.element?.querySelectorAll("input[data-field]");
- if (!inputs?.length) return;
- inputs.forEach((input) => {
- input.addEventListener("change", async (ev) => {
- const field = ev.currentTarget.dataset.field;
- const val = parseInt(ev.currentTarget.value, 10);
- if (!field || isNaN(val)) return;
- const [aspect, dim] = field.split(".");
- if (!aspect || !dim) return;
- const data = getLoksyuData();
- if (!data[aspect]) data[aspect] = { yin: 0, yang: 0 };
- data[aspect][dim] = Math.max(0, val);
- await setLoksyuData(data);
- });
- });
- }
- static async #onResetElement(event, target) {
- const key = target.dataset.element;
- if (!key) return;
- const data = getLoksyuData();
- data[key] = { yin: 0, yang: 0 };
- await setLoksyuData(data);
- }
- static async #onResetAll(_event, _target) {
- const KEYS = ["wood", "fire", "earth", "metal", "water"];
- const data = getLoksyuData();
- for (const k of KEYS) data[k] = { yin: 0, yang: 0 };
- await setLoksyuData(data);
- }
-};
-
-// src/ui/apps/tinji-app.js
-var SYSTEM_ID4 = "fvtt-chroniques-de-l-etrange";
-var CDETinjiApp = class _CDETinjiApp extends foundry.applications.api.HandlebarsApplicationMixin(
- foundry.applications.api.ApplicationV2
-) {
- static DEFAULT_OPTIONS = {
- id: "cde-tinji-app",
- tag: "div",
- window: {
- title: "CDE.TinJi2",
- icon: "fas fa-star",
- resizable: false
- },
- classes: ["cde-app", "cde-tinji-standalone"],
- position: { width: 320, height: "auto" },
- actions: {
- increment: _CDETinjiApp.#onIncrement,
- decrement: _CDETinjiApp.#onDecrement,
- reset: _CDETinjiApp.#onReset,
- spend: _CDETinjiApp.#onSpend
- }
- };
- static PARTS = {
- main: {
- template: `systems/${SYSTEM_ID4}/templates/apps/cde-tinji-app.html`
- }
- };
- /** @type {Function|null} */
- _updateHook = null;
- static open() {
- const existing = Array.from(foundry.applications.instances.values()).find(
- (app2) => app2 instanceof _CDETinjiApp
- );
- if (existing) {
- existing.bringToFront();
- return existing;
- }
- const app = new _CDETinjiApp();
- app.render(true);
- return app;
- }
- async _prepareContext() {
- return {
- canEdit: game.user.isGM,
- value: getTinjiValue()
- };
- }
- _onRender(context, options) {
- super._onRender(context, options);
- this.#bindDirectInput();
- this._updateHook = Hooks.on("cde:tinjiUpdated", () => this.render());
- }
- _onClose(options) {
- if (this._updateHook !== null) {
- Hooks.off("cde:tinjiUpdated", this._updateHook);
- this._updateHook = null;
- }
- super._onClose(options);
- }
- #bindDirectInput() {
- const input = this.element?.querySelector("input.cde-tinji-direct");
- if (!input) return;
- input.addEventListener("change", async (ev) => {
- const val = parseInt(ev.currentTarget.value, 10);
- if (!isNaN(val)) await setTinjiValue(val);
- });
- }
- static async #onIncrement() {
- await setTinjiValue(getTinjiValue() + 1);
- }
- static async #onDecrement() {
- const current = getTinjiValue();
- if (current <= 0) return;
- await setTinjiValue(current - 1);
- }
- static async #onReset() {
- await setTinjiValue(0);
- }
- static async #onSpend() {
- const current = getTinjiValue();
- if (current <= 0) {
- ui.notifications.warn(game.i18n.localize("CDE.TinjiEmpty"));
- return;
- }
- await setTinjiValue(current - 1);
- ChatMessage.create({
- user: game.user.id,
- content: `
-
- ${game.i18n.localize("CDE.TinJi2")}
- ${game.i18n.format("CDE.TinjiSpent", { name: game.user.name })}
- (${current - 1} ${game.i18n.localize("CDE.TinjiRemaining")})
-
`
- });
- }
-};
-
-// 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"
- });
- game.settings.register(SYSTEM_ID, "loksyuData", {
- name: "Loksyu Data",
- scope: "world",
- config: false,
- type: Object,
- default: { wood: { yin: 0, yang: 0 }, fire: { yin: 0, yang: 0 }, earth: { yin: 0, yang: 0 }, metal: { yin: 0, yang: 0 }, water: { yin: 0, yang: 0 } }
- });
- game.settings.register(SYSTEM_ID, "tinjiData", {
- name: "TinJi Data",
- scope: "world",
- config: false,
- type: Number,
- default: 0
- });
-}
-async function migrateIfNeeded() {
- const current = 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));
- }
- }
- 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 });
- }
- }
- }
-}
-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));
- }
- }
- 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 });
- }
- }
- }
-}
-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 ?? {};
- if (item.type === "weapon") {
- const ASPECT_FR_TO_EN = { eau: "water", terre: "earth", feu: "fire", bois: "wood" };
- const normalized = ASPECT_FR_TO_EN[system.damageAspect];
- if (normalized) {
- updateData["system.damageAspect"] = normalized;
- }
- }
- return updateData;
-}
-
-// src/ui/roll-actions.js
-var SYSTEM_ID5 = "fvtt-chroniques-de-l-etrange";
-var RESULT_TEMPLATE3 = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-dice-result.html";
-var WU_XING_CYCLE3 = {
- 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 ASPECT_LABELS2 = {
- metal: "CDE.Metal",
- water: "CDE.Water",
- earth: "CDE.Earth",
- fire: "CDE.Fire",
- wood: "CDE.Wood"
-};
-var ASPECT_ICONS2 = {
- metal: "systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp",
- water: "systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
- earth: "systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
- fire: "systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",
- wood: "systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp"
-};
-function injectRollActions(message, html) {
- const rollCard = html.querySelector(".cde-roll-result");
- if (!rollCard) return;
- const aspect = rollCard.dataset.aspect;
- if (!aspect || !WU_XING_CYCLE3[aspect]) return;
- refreshRollActions(rollCard, aspect, message);
-}
-function refreshRollActions(rollCard, aspect, message) {
- rollCard.querySelector(".cde-roll-actions")?.remove();
- const cycle = WU_XING_CYCLE3[aspect];
- const fasteAspect = cycle[1];
- const loksyu = getLoksyuData();
- const tinji = getTinjiValue();
- const successAvail = (loksyu[aspect]?.yin ?? 0) + (loksyu[aspect]?.yang ?? 0);
- const fasteAvail = (loksyu[fasteAspect]?.yin ?? 0) + (loksyu[fasteAspect]?.yang ?? 0);
- const isGM = game.user.isGM;
- const hasSomething = successAvail > 0 || fasteAvail > 0 || isGM && tinji > 0;
- if (!hasSomething) return;
- const aspLabel = game.i18n.localize(ASPECT_LABELS2[aspect]);
- const fasteLabel = game.i18n.localize(ASPECT_LABELS2[fasteAspect]);
- let btns = "";
- if (successAvail > 0) {
- btns += ``;
- }
- if (fasteAvail > 0) {
- btns += ``;
- }
- if (isGM && tinji > 0) {
- btns += ``;
- }
- const wrapper = document.createElement("div");
- wrapper.className = "cde-roll-actions";
- wrapper.innerHTML = `
-
-
- ${game.i18n.localize("CDE.PostRollActions")}
-
- ${btns}
- `;
- rollCard.appendChild(wrapper);
- wrapper.addEventListener("click", async (ev) => {
- const btn = ev.target.closest("[data-action]");
- if (!btn || btn.disabled) return;
- const action = btn.dataset.action;
- if (action === "loksyu-success") {
- await _drawFromLoksyu(message, aspect, "success", aspLabel);
- } else if (action === "loksyu-faste") {
- await _drawFromLoksyu(message, fasteAspect, "faste", fasteLabel);
- } else if (action === "tinji") {
- await _spendTinjiPostRoll();
- }
- if (action === "tinji") refreshRollActions(rollCard, aspect, message);
- });
-}
-async function _drawFromLoksyu(message, aspect, type, aspectLabel) {
- const data = getLoksyuData();
- const entry = data[aspect] ?? { yin: 0, yang: 0 };
- const total = entry.yin + entry.yang;
- if (total <= 0) {
- ui.notifications.warn(game.i18n.localize("CDE.LoksyuEmpty"));
- return;
- }
- if (entry.yang > 0) entry.yang--;
- else entry.yin--;
- data[aspect] = entry;
- await setLoksyuData(data);
- const flags = message?.flags?.[SYSTEM_ID5];
- if (flags?.rollResult && message.isOwner) {
- const updated = foundry.utils.deepClone(flags.rollResult);
- if (type === "success") {
- updated.successesdice = (updated.successesdice ?? 0) + 1;
- updated.loksyuBonusSuc = (updated.loksyuBonusSuc ?? 0) + 1;
- if (updated.damageBase) updated.totalDamage = updated.successesdice * updated.damageBase;
- } else {
- updated.auspiciousdice = (updated.auspiciousdice ?? 0) + 1;
- updated.loksyuBonusFaste = (updated.loksyuBonusFaste ?? 0) + 1;
- }
- const newHtml = await foundry.applications.handlebars.renderTemplate(RESULT_TEMPLATE3, updated);
- await message.update({
- content: newHtml,
- [`flags.${SYSTEM_ID5}.rollResult`]: updated
- });
- }
- const remain = entry.yin + entry.yang;
- const typeLabel = type === "success" ? game.i18n.localize("CDE.Successes") : game.i18n.localize("CDE.AuspiciousDie");
- ChatMessage.create({
- user: game.user.id,
- content: `
-
-
-
`
- });
-}
-async function _spendTinjiPostRoll() {
- if (!game.user.isGM) return;
- const current = getTinjiValue();
- if (current <= 0) {
- ui.notifications.warn(game.i18n.localize("CDE.TinjiEmpty"));
- return;
- }
- await setTinjiValue(current - 1);
- ChatMessage.create({
- user: game.user.id,
- content: `
- \u5929
-
- ${game.user.name} ${game.i18n.localize("CDE.TinjiSpent").replace("{name}", game.user.name)}
-
- (${current - 1} ${game.i18n.localize("CDE.TinjiRemaining")})
-
`
- });
-}
-function refreshAllRollActions() {
- document.querySelectorAll(".chat-message .cde-roll-result[data-aspect]").forEach((card) => {
- const aspect = card.dataset.aspect;
- if (!aspect || !WU_XING_CYCLE3[aspect]) return;
- const msgEl = card.closest("[data-message-id]");
- const msgId = msgEl?.dataset?.messageId;
- const message = msgId ? game.messages.get(msgId) : null;
- refreshRollActions(card, aspect, message);
- });
-}
-
-// src/system.js
-Hooks.once("i18nInit", preLocalizeConfig);
-Hooks.once("init", async () => {
- console.info(`CHRONIQUESDELETRANGE | Initializing ${SYSTEM_ID}`);
- registerSettings();
- game.system.CONST = { MAGICS, SUBTYPES };
- game.cde = { CDELoksyuApp, CDETinjiApp };
- CONFIG.Actor.dataModels = {
- [ACTOR_TYPES.character]: CharacterDataModel,
- [ACTOR_TYPES.npc]: NpcDataModel
- };
- 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", foundry.appv1.sheets.ActorSheet);
- foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Item, "core", foundry.appv1.sheets.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(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 () => {
- await migrateIfNeeded();
-});
-Hooks.on("renderChatLog", (_app, html) => {
- const el = html instanceof HTMLElement ? html : html[0] ?? html;
- if (!el?.querySelector) return;
- if (el.querySelector(".cde-chat-app-buttons")) return;
- const wrapper = document.createElement("div");
- wrapper.classList.add("cde-chat-app-buttons");
- wrapper.innerHTML = `
-
-
- `;
- wrapper.addEventListener("click", (ev) => {
- if (ev.target.closest(".cde-chat-btn--loksyu")) CDELoksyuApp.open();
- if (ev.target.closest(".cde-chat-btn--tinji")) CDETinjiApp.open();
- });
- const anchor = el.querySelector(".chat-form") ?? el.querySelector(".chat-message-form") ?? el.querySelector("form");
- if (anchor) anchor.parentElement.insertBefore(wrapper, anchor);
- else el.appendChild(wrapper);
-});
-Hooks.on("renderChatMessageHTML", (message, html) => {
- injectRollActions(message, html);
-});
-Hooks.on("updateSetting", (setting) => {
- if (!setting.key) return;
- if (setting.key.includes("loksyuData") || setting.key.includes("tinjiData")) {
- refreshAllRollActions();
- }
-});
-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 = `
-
-
- Lien utile
-
-
-
-
- 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-letrange/", "_blank");
- });
- header.parentNode.insertBefore(section, header);
-}
-//# sourceMappingURL=system.js.map
diff --git a/dist/system.js.map b/dist/system.js.map
deleted file mode 100644
index 90e2063..0000000
--- a/dist/system.js.map
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "version": 3,
- "sources": ["../src/config/constants.js", "../src/config/localize.js", "../src/config/runtime.js", "../src/data/actors/character.js", "../src/data/actors/npc.js", "../src/data/items/item.js", "../src/data/items/kungfu.js", "../src/data/items/spell.js", "../src/data/items/supernatural.js", "../src/data/items/weapon.js", "../src/data/items/armor.js", "../src/data/items/sanhei.js", "../src/data/items/ingredient.js", "../src/documents/chat-message.js", "../src/documents/actor.js", "../src/documents/item.js", "../src/ui/dice.js", "../src/ui/helpers.js", "../src/ui/templates.js", "../src/ui/initiative.js", "../src/ui/apps/singletons.js", "../src/ui/rolling.js", "../src/ui/sheets/actors/base.js", "../src/ui/sheets/actors/character.js", "../src/ui/sheets/actors/npc.js", "../src/ui/sheets/items/base.js", "../src/ui/sheets/items/item.js", "../src/ui/sheets/items/kungfu.js", "../src/ui/sheets/items/spell.js", "../src/ui/sheets/items/supernatural.js", "../src/ui/sheets/items/weapon.js", "../src/ui/sheets/items/armor.js", "../src/ui/sheets/items/sanhei.js", "../src/ui/sheets/items/ingredient.js", "../src/ui/apps/loksyu-app.js", "../src/ui/apps/tinji-app.js", "../src/migration.js", "../src/ui/roll-actions.js", "../src/system.js"],
- "sourcesContent": ["export const SYSTEM_ID = \"fvtt-chroniques-de-l-etrange\"\n\nexport const ACTOR_TYPES = {\n character: \"character\",\n npc: \"npc\",\n}\n\nexport const ITEM_TYPES = {\n item: \"item\",\n kungfu: \"kungfu\",\n spell: \"spell\",\n supernatural: \"supernatural\",\n weapon: \"weapon\",\n armor: \"armor\",\n sanhei: \"sanhei\",\n ingredient: \"ingredient\",\n}\n\nexport const SUBTYPES = {\n weapon: { id: \"weapon\", label: \"CDE.Weapon\" },\n armor: { id: \"armor\", label: \"CDE.Armor\" },\n sanhei: { id: \"sanhei\", label: \"CDE.Sanhei\" },\n other: { id: \"other\", label: \"CDE.Other\" },\n}\n\nexport const MAGICS = {\n internalcinnabar: {\n id: \"internalcinnabar\",\n background: \"linear-grey\",\n label: \"CDE.InternalCinnabar\",\n aspectlabel: \"CDE.Metal\",\n speciality: {\n essence: { label: \"CDE.Essence\", classicon: \"icon-yin\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp\", labelicon: \"Yin\", labelelement: \"CDE.Metal\" },\n mind: { label: \"CDE.Mind\", classicon: \"icon-yin\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp\", labelicon: \"Yin\", labelelement: \"CDE.Water\" },\n purification: { label: \"CDE.Purification\", classicon: \"icon-yinyang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp\", labelicon: \"Yin/Yang\", labelelement: \"CDE.Earth\" },\n manipulation: { label: \"CDE.Manipulation\", classicon: \"icon-yang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp\", labelicon: \"Yang\", labelelement: \"CDE.Fire\" },\n aura: { label: \"CDE.Aura\", classicon: \"icon-yang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp\", labelicon: \"Yang\", labelelement: \"CDE.Wood\" },\n },\n },\n alchemy: {\n id: \"alchemy\",\n background: \"linear-blue\",\n label: \"CDE.Alchemy\",\n aspectlabel: \"CDE.Water\",\n speciality: {\n acupuncture: { label: \"CDE.Acupuncture\", classicon: \"icon-yin\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp\", labelicon: \"Yin\", labelelement: \"CDE.Metal\" },\n elixirs: { label: \"CDE.Elixirs\", classicon: \"icon-yin\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp\", labelicon: \"Yin\", labelelement: \"CDE.Water\" },\n poisons: { label: \"CDE.Poisons\", classicon: \"icon-yinyang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp\", labelicon: \"Yin/Yang\", labelelement: \"CDE.Earth\" },\n arsenal: { label: \"CDE.Arsenal\", classicon: \"icon-yang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp\", labelicon: \"Yang\", labelelement: \"CDE.Fire\" },\n potions: { label: \"CDE.Potions\", classicon: \"icon-yang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp\", labelicon: \"Yang\", labelelement: \"CDE.Wood\" },\n },\n },\n masteryoftheway: {\n id: \"masteryoftheway\",\n background: \"linear-brown\",\n label: \"CDE.MasteryOfTheWay\",\n aspectlabel: \"CDE.Earth\",\n speciality: {\n curse: { label: \"CDE.Curse\", classicon: \"icon-yin\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp\", labelicon: \"Yin\", labelelement: \"CDE.Metal\" },\n transfiguration: { label: \"CDE.Transfiguration\", classicon: \"icon-yin\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp\", labelicon: \"Yin\", labelelement: \"CDE.Water\" },\n necromancy: { label: \"CDE.Necromancy\", classicon: \"icon-yinyang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp\", labelicon: \"Yin/Yang\", labelelement: \"CDE.Earth\" },\n climatecontrol: { label: \"CDE.ClimateControl\", classicon: \"icon-yang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp\", labelicon: \"Yang\", labelelement: \"CDE.Fire\" },\n goldenmagic: { label: \"CDE.GoldenMagic\", classicon: \"icon-yang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp\", labelicon: \"Yang\", labelelement: \"CDE.Wood\" },\n },\n },\n exorcism: {\n id: \"exorcism\",\n background: \"linear-red\",\n label: \"CDE.Exorcism\",\n aspectlabel: \"CDE.Fire\",\n speciality: {\n invocation: { label: \"CDE.Invocation\", classicon: \"icon-yin\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp\", labelicon: \"Yin\", labelelement: \"CDE.Metal\" },\n tracking: { label: \"CDE.Tracking\", classicon: \"icon-yin\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp\", labelicon: \"Yin\", labelelement: \"CDE.Water\" },\n protection: { label: \"CDE.Protection\", classicon: \"icon-yinyang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp\", labelicon: \"Yin/Yang\", labelelement: \"CDE.Earth\" },\n punishment: { label: \"CDE.Punishment\", classicon: \"icon-yang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp\", labelicon: \"Yang\", labelelement: \"CDE.Fire\" },\n domination: { label: \"CDE.Domination\", classicon: \"icon-yang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp\", labelicon: \"Yang\", labelelement: \"CDE.Wood\" },\n },\n },\n geomancy: {\n id: \"geomancy\",\n background: \"linear-green\",\n label: \"CDE.Geomancy\",\n aspectlabel: \"CDE.Wood\",\n speciality: {\n neutralization: { label: \"CDE.Neutralization\", classicon: \"icon-yin\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp\", labelicon: \"Yin\", labelelement: \"CDE.Metal\" },\n divination: { label: \"CDE.Divination\", classicon: \"icon-yin\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp\", labelicon: \"Yin\", labelelement: \"CDE.Water\" },\n earthlyprayer: { label: \"CDE.EarthlyPrayer\", classicon: \"icon-yinyang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp\", labelicon: \"Yin/Yang\", labelelement: \"CDE.Earth\" },\n heavenlyprayer: { label: \"CDE.HeavenlyPrayer\", classicon: \"icon-yang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp\", labelicon: \"Yang\", labelelement: \"CDE.Fire\" },\n fungseoi: { label: \"CDE.Fungseoi\", classicon: \"icon-yang\", icon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp\", elementicon: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp\", labelicon: \"Yang\", labelelement: \"CDE.Wood\" },\n },\n },\n}\n\nexport const TEMPLATE_PARTIALS = [\n \"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-skills.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-magics.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-nghang.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-treasures.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-items.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-kungfus.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-spells.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-supernaturals.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-spells.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-kungfus.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-items.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-loksyu-app.html\",\n \"systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-tinji-app.html\",\n]\n", "import { MAGICS, SUBTYPES } from \"./constants.js\"\n\nexport function preLocalizeConfig() {\n const localizeConfigObject = (obj, keys) => {\n for (const o of Object.values(obj)) {\n for (const key of keys) {\n o[key] = game.i18n.localize(o[key])\n }\n }\n }\n\n localizeConfigObject(SUBTYPES, [\"label\"])\n Object.values(MAGICS).forEach((magic) => {\n magic.label = game.i18n.localize(magic.label)\n magic.aspectlabel = game.i18n.localize(magic.aspectlabel)\n Object.values(magic.speciality).forEach((spec) => {\n spec.label = game.i18n.localize(spec.label)\n spec.labelelement = game.i18n.localize(spec.labelelement)\n })\n })\n}\n", "export function configureRuntime() {\n CONFIG.Actor.compendiumBanner = \"/systems/fvtt-chroniques-de-l-etrange/images/banners/actor-banner.webp\"\n CONFIG.Adventure.compendiumBanner = \"/systems/fvtt-chroniques-de-l-etrange/images/banners/adventure-banner.webp\"\n CONFIG.Cards.compendiumBanner = \"ui/banners/cards-banner.webp\"\n CONFIG.Item.compendiumBanner = \"/systems/fvtt-chroniques-de-l-etrange/images/banners/item-banner.webp\"\n CONFIG.JournalEntry.compendiumBanner = \"/systems/fvtt-chroniques-de-l-etrange/images/banners/journalentry-banner.webp\"\n CONFIG.Macro.compendiumBanner = \"ui/banners/macro-banner.webp\"\n CONFIG.Playlist.compendiumBanner = \"ui/banners/playlist-banner.webp\"\n CONFIG.RollTable.compendiumBanner = \"ui/banners/rolltable-banner.webp\"\n CONFIG.Scene.compendiumBanner = \"/systems/fvtt-chroniques-de-l-etrange/images/banners/scene-banner.webp\"\n}\n", "export default class CharacterDataModel extends foundry.abstract.TypeDataModel {\n static defineSchema() {\n const { fields } = foundry.data\n const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra })\n const stringField = (initial = \"\") => new fields.StringField({ required: true, nullable: false, initial })\n const boolField = (initial = false) => new fields.BooleanField({ required: true, initial })\n const htmlField = (initial = \"\") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })\n\n const aspectField = (label, chinese) =>\n new fields.SchemaField({\n chinese: stringField(chinese),\n label: stringField(label),\n value: numberField(15, { min: 0 }),\n })\n\n const skillField = (label) =>\n new fields.SchemaField({\n label: stringField(label),\n specialities: stringField(\"\"),\n value: numberField(0, { min: 0 }),\n })\n\n const resourceField = (label) =>\n new fields.SchemaField({\n label: stringField(label),\n specialities: stringField(\"\"),\n value: numberField(0, { min: 0 }),\n debt: boolField(false),\n })\n\n const componentField = () =>\n new fields.SchemaField({\n value: stringField(\"\"),\n })\n\n const magicSpecialityField = () =>\n new fields.SchemaField({\n check: boolField(false),\n })\n\n const magicField = () =>\n new fields.SchemaField({\n visible: boolField(true),\n value: numberField(0, { min: 0 }),\n speciality: new fields.SchemaField({\n essence: magicSpecialityField(),\n mind: magicSpecialityField(),\n purification: magicSpecialityField(),\n manipulation: magicSpecialityField(),\n aura: magicSpecialityField(),\n acupuncture: magicSpecialityField(),\n elixirs: magicSpecialityField(),\n poisons: magicSpecialityField(),\n arsenal: magicSpecialityField(),\n potions: magicSpecialityField(),\n curse: magicSpecialityField(),\n transfiguration: magicSpecialityField(),\n necromancy: magicSpecialityField(),\n climatecontrol: magicSpecialityField(),\n goldenmagic: magicSpecialityField(),\n invocation: magicSpecialityField(),\n tracking: magicSpecialityField(),\n protection: magicSpecialityField(),\n punishment: magicSpecialityField(),\n domination: magicSpecialityField(),\n neutralization: magicSpecialityField(),\n divination: magicSpecialityField(),\n earthlyprayer: magicSpecialityField(),\n heavenlyprayer: magicSpecialityField(),\n fungseoi: magicSpecialityField(),\n }),\n })\n\n const treasureBranch = () =>\n new fields.SchemaField({\n value: numberField(0, { min: 0 }),\n max: numberField(0, { min: 0 }),\n min: numberField(0, { min: 0 }),\n })\n\n const treasureLevel = () =>\n new fields.SchemaField({\n san: treasureBranch(),\n zing: treasureBranch(),\n })\n\n const schema = {\n concept: stringField(\"\"),\n guardian: numberField(0, { min: 0, max: 5 }),\n initiative: numberField(1, { min: 0 }),\n anti_initiative: numberField(24, { min: 0 }),\n description: htmlField(\"\"),\n prefs: new fields.SchemaField({\n typeofthrow: new fields.SchemaField({\n check: boolField(true),\n choice: stringField(\"0\"),\n }),\n }),\n prompt: new fields.SchemaField({\n typeofthrow: new fields.SchemaField({\n check: boolField(true),\n choice: stringField(\"0\"),\n }),\n configure: new fields.SchemaField({\n numberofdice: numberField(0),\n aspect: numberField(0),\n bonus: numberField(0),\n bonusauspiciousdice: numberField(0),\n typeofthrow: numberField(0),\n aspectskill: numberField(0),\n bonusmalusskill: numberField(0),\n aspectspeciality: numberField(0),\n rolldifficulty: numberField(0),\n bonusmalusspeciality: numberField(0),\n }),\n }),\n aspect: new fields.SchemaField({\n fire: aspectField(\"CDE.Fire\", \"\u328B\"),\n earth: aspectField(\"CDE.Earth\", \"\u328F\"),\n metal: aspectField(\"CDE.Metal\", \"\u328E\"),\n water: aspectField(\"CDE.Water\", \"\u328C\"),\n wood: aspectField(\"CDE.Wood\", \"\u328D\"),\n }),\n skills: new fields.SchemaField({\n art: skillField(\"CDE.Art\"),\n investigation: skillField(\"CDE.Investigation\"),\n erudition: skillField(\"CDE.Erudition\"),\n knavery: skillField(\"CDE.Knavery\"),\n wordliness: skillField(\"CDE.Wordliness\"),\n prowess: skillField(\"CDE.Prowess\"),\n sciences: skillField(\"CDE.Sciences\"),\n technologies: skillField(\"CDE.Technologies\"),\n kungfu: skillField(\"CDE.KungFu\"),\n rangedcombat: skillField(\"CDE.RangedCombat\"),\n }),\n resources: new fields.SchemaField({\n supply: resourceField(\"CDE.Supply\"),\n inquiry: resourceField(\"CDE.Inquiry\"),\n influence: resourceField(\"CDE.Influence\"),\n }),\n component: new fields.SchemaField({\n one: componentField(),\n two: componentField(),\n three: componentField(),\n four: componentField(),\n five: componentField(),\n six: componentField(),\n seven: componentField(),\n eight: componentField(),\n nine: componentField(),\n zero: componentField(),\n }),\n magics: new fields.SchemaField({\n internalcinnabar: magicField(),\n alchemy: magicField(),\n masteryoftheway: magicField(),\n exorcism: magicField(),\n geomancy: magicField(),\n }),\n threetreasures: new fields.SchemaField({\n heiyang: new fields.SchemaField({ value: numberField(0, { min: 0 }), max: numberField(0, { min: 0 }) }),\n heiyin: new fields.SchemaField({ value: numberField(0, { min: 0 }), max: numberField(0, { min: 0 }) }),\n dicelevel: new fields.SchemaField({\n level0d: treasureLevel(),\n level1d: treasureLevel(),\n level2d: treasureLevel(),\n }),\n }),\n experience: new fields.SchemaField({\n value: numberField(0, { min: 0 }),\n max: numberField(0, { min: 0 }),\n min: numberField(0, { min: 0 }),\n }),\n }\n\n return schema\n }\n}\n", "export default class NpcDataModel extends foundry.abstract.TypeDataModel {\n static defineSchema() {\n const { fields } = foundry.data\n const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra })\n const stringField = (initial = \"\") => new fields.StringField({ required: true, nullable: false, initial })\n const boolField = (initial = false) => new fields.BooleanField({ required: true, initial })\n const htmlField = (initial = \"\") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })\n\n const aptitudeField = () =>\n new fields.SchemaField({\n value: numberField(0, { min: 0 }),\n speciality: stringField(\"\"),\n })\n\n const trackedField = () =>\n new fields.SchemaField({\n value: numberField(0, { min: 0 }),\n calcul: numberField(0, { min: 0 }),\n note: stringField(\"\"),\n })\n\n return {\n type: stringField(\"\"),\n threat: numberField(0, { min: 0, max: 4 }), // profane(0) | apprentice(1) | initiate(2) | accomplished(3) | renowned(4)\n nuisance: numberField(0, { min: 0, max: 5 }), // figurant(0) | minion(1) | adversary(2) | ally(3) | boss(4) | divinity(5)\n initiative: numberField(1, { min: 0 }),\n anti_initiative: numberField(24, { min: 0 }),\n aptitudes: new fields.SchemaField({\n physical: aptitudeField(),\n martial: aptitudeField(),\n mental: aptitudeField(),\n social: aptitudeField(),\n spiritual: aptitudeField(),\n }),\n vitality: trackedField(),\n hei: trackedField(),\n description: htmlField(\"\"),\n prefs: new fields.SchemaField({\n typeofthrow: new fields.SchemaField({\n check: boolField(false),\n choice: stringField(\"0\"),\n }),\n }),\n }\n }\n}\n", "export default class EquipmentDataModel extends foundry.abstract.TypeDataModel {\n static defineSchema() {\n const { fields } = foundry.data\n const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra })\n const stringField = (initial = \"\") => new fields.StringField({ required: true, nullable: false, initial })\n const htmlField = (initial = \"\") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })\n\n return {\n reference: stringField(\"\"),\n description: htmlField(\"\"),\n quantity: numberField(1, { min: 0 }),\n weight: numberField(0, { min: 0 }),\n notes: htmlField(\"\"),\n }\n }\n}\n", "export default class KungfuDataModel extends foundry.abstract.TypeDataModel {\n static defineSchema() {\n const { fields } = foundry.data\n const stringField = (initial = \"\") => new fields.StringField({ required: true, nullable: false, initial })\n const htmlField = (initial = \"\") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })\n const boolField = (initial = false) => new fields.BooleanField({ required: true, initial })\n\n const techniqueField = () =>\n new fields.SchemaField({\n check: boolField(false),\n name: stringField(\"\"),\n activation: stringField(\"action-attack\"), // action-attack | action-defense | action-aid | action-attack-defense | reaction | dice | damage-inflicted | damage-received\n technique: htmlField(\"\"),\n })\n\n return {\n reference: stringField(\"\"),\n description: htmlField(\"\"),\n orientation: stringField(\"yin\"), // yin | yang | yinyang\n aspect: stringField(\"metal\"), // metal | eau | terre | feu | bois\n skill: stringField(\"kungfu\"), // kungfu | rangedcombat\n speciality: stringField(\"\"),\n style: stringField(\"\"),\n techniques: new fields.SchemaField({\n technique1: techniqueField(),\n technique2: techniqueField(),\n technique3: techniqueField(),\n }),\n notes: htmlField(\"\"),\n }\n }\n}\n", "export default class SpellDataModel extends foundry.abstract.TypeDataModel {\n static defineSchema() {\n const { fields } = foundry.data\n const stringField = (initial = \"\") => new fields.StringField({ required: true, nullable: false, initial })\n const htmlField = (initial = \"\") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })\n\n return {\n reference: stringField(\"\"),\n description: htmlField(\"\"),\n specialityname: stringField(\"\"),\n associatedelement: stringField(\"metal\"), // metal | eau | terre | feu | bois\n hei: stringField(\"\"),\n realizationtimeritual: stringField(\"\"),\n realizationtimeaccelerated: stringField(\"\"),\n flashback: stringField(\"\"),\n components: htmlField(\"\"),\n effects: htmlField(\"\"),\n examples: htmlField(\"\"),\n notes: htmlField(\"\"),\n discipline: stringField(\"internalcinnabar\"),\n heiType: stringField(\"yin\"),\n heiCost: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 1 }),\n difficulty: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 1 }),\n }\n }\n}\n", "export default class SupernaturalDataModel extends foundry.abstract.TypeDataModel {\n static defineSchema() {\n const { fields } = foundry.data\n const stringField = (initial = \"\") => new fields.StringField({ required: true, nullable: false, initial })\n const htmlField = (initial = \"\") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })\n\n return {\n reference: stringField(\"\"),\n description: htmlField(\"\"),\n notes: htmlField(\"\"),\n heiType: stringField(\"yin\"),\n heiCost: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 0 }),\n trigger: stringField(\"\"),\n effects: htmlField(\"\"),\n }\n }\n}\n", "export default class WeaponDataModel extends foundry.abstract.TypeDataModel {\n static defineSchema() {\n const { fields } = foundry.data\n const stringField = (initial = \"\") => new fields.StringField({ required: true, nullable: false, initial })\n const htmlField = (initial = \"\") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })\n const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts })\n\n return {\n reference: stringField(\"\"),\n description: htmlField(\"\"),\n weaponType: stringField(\"melee\"),\n material: stringField(\"\"),\n damageAspect: stringField(\"metal\"),\n damageBase: intField(1),\n range: stringField(\"contact\"), // contact | courte | mediane | longue | extreme\n obtainLevel: intField(0, { min: 0, max: 5 }),\n obtainDifficulty: intField(0, { min: 0, max: 3 }),\n quantity: intField(1),\n notes: htmlField(\"\"),\n }\n }\n}\n", "export default class ArmorDataModel extends foundry.abstract.TypeDataModel {\n static defineSchema() {\n const { fields } = foundry.data\n const stringField = (initial = \"\") => new fields.StringField({ required: true, nullable: false, initial })\n const htmlField = (initial = \"\") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })\n const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts })\n\n return {\n reference: stringField(\"\"),\n description: htmlField(\"\"),\n protectionValue: intField(0),\n domain: stringField(\"\"),\n obtainLevel: intField(0, { min: 0, max: 5 }),\n obtainDifficulty: intField(0, { min: 0, max: 3 }),\n quantity: intField(1),\n notes: htmlField(\"\"),\n }\n }\n}\n", "export default class SanheiDataModel extends foundry.abstract.TypeDataModel {\n static defineSchema() {\n const { fields } = foundry.data\n const stringField = (initial = \"\") => new fields.StringField({ required: true, nullable: false, initial })\n const htmlField = (initial = \"\") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })\n const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts })\n\n const propertySchema = () => new fields.SchemaField({\n name: stringField(\"\"),\n heiCost: intField(0),\n heiType: stringField(\"yin\"),\n description: htmlField(\"\"),\n })\n\n return {\n reference: stringField(\"\"),\n description: htmlField(\"\"),\n heiType: stringField(\"yin\"),\n properties: new fields.SchemaField({\n prop1: propertySchema(),\n prop2: propertySchema(),\n prop3: propertySchema(),\n }),\n notes: htmlField(\"\"),\n }\n }\n}\n", "export default class IngredientDataModel extends foundry.abstract.TypeDataModel {\n static defineSchema() {\n const { fields } = foundry.data\n const stringField = (initial = \"\") => new fields.StringField({ required: true, nullable: false, initial })\n const htmlField = (initial = \"\") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })\n const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts })\n\n return {\n reference: stringField(\"\"),\n description: htmlField(\"\"),\n school: stringField(\"all\"),\n obtainLevel: intField(0, { min: 0, max: 5 }),\n obtainDifficulty: intField(0, { min: 0, max: 3 }),\n quantity: intField(1),\n notes: htmlField(\"\"),\n }\n }\n}\n", "export class CDEMessage extends ChatMessage {\n async renderHTML({ canDelete, canClose = false, ...rest } = {}) {\n const html = await super.renderHTML({ canDelete, canClose, ...rest })\n this.#enrichChatCard(html)\n return html\n }\n\n getAssociatedActor() {\n if (this.speaker.scene && this.speaker.token) {\n const scene = game.scenes.get(this.speaker.scene)\n const token = scene?.tokens.get(this.speaker.token)\n if (token) return token.actor\n }\n return game.actors.get(this.speaker.actor)\n }\n\n #enrichChatCard(html) {\n const actor = this.getAssociatedActor()\n\n let img\n let nameText\n if (this.isContentVisible) {\n img = actor?.img ?? this.author.avatar\n nameText = this.alias\n } else {\n img = this.author.avatar\n nameText = this.author.name\n }\n\n const avatar = document.createElement(\"a\")\n avatar.classList.add(\"avatar\")\n if (actor) avatar.dataset.uuid = actor.uuid\n const avatarImg = document.createElement(\"img\")\n Object.assign(avatarImg, { src: img, alt: nameText })\n avatar.append(avatarImg)\n\n const name = document.createElement(\"span\")\n name.classList.add(\"name-stacked\")\n const title = document.createElement(\"span\")\n title.classList.add(\"title\")\n title.append(nameText)\n name.append(title)\n\n const sender = html.querySelector(\".message-sender\")\n sender?.replaceChildren(avatar, name)\n }\n}\n", "import { ACTOR_TYPES } from \"../config/constants.js\"\n\nexport class CDEActor extends Actor {\n getRollData() {\n const data = this.toObject(false).system\n return data\n }\n\n prepareBaseData() {\n super.prepareBaseData()\n\n if (this.type === ACTOR_TYPES.character) {\n this.system.anti_initiative = 25 - (this.system.initiative ?? 0)\n }\n\n if (this.type === ACTOR_TYPES.npc) {\n this.system.vitality.calcul = (this.system.aptitudes.physical.value ?? 0) * 4\n this.system.hei.calcul = (this.system.aptitudes.spiritual.value ?? 0) * 4\n this.system.anti_initiative = 25 - (this.system.initiative ?? 0)\n }\n }\n}\n", "export class CDEItem extends Item {\n get isWeapon() {\n return this.system.subtype === \"weapon\"\n }\n\n get isArmor() {\n return this.system.subtype === \"armor\"\n }\n\n get isSanhei() {\n return this.system.subtype === \"sanhei\"\n }\n\n get isOther() {\n return this.system.subtype === \"other\"\n }\n}\n", "const DIGIT_LABELS = [\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-1.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-2.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-3.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-4.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-5.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-6.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-7.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-8.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-9.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-10.webp\",\n]\n\nconst CLASSIC_LABELS = [\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-1.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-2.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-3.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-4.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-5.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-6.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-7.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-8.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-9.webp\",\n \"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-10.webp\",\n]\n\nexport function registerDice() {\n Hooks.once(\"diceSoNiceReady\", (dice3d) => {\n dice3d.addColorset(\n {\n name: \"cde\",\n description: \"CdE\",\n foreground: \"#000000\",\n background: \"#ffffff\",\n edge: \"#ffffff\",\n font: \"DeliusUnicase\",\n texture: \"ice\",\n material: \"plastic\",\n },\n \"preferred\",\n )\n\n dice3d.addSystem({ id: \"fvtt-chroniques-de-l-etrangedigit\", name: \"Chroniques de l'\u00E9trange digits\" }, \"preferred\")\n dice3d.addDicePreset({ type: \"d10\", labels: DIGIT_LABELS, system: \"fvtt-chroniques-de-l-etrangedigit\" })\n\n dice3d.addSystem({ id: \"fvtt-chroniques-de-l-etrange\", name: \"Chroniques de l'\u00E9trange\" }, \"preferred\")\n dice3d.addDicePreset({ type: \"d10\", labels: CLASSIC_LABELS, system: \"fvtt-chroniques-de-l-etrange\" })\n })\n}\n", "import { MAGICS } from \"../config/constants.js\"\n\nexport function registerHandlebarsHelpers() {\n const { Handlebars } = globalThis\n if (!Handlebars) return\n\n Handlebars.registerHelper(\"select\", function (selected, options) {\n const escapedValue = RegExp.escape(Handlebars.escapeExpression(selected))\n const rgx = new RegExp(` value=[\"']${escapedValue}[\"']`)\n const html = options.fn(this)\n return html.replace(rgx, \"$& selected\")\n })\n\n Handlebars.registerHelper(\"getMagicBackground\", function (magic) {\n return game.i18n.localize(MAGICS[magic]?.background ?? \"\")\n })\n\n Handlebars.registerHelper(\"getMagicLabel\", function (magic) {\n return game.i18n.localize(MAGICS[magic]?.label ?? \"\")\n })\n\n Handlebars.registerHelper(\"getMagicAspectLabel\", function (magic) {\n return game.i18n.localize(MAGICS[magic]?.aspectlabel ?? \"\")\n })\n\n Handlebars.registerHelper(\"getMagicSpecialityLabel\", function (magic, speciality) {\n return game.i18n.localize(MAGICS[magic]?.speciality?.[speciality]?.label ?? \"\")\n })\n\n Handlebars.registerHelper(\"getMagicSpecialityClassIcon\", function (magic, speciality) {\n return MAGICS[magic]?.speciality?.[speciality]?.classicon ?? \"\"\n })\n\n Handlebars.registerHelper(\"getMagicSpecialityIcon\", function (magic, speciality) {\n return MAGICS[magic]?.speciality?.[speciality]?.icon ?? \"\"\n })\n\n Handlebars.registerHelper(\"getMagicSpecialityElementIcon\", function (magic, speciality) {\n return MAGICS[magic]?.speciality?.[speciality]?.elementicon ?? \"\"\n })\n\n Handlebars.registerHelper(\"getMagicSpecialityLabelIcon\", function (magic, speciality) {\n return MAGICS[magic]?.speciality?.[speciality]?.labelicon ?? \"\"\n })\n\n Handlebars.registerHelper(\"getMagicSpecialityLabelElement\", function (magic, speciality) {\n return game.i18n.localize(MAGICS[magic]?.speciality?.[speciality]?.labelelement ?? \"\")\n })\n\n Handlebars.registerHelper(\"getMagicAspectIcon\", function (magic) {\n const icons = {\n internalcinnabar: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp\",\n alchemy: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp\",\n masteryoftheway: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp\",\n exorcism: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp\",\n geomancy: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp\",\n }\n return icons[magic] ?? \"\"\n })\n\n Handlebars.registerHelper(\"getElementIcon\", function (aspect) {\n const icons = {\n metal: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp\",\n water: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp\",\n earth: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp\",\n fire: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp\",\n wood: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp\",\n // legacy French keys\n eau: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp\",\n terre: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp\",\n feu: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp\",\n bois: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp\",\n }\n return icons[aspect] ?? \"\"\n })\n\n Handlebars.registerHelper(\"getOrientationIcon\", function (orientation) {\n const icons = {\n yin: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp\",\n yang: \"/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp\",\n yinyang: \"/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp\",\n }\n return icons[orientation] ?? \"\"\n })\n\n Handlebars.registerHelper(\"getOrientationLabel\", function (orientation) {\n const keys = {\n yin: \"CDE.OrientationYin\",\n yang: \"CDE.OrientationYang\",\n yinyang: \"CDE.OrientationYinYang\",\n }\n return game.i18n.localize(keys[orientation] ?? \"CDE.Orientation\")\n })\n\n Handlebars.registerHelper(\"getActivationLabel\", function (activation) {\n const keys = {\n \"action-attack\": \"CDE.ActivationAttack\",\n \"action-defense\": \"CDE.ActivationDefense\",\n \"action-aid\": \"CDE.ActivationAid\",\n \"action-attack-defense\": \"CDE.ActivationAttackOrDefense\",\n reaction: \"CDE.ActivationReaction\",\n dice: \"CDE.ActivationDice\",\n \"damage-inflicted\": \"CDE.ActivationDamageInflicted\",\n \"damage-received\": \"CDE.ActivationDamageReceived\",\n }\n return game.i18n.localize(keys[activation] ?? \"CDE.Activation\")\n })\n}\n", "import { TEMPLATE_PARTIALS } from \"../config/constants.js\"\n\nexport async function preloadPartials() {\n return foundry.applications.handlebars.loadTemplates(TEMPLATE_PARTIALS)\n}\n", "/**\n * Initiative determination system for Chroniques de l'\u00C9trange.\n *\n * PJ formula: Initiative = Prouesse + Premi\u00E8re action (comp\u00E9tence/ressource/magie)\n * PNJ formula: Initiative = Aptitude physique + Premi\u00E8re action (aptitude)\n *\n * Range 1-24 ; anti-initiative = 25 \u2212 initiative.\n * Combat order is ascending (low initiative acts first).\n */\n\nconst PC_PROMPT_TEMPLATE = \"systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-prompt.html\"\nconst NPC_PROMPT_TEMPLATE = \"systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-prompt-npc.html\"\nconst RESULT_TEMPLATE = \"systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-result.html\"\n\n/** Skills, resources and magics available as \"premi\u00E8re action\" for a PC. */\nfunction buildPCOptions(sys) {\n const sk = sys.skills ?? {}\n const rs = sys.resources ?? {}\n const mg = sys.magics ?? {}\n return [\n { key: \"art\", label: game.i18n.localize(\"CDE.Art\"), value: sk.art?.value ?? 0 },\n { key: \"investigation\", label: game.i18n.localize(\"CDE.Investigation\"), value: sk.investigation?.value ?? 0 },\n { key: \"erudition\", label: game.i18n.localize(\"CDE.Erudition\"), value: sk.erudition?.value ?? 0 },\n { key: \"knavery\", label: game.i18n.localize(\"CDE.Knavery\"), value: sk.knavery?.value ?? 0 },\n { key: \"wordliness\", label: game.i18n.localize(\"CDE.Wordliness\"), value: sk.wordliness?.value ?? 0 },\n { key: \"prowess\", label: game.i18n.localize(\"CDE.Prowess\"), value: sk.prowess?.value ?? 0 },\n { key: \"sciences\", label: game.i18n.localize(\"CDE.Sciences\"), value: sk.sciences?.value ?? 0 },\n { key: \"technologies\", label: game.i18n.localize(\"CDE.Technologies\"), value: sk.technologies?.value ?? 0 },\n { key: \"kungfu\", label: game.i18n.localize(\"CDE.KungFu\"), value: sk.kungfu?.value ?? 0 },\n { key: \"rangedcombat\", label: game.i18n.localize(\"CDE.RangedCombat\"), value: sk.rangedcombat?.value ?? 0 },\n { key: \"supply\", label: game.i18n.localize(\"CDE.Supply\"), value: rs.supply?.value ?? 0 },\n { key: \"inquiry\", label: game.i18n.localize(\"CDE.Inquiry\"), value: rs.inquiry?.value ?? 0 },\n { key: \"influence\", label: game.i18n.localize(\"CDE.Influence\"), value: rs.influence?.value ?? 0 },\n { key: \"internalcinnabar\", label: game.i18n.localize(\"CDE.InternalCinnabar\"), value: mg.internalcinnabar?.value ?? 0 },\n { key: \"alchemy\", label: game.i18n.localize(\"CDE.Alchemy\"), value: mg.alchemy?.value ?? 0 },\n { key: \"masteryoftheway\", label: game.i18n.localize(\"CDE.MasteryOfTheWay\"), value: mg.masteryoftheway?.value ?? 0 },\n { key: \"exorcism\", label: game.i18n.localize(\"CDE.Exorcism\"), value: mg.exorcism?.value ?? 0 },\n { key: \"geomancy\", label: game.i18n.localize(\"CDE.Geomancy\"), value: mg.geomancy?.value ?? 0 },\n ]\n}\n\n/** Aptitudes available as \"premi\u00E8re action\" for an NPC. */\nfunction buildNPCOptions(sys) {\n const ap = sys.aptitudes ?? {}\n return [\n { key: \"physical\", label: game.i18n.localize(\"CDE.Physical\"), value: ap.physical?.value ?? 0 },\n { key: \"martial\", label: game.i18n.localize(\"CDE.Martial\"), value: ap.martial?.value ?? 0 },\n { key: \"mental\", label: game.i18n.localize(\"CDE.Mental\"), value: ap.mental?.value ?? 0 },\n { key: \"social\", label: game.i18n.localize(\"CDE.Social\"), value: ap.social?.value ?? 0 },\n { key: \"spiritual\", label: game.i18n.localize(\"CDE.Spiritual\"), value: ap.spiritual?.value ?? 0 },\n ]\n}\n\n/** Parse the dialog element and extract firstaction + modifier. */\nfunction readInitFields(dialog) {\n const root = dialog.element ?? dialog\n const selectedKey = root.querySelector(\"select[name='firstaction']\")?.value ?? \"\"\n const modifier = parseInt(root.querySelector(\"input[name='modifier']\")?.value ?? \"0\", 10) || 0\n return { selectedKey, modifier }\n}\n\n/** Post a styled initiative chat message. */\nasync function sendInitChatMessage({ actor, baseName, baseValue, actionName, actionValue, modifier, initiative, antiInitiative }) {\n const html = await foundry.applications.handlebars.renderTemplate(RESULT_TEMPLATE, {\n actorName: actor.name,\n actorImg: actor.img,\n baseName,\n baseValue,\n actionName,\n actionValue,\n modifier,\n hasModifier: modifier !== 0,\n initiative,\n antiInitiative,\n })\n await ChatMessage.create({\n user: game.user.id,\n speaker: ChatMessage.getSpeaker({ actor }),\n content: html,\n })\n}\n\n/**\n * Open the PC initiative dialog, compute initiative (Prouesse + Premi\u00E8re action + modificateur)\n * and update the actor, then post a chat card.\n */\nexport async function rollInitiativePC(actor) {\n const sys = actor.system\n const prowess = sys.skills?.prowess?.value ?? 0\n const options = buildPCOptions(sys)\n const baseName = game.i18n.localize(\"CDE.Prowess\")\n\n const content = await foundry.applications.handlebars.renderTemplate(PC_PROMPT_TEMPLATE, {\n prowessValue: prowess,\n options,\n modifier: 0,\n })\n\n const result = await foundry.applications.api.DialogV2.prompt({\n window: { title: game.i18n.localize(\"CDE.InitiativeRoll\") },\n content,\n rejectClose: false,\n ok: {\n label: game.i18n.localize(\"CDE.Validate\"),\n callback: (_ev, _btn, dialog) => readInitFields(dialog),\n },\n })\n if (!result) return\n\n const { selectedKey, modifier } = result\n const selected = options.find((o) => o.key === selectedKey) ?? options[0]\n const rawValue = prowess + selected.value + modifier\n const initiative = Math.max(1, Math.min(24, rawValue))\n const antiInit = 25 - initiative\n\n await actor.update({ \"system.initiative\": initiative })\n await sendInitChatMessage({\n actor,\n baseName,\n baseValue: prowess,\n actionName: selected.label,\n actionValue: selected.value,\n modifier,\n initiative,\n antiInitiative: antiInit,\n })\n}\n\n/**\n * Open the NPC initiative dialog, compute initiative (Aptitude physique + Premi\u00E8re action + modificateur)\n * and update the actor, then post a chat card.\n */\nexport async function rollInitiativeNPC(actor) {\n const sys = actor.system\n const physical = sys.aptitudes?.physical?.value ?? 0\n const options = buildNPCOptions(sys)\n const baseName = game.i18n.localize(\"CDE.Physical\")\n\n const content = await foundry.applications.handlebars.renderTemplate(NPC_PROMPT_TEMPLATE, {\n physicalValue: physical,\n options,\n modifier: 0,\n })\n\n const result = await foundry.applications.api.DialogV2.prompt({\n window: { title: game.i18n.localize(\"CDE.InitiativeRoll\") },\n content,\n rejectClose: false,\n ok: {\n label: game.i18n.localize(\"CDE.Validate\"),\n callback: (_ev, _btn, dialog) => readInitFields(dialog),\n },\n })\n if (!result) return\n\n const { selectedKey, modifier } = result\n const selected = options.find((o) => o.key === selectedKey) ?? options[0]\n const rawValue = physical + selected.value + modifier\n const initiative = Math.max(1, Math.min(24, rawValue))\n const antiInit = 25 - initiative\n\n await actor.update({ \"system.initiative\": initiative })\n await sendInitChatMessage({\n actor,\n baseName,\n baseValue: physical,\n actionName: selected.label,\n actionValue: selected.value,\n modifier,\n initiative,\n antiInitiative: antiInit,\n })\n}\n", "/**\n * Loksyu / TinJi settings-based helpers.\n *\n * Data is stored as world settings instead of singleton Actor documents.\n */\n\nconst SYSTEM_ID = \"fvtt-chroniques-de-l-etrange\"\n\n/** Wu Xing generating cycle \u2014 [successes, auspicious, noxious, loksyu, tinji] */\nconst WU_XING_CYCLE = {\n wood: [\"wood\", \"fire\", \"water\", \"earth\", \"metal\"],\n fire: [\"fire\", \"earth\", \"wood\", \"metal\", \"water\"],\n earth: [\"earth\", \"metal\", \"fire\", \"water\", \"wood\"],\n metal: [\"metal\", \"water\", \"earth\", \"wood\", \"fire\"],\n water: [\"water\", \"wood\", \"metal\", \"fire\", \"earth\"],\n}\n\nconst ASPECT_FACES = {\n metal: [3, 8],\n water: [1, 6],\n earth: [0, 5],\n fire: [2, 7],\n wood: [4, 9],\n}\n\n/** Read the current loksyu data object from world settings */\nexport function getLoksyuData() {\n return game.settings.get(SYSTEM_ID, \"loksyuData\") ?? {\n wood: {yin:0,yang:0}, fire: {yin:0,yang:0}, earth: {yin:0,yang:0}, metal: {yin:0,yang:0}, water: {yin:0,yang:0},\n }\n}\n\n/** Write the loksyu data object to world settings */\nexport async function setLoksyuData(data) {\n await game.settings.set(SYSTEM_ID, \"loksyuData\", data)\n Hooks.callAll(\"cde:loksyuUpdated\", data)\n}\n\n/** Read current TinJi value from world settings */\nexport function getTinjiValue() {\n return game.settings.get(SYSTEM_ID, \"tinjiData\") ?? 0\n}\n\n/** Write TinJi value to world settings */\nexport async function setTinjiValue(value) {\n await game.settings.set(SYSTEM_ID, \"tinjiData\", Math.max(0, value))\n Hooks.callAll(\"cde:tinjiUpdated\", Math.max(0, value))\n}\n\n/**\n * After a WuXing roll, add the loksyu faces (yin + yang) of the relevant\n * aspect to the loksyu settings data.\n *\n * @param {string} activeAspect - e.g. \"fire\"\n * @param {Object} faces - Die face counts { 0: n, 1: n, \u2026, 9: n }\n */\nexport async function updateLoksyuFromRoll(activeAspect, faces) {\n const cycle = WU_XING_CYCLE[activeAspect]\n if (!cycle) return\n\n const lokAspect = cycle[3]\n const [yinFace, yangFace] = ASPECT_FACES[lokAspect] ?? []\n if (yinFace === undefined) return\n\n const yinCount = faces[yinFace] ?? 0\n const yangCount = faces[yangFace] ?? 0\n if (yinCount === 0 && yangCount === 0) return\n\n const data = getLoksyuData()\n const current = data[lokAspect] ?? { yin: 0, yang: 0 }\n data[lokAspect] = {\n yin: (current.yin ?? 0) + yinCount,\n yang: (current.yang ?? 0) + yangCount,\n }\n await setLoksyuData(data)\n}\n\n/**\n * After a WuXing roll, add tinji faces to the TinJi settings.\n *\n * @param {number} count - Number of tinji faces rolled\n */\nexport async function updateTinjiFromRoll(count) {\n if (!count || count <= 0) return\n const current = getTinjiValue()\n await setTinjiValue(current + count)\n}\n", "/**\n * Wu Xing rolling system for Chroniques de l'\u00C9trange.\n *\n * The Wu Xing cycle maps each aspect (by index 0-4) to die face groups:\n * - metal=0 : faces 3,8\n * - water=1 : faces 1,6\n * - earth=2 : faces 0/10,5\n * - fire=3 : faces 2,7\n * - wood=4 : faces 4,9\n *\n * For a given active aspect the five result categories are:\n * successes / auspicious / noxious / loksyu (yin face, yang face) / tinji\n * Each category is associated with one of the five aspects in Wu Xing cycle order.\n */\n\nimport { MAGICS } from \"../config/constants.js\"\nimport { updateLoksyuFromRoll, updateTinjiFromRoll } from \"./apps/singletons.js\"\n\nconst RESULT_TEMPLATE = \"systems/fvtt-chroniques-de-l-etrange/templates/form/cde-dice-result.html\"\nconst SKILL_PROMPT_TEMPLATE = \"systems/fvtt-chroniques-de-l-etrange/templates/form/cde-skill-dice-prompt.html\"\nconst SKILL_SPECIAL_PROMPT_TEMPLATE = \"systems/fvtt-chroniques-de-l-etrange/templates/form/cde-skill-special-dice-prompt.html\"\nconst MAGIC_PROMPT_TEMPLATE = \"systems/fvtt-chroniques-de-l-etrange/templates/form/cde-magic-dice-prompt.html\"\nconst WEAPON_PROMPT_TEMPLATE = \"systems/fvtt-chroniques-de-l-etrange/templates/form/cde-weapon-dice-prompt.html\"\n\n/** Maps i18n element label \u2192 aspect name (for speciality default aspect lookup) */\nconst LABELELEMENT_TO_ASPECT = {\n \"CDE.Metal\": \"metal\",\n \"CDE.Water\": \"water\",\n \"CDE.Earth\": \"earth\",\n \"CDE.Fire\": \"fire\",\n \"CDE.Wood\": \"wood\",\n}\n\n/** Map aspect index \u2192 string name used in result template */\nconst ASPECT_NAMES = [\"metal\", \"water\", \"earth\", \"fire\", \"wood\"]\n\n/** Map aspect name \u2192 i18n label key */\nconst ASPECT_LABELS = {\n metal: \"CDE.Metal\",\n water: \"CDE.Water\",\n earth: \"CDE.Earth\",\n fire: \"CDE.Fire\",\n wood: \"CDE.Wood\",\n}\n\n/** Map aspect name \u2192 image path */\nconst ASPECT_ICONS = {\n metal: \"systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp\",\n water: \"systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp\",\n earth: \"systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp\",\n fire: \"systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp\",\n wood: \"systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp\",\n}\n\n/** Map aspect index \u2192 die face pair [yin, yang] (face=10 stored as 0) */\nconst ASPECT_FACES = {\n metal: [3, 8],\n water: [1, 6],\n earth: [0, 5], // 0 = face \"10\"\n fire: [2, 7],\n wood: [4, 9],\n}\n\n/**\n * Wu Xing generating/overcoming cycle:\n * wood \u2192 fire \u2192 earth \u2192 metal \u2192 water \u2192 wood (generating)\n * For each active aspect, the five categories in order:\n * [successes, auspicious, noxious, loksyu, tinji]\n */\nconst WU_XING_CYCLE = {\n wood: [\"wood\", \"fire\", \"water\", \"earth\", \"metal\"],\n fire: [\"fire\", \"earth\", \"wood\", \"metal\", \"water\"],\n earth: [\"earth\", \"metal\", \"fire\", \"water\", \"wood\"],\n metal: [\"metal\", \"water\", \"earth\", \"wood\", \"fire\"],\n water: [\"water\", \"wood\", \"metal\", \"fire\", \"earth\"],\n}\n\n/** Maps weapon range string \u2192 dice malus applied to the attack pool */\nconst RANGE_MALUS = {\n contact: 0,\n courte: 0,\n mediane: -1,\n longue: -2,\n extreme: -3,\n}\n\n/** Maps weapon type string \u2192 default skill key */\nconst WEAPON_TYPE_SKILL = {\n melee: \"kungfu\",\n thrown: \"rangedcombat\",\n ranged: \"rangedcombat\",\n firearm: \"rangedcombat\",\n}\n\n/** Maps weapon damageAspect name \u2192 ASPECT_NAMES index */\nconst WEAPON_ASPECT_INDEX = { metal: 0, eau: 1, water: 1, terre: 2, earth: 2, feu: 3, fire: 3, bois: 4, wood: 4 }\n\n/** Count how many times each die face appeared in the roll results */\nfunction countFaces(rollResults) {\n const counts = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 0:0 }\n for (const die of rollResults) {\n const face = die.result === 10 ? 0 : die.result\n counts[face]++\n }\n return counts\n}\n\n/**\n * Compute Wu Xing result categories from face counts and active aspect.\n * Returns { successesdice, auspiciousdice, noxiousdice, loksyudice, tinjidice, loksyurepartition }\n */\nfunction computeWuXingResults(faces, aspectName, bonusAuspicious = 0) {\n const cycle = WU_XING_CYCLE[aspectName]\n if (!cycle) return null\n\n const [succAspect, ausAspect, noxAspect, lokAspect, tinAspect] = cycle\n const [succYin, succYang] = ASPECT_FACES[succAspect]\n const [ausYin, ausYang] = ASPECT_FACES[ausAspect]\n const [noxYin, noxYang] = ASPECT_FACES[noxAspect]\n const [lokYin, lokYang] = ASPECT_FACES[lokAspect]\n const [tinYin, tinYang] = ASPECT_FACES[tinAspect]\n\n const yin = game.i18n.localize(\"CDE.Yin\")\n const yang = game.i18n.localize(\"CDE.Yang\")\n\n return {\n successesdice: faces[succYin] + faces[succYang],\n auspiciousdice: faces[ausYin] + faces[ausYang] + bonusAuspicious,\n noxiousdice: faces[noxYin] + faces[noxYang],\n loksyudice: faces[lokYin] + faces[lokYang],\n loksyurepartition: `[${yin}(${faces[lokYin]}) ${yang}(${faces[lokYang]})]`,\n tinjidice: faces[tinYin] + faces[tinYang],\n }\n}\n\n/** Read a named field from a dialog DOM element */\nfunction readField(dlg, name) {\n const el = dlg.querySelector(`[name=\"${name}\"]`)\n if (!el) return null\n return el.type === \"checkbox\" ? el.checked : el.value\n}\n\n/**\n * Open a DialogV2.prompt with the given template + data and return the resolved form values.\n * The callback receives the DialogV2 application instance; fields are read from its .element.\n * @returns {Promise|null>}\n */\nasync function showRollPrompt({ title, template, data, fields }) {\n const content = await foundry.applications.handlebars.renderTemplate(template, data)\n return foundry.applications.api.DialogV2.prompt({\n window: { title },\n content,\n rejectClose: false,\n ok: {\n label: game.i18n.localize(\"CDE.Validate\"),\n callback: (event, button, dialog) => {\n // In AppV2, dialog is the application instance; .element is the root HTMLElement\n const root = dialog.element ?? dialog\n const result = {}\n for (const field of fields) {\n result[field] = readField(root, field)\n }\n return result\n },\n },\n })\n}\n\n/**\n * Open the skill roll prompt and return the user-confirmed parameters.\n * @param {object} params - Initial values\n * @returns {Promise