2451 lines
99 KiB
JavaScript
2451 lines
99 KiB
JavaScript
// 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 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_FACES = {
|
||
metal: [3, 8],
|
||
water: [1, 6],
|
||
earth: [0, 5],
|
||
// 0 = face "10"
|
||
fire: [2, 7],
|
||
wood: [4, 9]
|
||
};
|
||
var ASPECT_NAMES = ["metal", "water", "earth", "fire", "wood"];
|
||
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 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/settings.js
|
||
function registerSettings() {
|
||
game.settings.register(SYSTEM_ID, "loksyuData", {
|
||
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", {
|
||
scope: "world",
|
||
config: false,
|
||
type: Number,
|
||
default: 0
|
||
});
|
||
}
|
||
async function migrateIfNeeded() {
|
||
}
|
||
|
||
// 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 {
|
||
};
|
||
|
||
// 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
|
||
function getLoksyuData() {
|
||
return game.settings.get(SYSTEM_ID, "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_ID, "loksyuData", data);
|
||
Hooks.callAll("cde:loksyuUpdated", data);
|
||
}
|
||
function getTinjiValue() {
|
||
return game.settings.get(SYSTEM_ID, "tinjiData") ?? 0;
|
||
}
|
||
async function setTinjiValue(value) {
|
||
await game.settings.set(SYSTEM_ID, "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 RANGE_MALUS = {
|
||
contact: 0,
|
||
courte: 0,
|
||
mediane: -1,
|
||
longue: -2,
|
||
extreme: -3
|
||
};
|
||
var WEAPON_TYPE_SKILL = {
|
||
melee: "kungfu",
|
||
thrown: "rangedcombat",
|
||
ranged: "rangedcombat",
|
||
firearm: "rangedcombat"
|
||
};
|
||
var WEAPON_ASPECT_INDEX = { metal: 0, eau: 1, water: 1, terre: 2, earth: 2, feu: 3, fire: 3, bois: 4, wood: 4 };
|
||
function countFaces(rollResults) {
|
||
const counts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 0: 0 };
|
||
for (const die of rollResults) {
|
||
const face = die.result === 10 ? 0 : die.result;
|
||
counts[face]++;
|
||
}
|
||
return counts;
|
||
}
|
||
function computeWuXingResults(faces, aspectName, bonusAuspicious = 0) {
|
||
const cycle = WU_XING_CYCLE[aspectName];
|
||
if (!cycle) return null;
|
||
const [succAspect, ausAspect, noxAspect, lokAspect, tinAspect] = cycle;
|
||
const [succYin, succYang] = ASPECT_FACES[succAspect];
|
||
const [ausYin, ausYang] = ASPECT_FACES[ausAspect];
|
||
const [noxYin, noxYang] = ASPECT_FACES[noxAspect];
|
||
const [lokYin, lokYang] = ASPECT_FACES[lokAspect];
|
||
const [tinYin, tinYang] = ASPECT_FACES[tinAspect];
|
||
const yin = game.i18n.localize("CDE.Yin");
|
||
const yang = game.i18n.localize("CDE.Yang");
|
||
return {
|
||
successesdice: faces[succYin] + faces[succYang],
|
||
auspiciousdice: faces[ausYin] + faces[ausYang] + bonusAuspicious,
|
||
noxiousdice: faces[noxYin] + faces[noxYang],
|
||
loksyudice: faces[lokYin] + faces[lokYang],
|
||
loksyurepartition: `[${yin}(${faces[lokYin]}) ${yang}(${faces[lokYang]})]`,
|
||
tinjidice: faces[tinYin] + faces[tinYang]
|
||
};
|
||
}
|
||
function readField(dlg, name) {
|
||
const el = dlg.querySelector(`[name="${name}"]`);
|
||
if (!el) return null;
|
||
return el.type === "checkbox" ? el.checked : el.value;
|
||
}
|
||
async function showRollPrompt({ title, template, data, fields }) {
|
||
const content = await foundry.applications.handlebars.renderTemplate(template, data);
|
||
return foundry.applications.api.DialogV2.prompt({
|
||
window: { title },
|
||
content,
|
||
rejectClose: false,
|
||
ok: {
|
||
label: game.i18n.localize("CDE.Validate"),
|
||
callback: (event, button, dialog) => {
|
||
const root = dialog.element ?? dialog;
|
||
const result = {};
|
||
for (const field of fields) {
|
||
result[field] = readField(root, field);
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
async function showSkillPrompt(params) {
|
||
return showRollPrompt({
|
||
title: params.title,
|
||
template: params.isSpecial ? SKILL_SPECIAL_PROMPT_TEMPLATE : SKILL_PROMPT_TEMPLATE,
|
||
data: {
|
||
numberofdice: params.numberofdice,
|
||
aspect: Number(params.aspect ?? 0),
|
||
bonusmalus: params.bonusmalus ?? 0,
|
||
woundmalus: params.woundmalus ?? 0,
|
||
bonusauspiciousdice: params.bonusauspiciousdice ?? 0,
|
||
typeofthrow: Number(params.typeofthrow ?? 0)
|
||
},
|
||
fields: ["aspect", "bonusmalus", "woundmalus", "bonusauspiciousdice", "typeofthrow"]
|
||
});
|
||
}
|
||
async function showMagicPrompt(params) {
|
||
return showRollPrompt({
|
||
title: params.title,
|
||
template: MAGIC_PROMPT_TEMPLATE,
|
||
data: {
|
||
numberofdice: params.numberofdice ?? 0,
|
||
aspectskill: Number(params.aspectskill ?? 0),
|
||
bonusmalusskill: params.bonusmalusskill ?? 0,
|
||
bonusauspiciousdice: params.bonusauspiciousdice ?? 0,
|
||
aspectspeciality: Number(params.aspectspeciality ?? 0),
|
||
rolldifficulty: params.rolldifficulty ?? 1,
|
||
bonusmalusspeciality: params.bonusmalusspeciality ?? 0,
|
||
heispend: params.heispend ?? 0,
|
||
typeofthrow: Number(params.typeofthrow ?? 0)
|
||
},
|
||
fields: [
|
||
"aspectskill",
|
||
"bonusmalusskill",
|
||
"bonusauspiciousdice",
|
||
"aspectspeciality",
|
||
"rolldifficulty",
|
||
"bonusmalusspeciality",
|
||
"heispend",
|
||
"typeofthrow"
|
||
]
|
||
});
|
||
}
|
||
async function showWeaponPrompt(params) {
|
||
return showRollPrompt({
|
||
title: params.title,
|
||
template: WEAPON_PROMPT_TEMPLATE,
|
||
data: {
|
||
numberofdice: params.numberofdice ?? 0,
|
||
weaponName: params.weaponName ?? "",
|
||
weaponTypeLabel: params.weaponTypeLabel ?? "CDE.Weapon",
|
||
weaponAspectIcon: params.weaponAspectIcon ?? "",
|
||
weaponAspectLabel: params.weaponAspectLabel ?? "",
|
||
damageBase: params.damageBase ?? 1,
|
||
weaponskill: params.weaponskill ?? "kungfu",
|
||
aspect: Number(params.aspect ?? 0),
|
||
effectiverange: params.effectiverange ?? "contact",
|
||
bonusmalus: params.bonusmalus ?? 0,
|
||
woundmalus: params.woundmalus ?? 0,
|
||
bonusauspiciousdice: params.bonusauspiciousdice ?? 0,
|
||
typeofthrow: Number(params.typeofthrow ?? 0)
|
||
},
|
||
fields: [
|
||
"weaponskill",
|
||
"aspect",
|
||
"effectiverange",
|
||
"bonusmalus",
|
||
"woundmalus",
|
||
"bonusauspiciousdice",
|
||
"typeofthrow"
|
||
]
|
||
});
|
||
}
|
||
async function sendResultMessage(actor, resultData, roll, rollMode) {
|
||
const html = await foundry.applications.handlebars.renderTemplate(RESULT_TEMPLATE2, resultData);
|
||
return ChatMessage.create({
|
||
user: game.user.id,
|
||
speaker: ChatMessage.getSpeaker({ actor }),
|
||
content: html,
|
||
rolls: [roll],
|
||
rollMode,
|
||
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 = `
|
||
<form class="flexcol">
|
||
<div class="form-group">
|
||
<label>${game.i18n.localize("CDE.ThrowType")}</label>
|
||
<select name="choice">
|
||
<option value="0"${current.choice === "0" ? " selected" : ""}>0</option>
|
||
<option value="1"${current.choice === "1" ? " selected" : ""}>1</option>
|
||
<option value="2"${current.choice === "2" ? " selected" : ""}>2</option>
|
||
<option value="3"${current.choice === "3" ? " selected" : ""}>3</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>${game.i18n.localize("CDE.EnablePrompt")}</label>
|
||
<input type="checkbox" name="check" ${current.check ? "checked" : ""}/>
|
||
</div>
|
||
</form>`;
|
||
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 ? `<strong>${componentValue}</strong>` : `<em>${game.i18n.localize("CDE.Component")}${face}</em>`;
|
||
const content = `
|
||
<div class="cde-chat-random-component">
|
||
<span class="cde-chat-component-label">${game.i18n.localize("CDE.ChanceThrowResult")}</span>
|
||
<span class="cde-chat-component-value">${label}</span>
|
||
</div>`;
|
||
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 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_ID}/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_ID}/images/cde_bois.webp` },
|
||
{ key: "fire", nameKey: "CDE.Fire", qualKey: "CDE.FireQualities", img: `systems/${SYSTEM_ID}/images/cde_feu.webp` },
|
||
{ key: "earth", nameKey: "CDE.Earth", qualKey: "CDE.EarthQualities", img: `systems/${SYSTEM_ID}/images/cde_terre.webp` },
|
||
{ key: "metal", nameKey: "CDE.Metal", qualKey: "CDE.MetalQualities", img: `systems/${SYSTEM_ID}/images/cde_metal.webp` },
|
||
{ key: "water", nameKey: "CDE.Water", qualKey: "CDE.WaterQualities", img: `systems/${SYSTEM_ID}/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 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_ID}/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: `<div class="cde-tinji-spend-msg">
|
||
<i class="fas fa-star"></i>
|
||
<strong>${game.i18n.localize("CDE.TinJi2")}</strong>
|
||
${game.i18n.format("CDE.TinjiSpent", { name: game.user.name })}
|
||
<span class="cde-tinji-remain">(${current - 1} ${game.i18n.localize("CDE.TinjiRemaining")})</span>
|
||
</div>`
|
||
});
|
||
}
|
||
};
|
||
|
||
// src/ui/roll-actions.js
|
||
var RESULT_TEMPLATE3 = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-dice-result.html";
|
||
function injectRollActions(message, html) {
|
||
const rollCard = html.querySelector(".cde-roll-result");
|
||
if (!rollCard) return;
|
||
const aspect = rollCard.dataset.aspect;
|
||
if (!aspect || !WU_XING_CYCLE[aspect]) return;
|
||
refreshRollActions(rollCard, aspect, message);
|
||
}
|
||
function refreshRollActions(rollCard, aspect, message) {
|
||
rollCard.querySelector(".cde-roll-actions")?.remove();
|
||
const cycle = WU_XING_CYCLE[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_LABELS[aspect]);
|
||
const fasteLabel = game.i18n.localize(ASPECT_LABELS[fasteAspect]);
|
||
let btns = "";
|
||
if (successAvail > 0) {
|
||
btns += `<button class="cde-roll-action-btn cde-roll-action--success" data-action="loksyu-success">
|
||
<img src="${ASPECT_ICONS[aspect]}" class="cde-roll-action-icon" alt="${aspLabel}"/>
|
||
<span class="cde-roll-action-label">+1 ${game.i18n.localize("CDE.Successes")}</span>
|
||
<span class="cde-roll-action-count">${successAvail}</span>
|
||
</button>`;
|
||
}
|
||
if (fasteAvail > 0) {
|
||
btns += `<button class="cde-roll-action-btn cde-roll-action--faste" data-action="loksyu-faste">
|
||
<img src="${ASPECT_ICONS[fasteAspect]}" class="cde-roll-action-icon" alt="${fasteLabel}"/>
|
||
<span class="cde-roll-action-label">+1 ${game.i18n.localize("CDE.AuspiciousDie")}</span>
|
||
<span class="cde-roll-action-count">${fasteAvail}</span>
|
||
</button>`;
|
||
}
|
||
if (isGM && tinji > 0) {
|
||
btns += `<button class="cde-roll-action-btn cde-roll-action--tinji" data-action="tinji">
|
||
<span class="cde-roll-action-tinji-char">\u5929</span>
|
||
<span class="cde-roll-action-label">${game.i18n.localize("CDE.TinJi2")}</span>
|
||
<span class="cde-roll-action-count">${tinji}</span>
|
||
</button>`;
|
||
}
|
||
const wrapper = document.createElement("div");
|
||
wrapper.className = "cde-roll-actions";
|
||
wrapper.innerHTML = `
|
||
<div class="cde-roll-actions-title">
|
||
<i class="fas fa-yin-yang"></i>
|
||
${game.i18n.localize("CDE.PostRollActions")}
|
||
</div>
|
||
<div class="cde-roll-actions-btns">${btns}</div>
|
||
`;
|
||
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_ID];
|
||
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_ID}.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: `<div class="cde-loksyu-draw-msg">
|
||
<div class="cde-loksyu-draw-header">
|
||
<img src="${ASPECT_ICONS[aspect]}" class="cde-loksyu-draw-aspect-icon" alt="${aspectLabel}"/>
|
||
<span class="cde-loksyu-draw-user">${game.user.name}</span>
|
||
<span class="cde-loksyu-draw-action">${game.i18n.localize("CDE.LoksyuDrawsA")}</span>
|
||
<span class="cde-loksyu-draw-type">${typeLabel}</span>
|
||
<span class="cde-loksyu-draw-from">${game.i18n.localize("CDE.LoksyuFromAspect")} <em>${aspectLabel}</em></span>
|
||
</div>
|
||
<div class="cde-loksyu-draw-footer">
|
||
<i class="fas fa-yin-yang"></i>
|
||
<span>${game.i18n.localize("CDE.Loksyu")} ${aspectLabel} : </span>
|
||
<strong class="cde-loksyu-remain">${remain} ${game.i18n.localize("CDE.LoksyuRemaining")}</strong>
|
||
</div>
|
||
</div>`
|
||
});
|
||
}
|
||
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: `<div class="cde-tinji-spend-msg">
|
||
<span class="cde-tinji-icon">\u5929</span>
|
||
<span class="cde-tinji-text">
|
||
<strong>${game.user.name}</strong> ${game.i18n.localize("CDE.TinjiSpent").replace("{name}", game.user.name)}
|
||
</span>
|
||
<span class="cde-tinji-remain">(${current - 1} ${game.i18n.localize("CDE.TinjiRemaining")})</span>
|
||
</div>`
|
||
});
|
||
}
|
||
function refreshAllRollActions() {
|
||
document.querySelectorAll(".chat-message .cde-roll-result[data-aspect]").forEach((card) => {
|
||
const aspect = card.dataset.aspect;
|
||
if (!aspect || !WU_XING_CYCLE[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();
|
||
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 = `
|
||
<button type="button" class="cde-chat-btn cde-chat-btn--loksyu">
|
||
<i class="fas fa-yin-yang"></i> ${game.i18n.localize("CDE.Loksyu")}
|
||
</button>
|
||
<button type="button" class="cde-chat-btn cde-chat-btn--tinji">
|
||
<i class="fas fa-star"></i> ${game.i18n.localize("CDE.TinJi2")}
|
||
</button>
|
||
`;
|
||
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();
|
||
}
|
||
});
|
||
/**
|
||
* Chroniques de l'Étrange — Système FoundryVTT
|
||
*
|
||
* Chroniques de l'Étrange est un jeu de rôle édité par Antre-Monde Éditions.
|
||
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||
* affilié à Antre-Monde Éditions.
|
||
*
|
||
* @author LeRatierBretonnien
|
||
* @copyright 2024–2026 LeRatierBretonnien
|
||
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||
*/
|
||
//# sourceMappingURL=system.js.map
|