2030 lines
83 KiB
JavaScript
2030 lines
83 KiB
JavaScript
// src/config/constants.js
|
|
var SYSTEM_ID = "fvtt-chroniques-de-l-etrange";
|
|
var ACTOR_TYPES = {
|
|
character: "character",
|
|
npc: "npc",
|
|
tinji: "tinji",
|
|
loksyu: "loksyu"
|
|
};
|
|
var ITEM_TYPES = {
|
|
item: "item",
|
|
kungfu: "kungfu",
|
|
spell: "spell",
|
|
supernatural: "supernatural",
|
|
weapon: "weapon",
|
|
armor: "armor",
|
|
sanhei: "sanhei",
|
|
ingredient: "ingredient"
|
|
};
|
|
var SUBTYPES = {
|
|
weapon: { id: "weapon", label: "CDE.Weapon" },
|
|
armor: { id: "armor", label: "CDE.Armor" },
|
|
sanhei: { id: "sanhei", label: "CDE.Sanhei" },
|
|
other: { id: "other", label: "CDE.Other" }
|
|
};
|
|
var MAGICS = {
|
|
internalcinnabar: {
|
|
id: "internalcinnabar",
|
|
background: "linear-grey",
|
|
label: "CDE.InternalCinnabar",
|
|
aspectlabel: "CDE.Metal",
|
|
speciality: {
|
|
essence: { label: "CDE.Essence", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" },
|
|
mind: { label: "CDE.Mind", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" },
|
|
purification: { label: "CDE.Purification", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
|
|
manipulation: { label: "CDE.Manipulation", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" },
|
|
aura: { label: "CDE.Aura", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" }
|
|
}
|
|
},
|
|
alchemy: {
|
|
id: "alchemy",
|
|
background: "linear-blue",
|
|
label: "CDE.Alchemy",
|
|
aspectlabel: "CDE.Water",
|
|
speciality: {
|
|
acupuncture: { label: "CDE.Acupuncture", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" },
|
|
elixirs: { label: "CDE.Elixirs", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" },
|
|
poisons: { label: "CDE.Poisons", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
|
|
arsenal: { label: "CDE.Arsenal", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" },
|
|
potions: { label: "CDE.Potions", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" }
|
|
}
|
|
},
|
|
masteryoftheway: {
|
|
id: "masteryoftheway",
|
|
background: "linear-brown",
|
|
label: "CDE.MasteryOfTheWay",
|
|
aspectlabel: "CDE.Earth",
|
|
speciality: {
|
|
curse: { label: "CDE.Curse", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" },
|
|
transfiguration: { label: "CDE.Transfiguration", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" },
|
|
necromancy: { label: "CDE.Necromancy", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
|
|
climatecontrol: { label: "CDE.ClimateControl", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" },
|
|
goldenmagic: { label: "CDE.GoldenMagic", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" }
|
|
}
|
|
},
|
|
exorcism: {
|
|
id: "exorcism",
|
|
background: "linear-red",
|
|
label: "CDE.Exorcism",
|
|
aspectlabel: "CDE.Fire",
|
|
speciality: {
|
|
invocation: { label: "CDE.Invocation", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" },
|
|
tracking: { label: "CDE.Tracking", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" },
|
|
protection: { label: "CDE.Protection", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
|
|
punishment: { label: "CDE.Punishment", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" },
|
|
domination: { label: "CDE.Domination", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" }
|
|
}
|
|
},
|
|
geomancy: {
|
|
id: "geomancy",
|
|
background: "linear-green",
|
|
label: "CDE.Geomancy",
|
|
aspectlabel: "CDE.Wood",
|
|
speciality: {
|
|
neutralization: { label: "CDE.Neutralization", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" },
|
|
divination: { label: "CDE.Divination", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" },
|
|
earthlyprayer: { label: "CDE.EarthlyPrayer", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
|
|
heavenlyprayer: { label: "CDE.HeavenlyPrayer", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" },
|
|
fungseoi: { label: "CDE.Fungseoi", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" }
|
|
}
|
|
}
|
|
};
|
|
var TEMPLATE_PARTIALS = [
|
|
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-skills.html",
|
|
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-magics.html",
|
|
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-nghang.html",
|
|
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-treasures.html",
|
|
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-items.html",
|
|
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-kungfus.html",
|
|
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-spells.html",
|
|
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-supernaturals.html",
|
|
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-spells.html",
|
|
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-kungfus.html",
|
|
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-items.html"
|
|
];
|
|
|
|
// src/config/localize.js
|
|
function preLocalizeConfig() {
|
|
const localizeConfigObject = (obj, keys) => {
|
|
for (const o of Object.values(obj)) {
|
|
for (const key of keys) {
|
|
o[key] = game.i18n.localize(o[key]);
|
|
}
|
|
}
|
|
};
|
|
localizeConfigObject(SUBTYPES, ["label"]);
|
|
Object.values(MAGICS).forEach((magic) => {
|
|
magic.label = game.i18n.localize(magic.label);
|
|
magic.aspectlabel = game.i18n.localize(magic.aspectlabel);
|
|
Object.values(magic.speciality).forEach((spec) => {
|
|
spec.label = game.i18n.localize(spec.label);
|
|
spec.labelelement = game.i18n.localize(spec.labelelement);
|
|
});
|
|
});
|
|
}
|
|
|
|
// src/config/runtime.js
|
|
function configureRuntime() {
|
|
CONFIG.Actor.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/actor-banner.webp";
|
|
CONFIG.Adventure.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/adventure-banner.webp";
|
|
CONFIG.Cards.compendiumBanner = "ui/banners/cards-banner.webp";
|
|
CONFIG.Item.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/item-banner.webp";
|
|
CONFIG.JournalEntry.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/journalentry-banner.webp";
|
|
CONFIG.Macro.compendiumBanner = "ui/banners/macro-banner.webp";
|
|
CONFIG.Playlist.compendiumBanner = "ui/banners/playlist-banner.webp";
|
|
CONFIG.RollTable.compendiumBanner = "ui/banners/rolltable-banner.webp";
|
|
CONFIG.Scene.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/scene-banner.webp";
|
|
}
|
|
|
|
// src/data/actors/character.js
|
|
var CharacterDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra });
|
|
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
|
|
const boolField = (initial = false) => new fields.BooleanField({ required: true, initial });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
const aspectField = (label, chinese) => new fields.SchemaField({
|
|
chinese: stringField(chinese),
|
|
label: stringField(label),
|
|
value: numberField(15, { min: 0 })
|
|
});
|
|
const skillField = (label) => new fields.SchemaField({
|
|
label: stringField(label),
|
|
specialities: stringField(""),
|
|
value: numberField(0, { min: 0 })
|
|
});
|
|
const resourceField = (label) => new fields.SchemaField({
|
|
label: stringField(label),
|
|
specialities: stringField(""),
|
|
value: numberField(0, { min: 0 }),
|
|
debt: boolField(false)
|
|
});
|
|
const componentField = () => new fields.SchemaField({
|
|
value: stringField("")
|
|
});
|
|
const magicSpecialityField = () => new fields.SchemaField({
|
|
check: boolField(false)
|
|
});
|
|
const magicField = () => new fields.SchemaField({
|
|
visible: boolField(true),
|
|
value: numberField(0, { min: 0 }),
|
|
speciality: new fields.SchemaField({
|
|
essence: magicSpecialityField(),
|
|
mind: magicSpecialityField(),
|
|
purification: magicSpecialityField(),
|
|
manipulation: magicSpecialityField(),
|
|
aura: magicSpecialityField(),
|
|
acupuncture: magicSpecialityField(),
|
|
elixirs: magicSpecialityField(),
|
|
poisons: magicSpecialityField(),
|
|
arsenal: magicSpecialityField(),
|
|
potions: magicSpecialityField(),
|
|
curse: magicSpecialityField(),
|
|
transfiguration: magicSpecialityField(),
|
|
necromancy: magicSpecialityField(),
|
|
climatecontrol: magicSpecialityField(),
|
|
goldenmagic: magicSpecialityField(),
|
|
invocation: magicSpecialityField(),
|
|
tracking: magicSpecialityField(),
|
|
protection: magicSpecialityField(),
|
|
punishment: magicSpecialityField(),
|
|
domination: magicSpecialityField(),
|
|
neutralization: magicSpecialityField(),
|
|
divination: magicSpecialityField(),
|
|
earthlyprayer: magicSpecialityField(),
|
|
heavenlyprayer: magicSpecialityField(),
|
|
fungseoi: magicSpecialityField()
|
|
})
|
|
});
|
|
const treasureBranch = () => new fields.SchemaField({
|
|
value: numberField(0, { min: 0 }),
|
|
max: numberField(0, { min: 0 }),
|
|
min: numberField(0, { min: 0 })
|
|
});
|
|
const treasureLevel = () => new fields.SchemaField({
|
|
san: treasureBranch(),
|
|
zing: treasureBranch()
|
|
});
|
|
const schema = {
|
|
concept: stringField(""),
|
|
guardian: numberField(0, { min: 0, max: 5 }),
|
|
initiative: numberField(1, { min: 0 }),
|
|
anti_initiative: numberField(24, { min: 0 }),
|
|
description: htmlField(""),
|
|
prefs: new fields.SchemaField({
|
|
typeofthrow: new fields.SchemaField({
|
|
check: boolField(true),
|
|
choice: stringField("0")
|
|
})
|
|
}),
|
|
prompt: new fields.SchemaField({
|
|
typeofthrow: new fields.SchemaField({
|
|
check: boolField(true),
|
|
choice: stringField("0")
|
|
}),
|
|
configure: new fields.SchemaField({
|
|
numberofdice: numberField(0),
|
|
aspect: numberField(0),
|
|
bonus: numberField(0),
|
|
bonusauspiciousdice: numberField(0),
|
|
typeofthrow: numberField(0),
|
|
aspectskill: numberField(0),
|
|
bonusmalusskill: numberField(0),
|
|
aspectspeciality: numberField(0),
|
|
rolldifficulty: numberField(0),
|
|
bonusmalusspeciality: numberField(0)
|
|
})
|
|
}),
|
|
aspect: new fields.SchemaField({
|
|
fire: aspectField("CDE.Fire", "\u328B"),
|
|
earth: aspectField("CDE.Earth", "\u328F"),
|
|
metal: aspectField("CDE.Metal", "\u328E"),
|
|
water: aspectField("CDE.Water", "\u328C"),
|
|
wood: aspectField("CDE.Wood", "\u328D")
|
|
}),
|
|
skills: new fields.SchemaField({
|
|
art: skillField("CDE.Art"),
|
|
investigation: skillField("CDE.Investigation"),
|
|
erudition: skillField("CDE.Erudition"),
|
|
knavery: skillField("CDE.Knavery"),
|
|
wordliness: skillField("CDE.Wordliness"),
|
|
prowess: skillField("CDE.Prowess"),
|
|
sciences: skillField("CDE.Sciences"),
|
|
technologies: skillField("CDE.Technologies"),
|
|
kungfu: skillField("CDE.KungFu"),
|
|
rangedcombat: skillField("CDE.RangedCombat")
|
|
}),
|
|
resources: new fields.SchemaField({
|
|
supply: resourceField("CDE.Supply"),
|
|
inquiry: resourceField("CDE.Inquiry"),
|
|
influence: resourceField("CDE.Influence")
|
|
}),
|
|
component: new fields.SchemaField({
|
|
one: componentField(),
|
|
two: componentField(),
|
|
three: componentField(),
|
|
four: componentField(),
|
|
five: componentField(),
|
|
six: componentField(),
|
|
seven: componentField(),
|
|
eight: componentField(),
|
|
nine: componentField(),
|
|
zero: componentField()
|
|
}),
|
|
magics: new fields.SchemaField({
|
|
internalcinnabar: magicField(),
|
|
alchemy: magicField(),
|
|
masteryoftheway: magicField(),
|
|
exorcism: magicField(),
|
|
geomancy: magicField()
|
|
}),
|
|
threetreasures: new fields.SchemaField({
|
|
heiyang: new fields.SchemaField({ value: numberField(0, { min: 0 }), max: numberField(0, { min: 0 }) }),
|
|
heiyin: new fields.SchemaField({ value: numberField(0, { min: 0 }), max: numberField(0, { min: 0 }) }),
|
|
dicelevel: new fields.SchemaField({
|
|
level0d: treasureLevel(),
|
|
level1d: treasureLevel(),
|
|
level2d: treasureLevel()
|
|
})
|
|
}),
|
|
experience: new fields.SchemaField({
|
|
value: numberField(0, { min: 0 }),
|
|
max: numberField(0, { min: 0 }),
|
|
min: numberField(0, { min: 0 })
|
|
})
|
|
};
|
|
return schema;
|
|
}
|
|
};
|
|
|
|
// src/data/actors/npc.js
|
|
var NpcDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra });
|
|
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
|
|
const boolField = (initial = false) => new fields.BooleanField({ required: true, initial });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
const aptitudeField = () => new fields.SchemaField({
|
|
value: numberField(0, { min: 0 }),
|
|
speciality: stringField("")
|
|
});
|
|
const trackedField = () => new fields.SchemaField({
|
|
value: numberField(0, { min: 0 }),
|
|
calcul: numberField(0, { min: 0 }),
|
|
note: stringField("")
|
|
});
|
|
return {
|
|
type: stringField(""),
|
|
threat: numberField(0, { min: 0, max: 4 }),
|
|
// profane(0) | apprentice(1) | initiate(2) | accomplished(3) | renowned(4)
|
|
nuisance: numberField(0, { min: 0, max: 5 }),
|
|
// figurant(0) | minion(1) | adversary(2) | ally(3) | boss(4) | divinity(5)
|
|
initiative: numberField(1, { min: 0 }),
|
|
anti_initiative: numberField(24, { min: 0 }),
|
|
aptitudes: new fields.SchemaField({
|
|
physical: aptitudeField(),
|
|
martial: aptitudeField(),
|
|
mental: aptitudeField(),
|
|
social: aptitudeField(),
|
|
spiritual: aptitudeField()
|
|
}),
|
|
vitality: trackedField(),
|
|
hei: trackedField(),
|
|
description: htmlField(""),
|
|
prefs: new fields.SchemaField({
|
|
typeofthrow: new fields.SchemaField({
|
|
check: boolField(false),
|
|
choice: stringField("0")
|
|
})
|
|
})
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/data/actors/tinji.js
|
|
var TinjiDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
return {
|
|
value: numberField(0, { min: 0 }),
|
|
description: htmlField("")
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/data/actors/loksyu.js
|
|
var LoksyuDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
const polarity = () => new fields.SchemaField({
|
|
yin: new fields.SchemaField({ value: numberField(0, { min: 0 }) }),
|
|
yang: new fields.SchemaField({ value: numberField(0, { min: 0 }) })
|
|
});
|
|
return {
|
|
fire: polarity(),
|
|
earth: polarity(),
|
|
metal: polarity(),
|
|
water: polarity(),
|
|
wood: polarity(),
|
|
description: htmlField("")
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/data/items/item.js
|
|
var EquipmentDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra });
|
|
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
return {
|
|
reference: stringField(""),
|
|
description: htmlField(""),
|
|
quantity: numberField(1, { min: 0 }),
|
|
weight: numberField(0, { min: 0 }),
|
|
notes: htmlField("")
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/data/items/kungfu.js
|
|
var KungfuDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
const boolField = (initial = false) => new fields.BooleanField({ required: true, initial });
|
|
const techniqueField = () => new fields.SchemaField({
|
|
check: boolField(false),
|
|
name: stringField(""),
|
|
activation: stringField("action-attack"),
|
|
// action-attack | action-defense | action-aid | action-attack-defense | reaction | dice | damage-inflicted | damage-received
|
|
technique: htmlField("")
|
|
});
|
|
return {
|
|
reference: stringField(""),
|
|
description: htmlField(""),
|
|
orientation: stringField("yin"),
|
|
// yin | yang | yinyang
|
|
aspect: stringField("metal"),
|
|
// metal | eau | terre | feu | bois
|
|
skill: stringField("kungfu"),
|
|
// kungfu | rangedcombat
|
|
speciality: stringField(""),
|
|
style: stringField(""),
|
|
techniques: new fields.SchemaField({
|
|
technique1: techniqueField(),
|
|
technique2: techniqueField(),
|
|
technique3: techniqueField()
|
|
}),
|
|
notes: htmlField("")
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/data/items/spell.js
|
|
var SpellDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
return {
|
|
reference: stringField(""),
|
|
description: htmlField(""),
|
|
specialityname: stringField(""),
|
|
associatedelement: stringField("metal"),
|
|
// metal | eau | terre | feu | bois
|
|
hei: stringField(""),
|
|
realizationtimeritual: stringField(""),
|
|
realizationtimeaccelerated: stringField(""),
|
|
flashback: stringField(""),
|
|
components: htmlField(""),
|
|
effects: htmlField(""),
|
|
examples: htmlField(""),
|
|
notes: htmlField(""),
|
|
discipline: stringField("cinabre"),
|
|
heiType: stringField("yin"),
|
|
heiCost: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 1 }),
|
|
difficulty: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 1 })
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/data/items/supernatural.js
|
|
var SupernaturalDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
return {
|
|
reference: stringField(""),
|
|
description: htmlField(""),
|
|
notes: htmlField(""),
|
|
heiType: stringField("yin"),
|
|
heiCost: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 0 }),
|
|
trigger: stringField(""),
|
|
effects: htmlField("")
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/data/items/weapon.js
|
|
var WeaponDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts });
|
|
return {
|
|
reference: stringField(""),
|
|
description: htmlField(""),
|
|
weaponType: stringField("melee"),
|
|
material: stringField(""),
|
|
damageAspect: stringField("metal"),
|
|
damageBase: intField(1),
|
|
range: stringField("contact"),
|
|
// contact | courte | mediane | longue | extreme
|
|
obtainLevel: intField(0, { min: 0, max: 5 }),
|
|
obtainDifficulty: intField(0, { min: 0, max: 3 }),
|
|
quantity: intField(1),
|
|
notes: htmlField("")
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/data/items/armor.js
|
|
var ArmorDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts });
|
|
return {
|
|
reference: stringField(""),
|
|
description: htmlField(""),
|
|
protectionValue: intField(0),
|
|
domain: stringField(""),
|
|
obtainLevel: intField(0, { min: 0, max: 5 }),
|
|
obtainDifficulty: intField(0, { min: 0, max: 3 }),
|
|
quantity: intField(1),
|
|
notes: htmlField("")
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/data/items/sanhei.js
|
|
var SanheiDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts });
|
|
const propertySchema = () => new fields.SchemaField({
|
|
name: stringField(""),
|
|
heiCost: intField(0),
|
|
heiType: stringField("yin"),
|
|
description: htmlField("")
|
|
});
|
|
return {
|
|
reference: stringField(""),
|
|
description: htmlField(""),
|
|
heiType: stringField("yin"),
|
|
properties: new fields.SchemaField({
|
|
prop1: propertySchema(),
|
|
prop2: propertySchema(),
|
|
prop3: propertySchema()
|
|
}),
|
|
notes: htmlField("")
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/data/items/ingredient.js
|
|
var IngredientDataModel = class extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
const { fields } = foundry.data;
|
|
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
|
|
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
|
|
const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts });
|
|
return {
|
|
reference: stringField(""),
|
|
description: htmlField(""),
|
|
school: stringField("all"),
|
|
obtainLevel: intField(0, { min: 0, max: 5 }),
|
|
obtainDifficulty: intField(0, { min: 0, max: 3 }),
|
|
quantity: intField(1),
|
|
notes: htmlField("")
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/documents/chat-message.js
|
|
var CDEMessage = class extends ChatMessage {
|
|
async renderHTML({ canDelete, canClose = false, ...rest } = {}) {
|
|
const html = await super.renderHTML({ canDelete, canClose, ...rest });
|
|
this.#enrichChatCard(html);
|
|
return html;
|
|
}
|
|
getAssociatedActor() {
|
|
if (this.speaker.scene && this.speaker.token) {
|
|
const scene = game.scenes.get(this.speaker.scene);
|
|
const token = scene?.tokens.get(this.speaker.token);
|
|
if (token) return token.actor;
|
|
}
|
|
return game.actors.get(this.speaker.actor);
|
|
}
|
|
#enrichChatCard(html) {
|
|
const actor = this.getAssociatedActor();
|
|
let img;
|
|
let nameText;
|
|
if (this.isContentVisible) {
|
|
img = actor?.img ?? this.author.avatar;
|
|
nameText = this.alias;
|
|
} else {
|
|
img = this.author.avatar;
|
|
nameText = this.author.name;
|
|
}
|
|
const avatar = document.createElement("a");
|
|
avatar.classList.add("avatar");
|
|
if (actor) avatar.dataset.uuid = actor.uuid;
|
|
const avatarImg = document.createElement("img");
|
|
Object.assign(avatarImg, { src: img, alt: nameText });
|
|
avatar.append(avatarImg);
|
|
const name = document.createElement("span");
|
|
name.classList.add("name-stacked");
|
|
const title = document.createElement("span");
|
|
title.classList.add("title");
|
|
title.append(nameText);
|
|
name.append(title);
|
|
const sender = html.querySelector(".message-sender");
|
|
sender?.replaceChildren(avatar, name);
|
|
}
|
|
};
|
|
|
|
// src/documents/actor.js
|
|
var CDEActor = class extends Actor {
|
|
getRollData() {
|
|
const data = this.toObject(false).system;
|
|
return data;
|
|
}
|
|
prepareBaseData() {
|
|
super.prepareBaseData();
|
|
if (this.type === ACTOR_TYPES.character) {
|
|
this.system.anti_initiative = 25 - (this.system.initiative ?? 0);
|
|
}
|
|
if (this.type === ACTOR_TYPES.npc) {
|
|
this.system.vitality.calcul = (this.system.aptitudes.physical.value ?? 0) * 4;
|
|
this.system.hei.calcul = (this.system.aptitudes.spiritual.value ?? 0) * 4;
|
|
this.system.anti_initiative = 25 - (this.system.initiative ?? 0);
|
|
}
|
|
}
|
|
};
|
|
|
|
// src/documents/item.js
|
|
var CDEItem = class extends Item {
|
|
get isWeapon() {
|
|
return this.system.subtype === "weapon";
|
|
}
|
|
get isArmor() {
|
|
return this.system.subtype === "armor";
|
|
}
|
|
get isSanhei() {
|
|
return this.system.subtype === "sanhei";
|
|
}
|
|
get isOther() {
|
|
return this.system.subtype === "other";
|
|
}
|
|
};
|
|
|
|
// src/ui/dice.js
|
|
var DIGIT_LABELS = [
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-1.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-2.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-3.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-4.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-5.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-6.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-7.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-8.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-9.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-10.webp"
|
|
];
|
|
var CLASSIC_LABELS = [
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-1.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-2.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-3.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-4.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-5.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-6.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-7.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-8.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-9.webp",
|
|
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-10.webp"
|
|
];
|
|
function registerDice() {
|
|
Hooks.once("diceSoNiceReady", (dice3d) => {
|
|
dice3d.addColorset(
|
|
{
|
|
name: "cde",
|
|
description: "CdE",
|
|
foreground: "#000000",
|
|
background: "#ffffff",
|
|
edge: "#ffffff",
|
|
font: "DeliusUnicase",
|
|
texture: "ice",
|
|
material: "plastic"
|
|
},
|
|
"preferred"
|
|
);
|
|
dice3d.addSystem({ id: "fvtt-chroniques-de-l-etrangedigit", name: "Chroniques de l'\xE9trange digits" }, "preferred");
|
|
dice3d.addDicePreset({ type: "d10", labels: DIGIT_LABELS, system: "fvtt-chroniques-de-l-etrangedigit" });
|
|
dice3d.addSystem({ id: "fvtt-chroniques-de-l-etrange", name: "Chroniques de l'\xE9trange" }, "preferred");
|
|
dice3d.addDicePreset({ type: "d10", labels: CLASSIC_LABELS, system: "fvtt-chroniques-de-l-etrange" });
|
|
});
|
|
}
|
|
|
|
// src/ui/helpers.js
|
|
function registerHandlebarsHelpers() {
|
|
const { Handlebars } = globalThis;
|
|
if (!Handlebars) return;
|
|
Handlebars.registerHelper("select", function(selected, options) {
|
|
const escapedValue = RegExp.escape(Handlebars.escapeExpression(selected));
|
|
const rgx = new RegExp(` value=["']${escapedValue}["']`);
|
|
const html = options.fn(this);
|
|
return html.replace(rgx, "$& selected");
|
|
});
|
|
Handlebars.registerHelper("getMagicBackground", function(magic) {
|
|
return game.i18n.localize(MAGICS[magic]?.background ?? "");
|
|
});
|
|
Handlebars.registerHelper("getMagicLabel", function(magic) {
|
|
return game.i18n.localize(MAGICS[magic]?.label ?? "");
|
|
});
|
|
Handlebars.registerHelper("getMagicAspectLabel", function(magic) {
|
|
return game.i18n.localize(MAGICS[magic]?.aspectlabel ?? "");
|
|
});
|
|
Handlebars.registerHelper("getMagicSpecialityLabel", function(magic, speciality) {
|
|
return game.i18n.localize(MAGICS[magic]?.speciality?.[speciality]?.label ?? "");
|
|
});
|
|
Handlebars.registerHelper("getMagicSpecialityClassIcon", function(magic, speciality) {
|
|
return MAGICS[magic]?.speciality?.[speciality]?.classicon ?? "";
|
|
});
|
|
Handlebars.registerHelper("getMagicSpecialityIcon", function(magic, speciality) {
|
|
return MAGICS[magic]?.speciality?.[speciality]?.icon ?? "";
|
|
});
|
|
Handlebars.registerHelper("getMagicSpecialityElementIcon", function(magic, speciality) {
|
|
return MAGICS[magic]?.speciality?.[speciality]?.elementicon ?? "";
|
|
});
|
|
Handlebars.registerHelper("getMagicSpecialityLabelIcon", function(magic, speciality) {
|
|
return MAGICS[magic]?.speciality?.[speciality]?.labelicon ?? "";
|
|
});
|
|
Handlebars.registerHelper("getMagicSpecialityLabelElement", function(magic, speciality) {
|
|
return game.i18n.localize(MAGICS[magic]?.speciality?.[speciality]?.labelelement ?? "");
|
|
});
|
|
}
|
|
|
|
// src/ui/templates.js
|
|
async function preloadPartials() {
|
|
return loadTemplates(TEMPLATE_PARTIALS);
|
|
}
|
|
|
|
// src/ui/initiative.js
|
|
var PC_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-prompt.html";
|
|
var NPC_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-prompt-npc.html";
|
|
var RESULT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-result.html";
|
|
function buildPCOptions(sys) {
|
|
const sk = sys.skills ?? {};
|
|
const rs = sys.resources ?? {};
|
|
const mg = sys.magics ?? {};
|
|
return [
|
|
{ key: "art", label: game.i18n.localize("CDE.Art"), value: sk.art?.value ?? 0 },
|
|
{ key: "investigation", label: game.i18n.localize("CDE.Investigation"), value: sk.investigation?.value ?? 0 },
|
|
{ key: "erudition", label: game.i18n.localize("CDE.Erudition"), value: sk.erudition?.value ?? 0 },
|
|
{ key: "knavery", label: game.i18n.localize("CDE.Knavery"), value: sk.knavery?.value ?? 0 },
|
|
{ key: "wordliness", label: game.i18n.localize("CDE.Wordliness"), value: sk.wordliness?.value ?? 0 },
|
|
{ key: "prowess", label: game.i18n.localize("CDE.Prowess"), value: sk.prowess?.value ?? 0 },
|
|
{ key: "sciences", label: game.i18n.localize("CDE.Sciences"), value: sk.sciences?.value ?? 0 },
|
|
{ key: "technologies", label: game.i18n.localize("CDE.Technologies"), value: sk.technologies?.value ?? 0 },
|
|
{ key: "kungfu", label: game.i18n.localize("CDE.KungFu"), value: sk.kungfu?.value ?? 0 },
|
|
{ key: "rangedcombat", label: game.i18n.localize("CDE.RangedCombat"), value: sk.rangedcombat?.value ?? 0 },
|
|
{ key: "supply", label: game.i18n.localize("CDE.Supply"), value: rs.supply?.value ?? 0 },
|
|
{ key: "inquiry", label: game.i18n.localize("CDE.Inquiry"), value: rs.inquiry?.value ?? 0 },
|
|
{ key: "influence", label: game.i18n.localize("CDE.Influence"), value: rs.influence?.value ?? 0 },
|
|
{ key: "internalcinnabar", label: game.i18n.localize("CDE.InternalCinnabar"), value: mg.internalcinnabar?.value ?? 0 },
|
|
{ key: "alchemy", label: game.i18n.localize("CDE.Alchemy"), value: mg.alchemy?.value ?? 0 },
|
|
{ key: "masteryoftheway", label: game.i18n.localize("CDE.MasteryOfTheWay"), value: mg.masteryoftheway?.value ?? 0 },
|
|
{ key: "exorcism", label: game.i18n.localize("CDE.Exorcism"), value: mg.exorcism?.value ?? 0 },
|
|
{ key: "geomancy", label: game.i18n.localize("CDE.Geomancy"), value: mg.geomancy?.value ?? 0 }
|
|
];
|
|
}
|
|
function buildNPCOptions(sys) {
|
|
const ap = sys.aptitudes ?? {};
|
|
return [
|
|
{ key: "physical", label: game.i18n.localize("CDE.Physical"), value: ap.physical?.value ?? 0 },
|
|
{ key: "martial", label: game.i18n.localize("CDE.Martial"), value: ap.martial?.value ?? 0 },
|
|
{ key: "mental", label: game.i18n.localize("CDE.Mental"), value: ap.mental?.value ?? 0 },
|
|
{ key: "social", label: game.i18n.localize("CDE.Social"), value: ap.social?.value ?? 0 },
|
|
{ key: "spiritual", label: game.i18n.localize("CDE.Spiritual"), value: ap.spiritual?.value ?? 0 }
|
|
];
|
|
}
|
|
function readInitFields(dialog) {
|
|
const root = dialog.element ?? dialog;
|
|
const selectedKey = root.querySelector("select[name='firstaction']")?.value ?? "";
|
|
const modifier = parseInt(root.querySelector("input[name='modifier']")?.value ?? 0) || 0;
|
|
return { selectedKey, modifier };
|
|
}
|
|
async function sendInitChatMessage({ actor, baseName, baseValue, actionName, actionValue, modifier, initiative, antiInitiative }) {
|
|
const html = await foundry.applications.handlebars.renderTemplate(RESULT_TEMPLATE, {
|
|
actorName: actor.name,
|
|
actorImg: actor.img,
|
|
baseName,
|
|
baseValue,
|
|
actionName,
|
|
actionValue,
|
|
modifier,
|
|
hasModifier: modifier !== 0,
|
|
initiative,
|
|
antiInitiative
|
|
});
|
|
await ChatMessage.create({
|
|
user: game.user.id,
|
|
speaker: ChatMessage.getSpeaker({ actor }),
|
|
content: html
|
|
});
|
|
}
|
|
async function rollInitiativePC(actor) {
|
|
const sys = actor.system;
|
|
const prowess = sys.skills?.prowess?.value ?? 0;
|
|
const options = buildPCOptions(sys);
|
|
const baseName = game.i18n.localize("CDE.Prowess");
|
|
const content = await foundry.applications.handlebars.renderTemplate(PC_PROMPT_TEMPLATE, {
|
|
prowessValue: prowess,
|
|
options,
|
|
modifier: 0
|
|
});
|
|
const result = await foundry.applications.api.DialogV2.prompt({
|
|
window: { title: game.i18n.localize("CDE.InitiativeRoll") },
|
|
content,
|
|
rejectClose: false,
|
|
ok: {
|
|
label: game.i18n.localize("CDE.Validate"),
|
|
callback: (_ev, _btn, dialog) => readInitFields(dialog)
|
|
}
|
|
});
|
|
if (!result) return;
|
|
const { selectedKey, modifier } = result;
|
|
const selected = options.find((o) => o.key === selectedKey) ?? options[0];
|
|
const rawValue = prowess + selected.value + modifier;
|
|
const initiative = Math.max(1, Math.min(24, rawValue));
|
|
const antiInit = 25 - initiative;
|
|
await actor.update({ "system.initiative": initiative });
|
|
await sendInitChatMessage({
|
|
actor,
|
|
baseName,
|
|
baseValue: prowess,
|
|
actionName: selected.label,
|
|
actionValue: selected.value,
|
|
modifier,
|
|
initiative,
|
|
antiInitiative: antiInit
|
|
});
|
|
}
|
|
async function rollInitiativeNPC(actor) {
|
|
const sys = actor.system;
|
|
const physical = sys.aptitudes?.physical?.value ?? 0;
|
|
const options = buildNPCOptions(sys);
|
|
const baseName = game.i18n.localize("CDE.Physical");
|
|
const content = await foundry.applications.handlebars.renderTemplate(NPC_PROMPT_TEMPLATE, {
|
|
physicalValue: physical,
|
|
options,
|
|
modifier: 0
|
|
});
|
|
const result = await foundry.applications.api.DialogV2.prompt({
|
|
window: { title: game.i18n.localize("CDE.InitiativeRoll") },
|
|
content,
|
|
rejectClose: false,
|
|
ok: {
|
|
label: game.i18n.localize("CDE.Validate"),
|
|
callback: (_ev, _btn, dialog) => readInitFields(dialog)
|
|
}
|
|
});
|
|
if (!result) return;
|
|
const { selectedKey, modifier } = result;
|
|
const selected = options.find((o) => o.key === selectedKey) ?? options[0];
|
|
const rawValue = physical + selected.value + modifier;
|
|
const initiative = Math.max(1, Math.min(24, rawValue));
|
|
const antiInit = 25 - initiative;
|
|
await actor.update({ "system.initiative": initiative });
|
|
await sendInitChatMessage({
|
|
actor,
|
|
baseName,
|
|
baseValue: physical,
|
|
actionName: selected.label,
|
|
actionValue: selected.value,
|
|
modifier,
|
|
initiative,
|
|
antiInitiative: antiInit
|
|
});
|
|
}
|
|
|
|
// src/ui/rolling.js
|
|
var RESULT_TEMPLATE2 = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-dice-result.html";
|
|
var SKILL_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-skill-dice-prompt.html";
|
|
var SKILL_SPECIAL_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-skill-special-dice-prompt.html";
|
|
var MAGIC_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-magic-dice-prompt.html";
|
|
var WEAPON_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-weapon-dice-prompt.html";
|
|
var LABELELEMENT_TO_ASPECT = {
|
|
"CDE.Metal": "metal",
|
|
"CDE.Water": "water",
|
|
"CDE.Earth": "earth",
|
|
"CDE.Fire": "fire",
|
|
"CDE.Wood": "wood"
|
|
};
|
|
var ASPECT_NAMES = ["metal", "water", "earth", "fire", "wood"];
|
|
var ASPECT_LABELS = {
|
|
metal: "CDE.Metal",
|
|
water: "CDE.Water",
|
|
earth: "CDE.Earth",
|
|
fire: "CDE.Fire",
|
|
wood: "CDE.Wood"
|
|
};
|
|
var ASPECT_ICONS = {
|
|
metal: "systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png",
|
|
water: "systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png",
|
|
earth: "systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png",
|
|
fire: "systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png",
|
|
wood: "systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png"
|
|
};
|
|
var ASPECT_FACES = {
|
|
metal: [3, 8],
|
|
water: [1, 6],
|
|
earth: [0, 5],
|
|
// 0 = face "10"
|
|
fire: [2, 7],
|
|
wood: [4, 9]
|
|
};
|
|
var WU_XING_CYCLE = {
|
|
wood: ["wood", "fire", "water", "earth", "metal"],
|
|
fire: ["fire", "earth", "wood", "metal", "water"],
|
|
earth: ["earth", "metal", "fire", "water", "wood"],
|
|
metal: ["metal", "water", "earth", "wood", "fire"],
|
|
water: ["water", "wood", "metal", "fire", "earth"]
|
|
};
|
|
var RANGE_MALUS = {
|
|
contact: 0,
|
|
courte: 0,
|
|
mediane: -1,
|
|
longue: -2,
|
|
extreme: -3
|
|
};
|
|
var WEAPON_TYPE_SKILL = {
|
|
melee: "kungfu",
|
|
thrown: "rangedcombat",
|
|
ranged: "rangedcombat",
|
|
firearm: "rangedcombat"
|
|
};
|
|
var WEAPON_ASPECT_INDEX = { metal: 0, eau: 1, water: 1, terre: 2, earth: 2, feu: 3, fire: 3, bois: 4, wood: 4 };
|
|
function countFaces(rollResults) {
|
|
const counts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 0: 0 };
|
|
for (const die of rollResults) {
|
|
const face = die.result === 10 ? 0 : die.result;
|
|
counts[face]++;
|
|
}
|
|
return counts;
|
|
}
|
|
function computeWuXingResults(faces, aspectName, bonusAuspicious = 0) {
|
|
const cycle = WU_XING_CYCLE[aspectName];
|
|
if (!cycle) return null;
|
|
const [succAspect, ausAspect, noxAspect, lokAspect, tinAspect] = cycle;
|
|
const [succYin, succYang] = ASPECT_FACES[succAspect];
|
|
const [ausYin, ausYang] = ASPECT_FACES[ausAspect];
|
|
const [noxYin, noxYang] = ASPECT_FACES[noxAspect];
|
|
const [lokYin, lokYang] = ASPECT_FACES[lokAspect];
|
|
const [tinYin, tinYang] = ASPECT_FACES[tinAspect];
|
|
const yin = game.i18n.localize("CDE.Yin");
|
|
const yang = game.i18n.localize("CDE.Yang");
|
|
return {
|
|
successesdice: faces[succYin] + faces[succYang],
|
|
auspiciousdice: faces[ausYin] + faces[ausYang] + bonusAuspicious,
|
|
noxiousdice: faces[noxYin] + faces[noxYang],
|
|
loksyudice: faces[lokYin] + faces[lokYang],
|
|
loksyurepartition: `[${yin}(${faces[lokYin]}) ${yang}(${faces[lokYang]})]`,
|
|
tinjidice: faces[tinYin] + faces[tinYang]
|
|
};
|
|
}
|
|
function readField(dlg, name) {
|
|
const el = dlg.querySelector(`[name="${name}"]`);
|
|
if (!el) return null;
|
|
return el.type === "checkbox" ? el.checked : el.value;
|
|
}
|
|
async function showRollPrompt({ title, template, data, fields }) {
|
|
const content = await foundry.applications.handlebars.renderTemplate(template, data);
|
|
return foundry.applications.api.DialogV2.prompt({
|
|
window: { title },
|
|
content,
|
|
rejectClose: false,
|
|
ok: {
|
|
label: game.i18n.localize("CDE.Validate"),
|
|
callback: (event, button, dialog) => {
|
|
const root = dialog.element ?? dialog;
|
|
const result = {};
|
|
for (const field of fields) {
|
|
result[field] = readField(root, field);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
async function showSkillPrompt(params) {
|
|
return showRollPrompt({
|
|
title: params.title,
|
|
template: params.isSpecial ? SKILL_SPECIAL_PROMPT_TEMPLATE : SKILL_PROMPT_TEMPLATE,
|
|
data: {
|
|
numberofdice: params.numberofdice,
|
|
aspect: Number(params.aspect ?? 0),
|
|
bonusmalus: params.bonusmalus ?? 0,
|
|
woundmalus: params.woundmalus ?? 0,
|
|
bonusauspiciousdice: params.bonusauspiciousdice ?? 0,
|
|
typeofthrow: Number(params.typeofthrow ?? 0)
|
|
},
|
|
fields: ["aspect", "bonusmalus", "woundmalus", "bonusauspiciousdice", "typeofthrow"]
|
|
});
|
|
}
|
|
async function showMagicPrompt(params) {
|
|
return showRollPrompt({
|
|
title: params.title,
|
|
template: MAGIC_PROMPT_TEMPLATE,
|
|
data: {
|
|
numberofdice: params.numberofdice ?? 0,
|
|
aspectskill: Number(params.aspectskill ?? 0),
|
|
bonusmalusskill: params.bonusmalusskill ?? 0,
|
|
bonusauspiciousdice: params.bonusauspiciousdice ?? 0,
|
|
aspectspeciality: Number(params.aspectspeciality ?? 0),
|
|
rolldifficulty: params.rolldifficulty ?? 1,
|
|
bonusmalusspeciality: params.bonusmalusspeciality ?? 0,
|
|
heispend: params.heispend ?? 0,
|
|
typeofthrow: Number(params.typeofthrow ?? 0)
|
|
},
|
|
fields: [
|
|
"aspectskill",
|
|
"bonusmalusskill",
|
|
"bonusauspiciousdice",
|
|
"aspectspeciality",
|
|
"rolldifficulty",
|
|
"bonusmalusspeciality",
|
|
"heispend",
|
|
"typeofthrow"
|
|
]
|
|
});
|
|
}
|
|
async function showWeaponPrompt(params) {
|
|
return showRollPrompt({
|
|
title: params.title,
|
|
template: WEAPON_PROMPT_TEMPLATE,
|
|
data: {
|
|
numberofdice: params.numberofdice ?? 0,
|
|
weaponName: params.weaponName ?? "",
|
|
weaponTypeLabel: params.weaponTypeLabel ?? "CDE.Weapon",
|
|
weaponAspectIcon: params.weaponAspectIcon ?? "",
|
|
weaponAspectLabel: params.weaponAspectLabel ?? "",
|
|
damageBase: params.damageBase ?? 1,
|
|
weaponskill: params.weaponskill ?? "kungfu",
|
|
aspect: Number(params.aspect ?? 0),
|
|
effectiverange: params.effectiverange ?? "contact",
|
|
bonusmalus: params.bonusmalus ?? 0,
|
|
woundmalus: params.woundmalus ?? 0,
|
|
bonusauspiciousdice: params.bonusauspiciousdice ?? 0,
|
|
typeofthrow: Number(params.typeofthrow ?? 0)
|
|
},
|
|
fields: [
|
|
"weaponskill",
|
|
"aspect",
|
|
"effectiverange",
|
|
"bonusmalus",
|
|
"woundmalus",
|
|
"bonusauspiciousdice",
|
|
"typeofthrow"
|
|
]
|
|
});
|
|
}
|
|
async function sendResultMessage(actor, resultData, roll, rollMode) {
|
|
const html = await foundry.applications.handlebars.renderTemplate(RESULT_TEMPLATE2, resultData);
|
|
return ChatMessage.create({
|
|
user: game.user.id,
|
|
speaker: ChatMessage.getSpeaker({ actor }),
|
|
content: html,
|
|
rolls: [roll],
|
|
rollMode
|
|
});
|
|
}
|
|
var ROLL_MODES = ["roll", "gmroll", "blindroll", "selfroll"];
|
|
async function rollForActor(actor, rollKey) {
|
|
const parts = rollKey.split("-");
|
|
const skillLibel = parts[0];
|
|
const typeLibel = parts[1];
|
|
const specialLibel = parts[2] ?? null;
|
|
const sys = actor.system;
|
|
const typeOfThrow = Number(sys.prefs?.typeofthrow?.choice ?? 0);
|
|
let numberofdice = 0;
|
|
let title = "";
|
|
let isSpecial = false;
|
|
let isMagic = false;
|
|
let isMagicSpecial = false;
|
|
let kfDefaultAspect = -1;
|
|
const MAGIC_I18N_KEYS = {
|
|
internalcinnabar: "CDE.InternalCinnabar",
|
|
alchemy: "CDE.Alchemy",
|
|
masteryoftheway: "CDE.MasteryOfTheWay",
|
|
exorcism: "CDE.Exorcism",
|
|
geomancy: "CDE.Geomancy"
|
|
};
|
|
switch (typeLibel) {
|
|
case "aspect":
|
|
numberofdice = sys.aspect[skillLibel]?.value ?? 0;
|
|
title = game.i18n.localize(sys.aspect[skillLibel]?.label ?? "CDE.Roll");
|
|
break;
|
|
case "skill":
|
|
numberofdice = sys.skills[skillLibel]?.value ?? 0;
|
|
title = game.i18n.localize(sys.skills[skillLibel]?.label ?? "CDE.Roll");
|
|
break;
|
|
case "special":
|
|
numberofdice = sys.skills[skillLibel]?.value ?? 0;
|
|
title = game.i18n.localize(sys.skills[skillLibel]?.label ?? "CDE.Roll");
|
|
title += ` [${game.i18n.localize("CDE.Speciality")}]`;
|
|
isSpecial = true;
|
|
if (!sys.skills[skillLibel]?.specialities) {
|
|
ui.notifications.warn(game.i18n.localize("CDE.Error2"));
|
|
return;
|
|
}
|
|
break;
|
|
case "resource":
|
|
numberofdice = sys.resources[skillLibel]?.value ?? 0;
|
|
title = game.i18n.localize(sys.resources[skillLibel]?.label ?? "CDE.Roll");
|
|
break;
|
|
case "field":
|
|
numberofdice = sys.resources[skillLibel]?.value ?? 0;
|
|
title = game.i18n.localize(sys.resources[skillLibel]?.label ?? "CDE.Roll");
|
|
title += ` [${game.i18n.localize("CDE.Field")}]`;
|
|
isSpecial = true;
|
|
if (!sys.resources[skillLibel]?.specialities) {
|
|
ui.notifications.warn(game.i18n.localize("CDE.Error4"));
|
|
return;
|
|
}
|
|
break;
|
|
case "magic":
|
|
numberofdice = sys.magics[skillLibel]?.value ?? 0;
|
|
isMagic = true;
|
|
title = game.i18n.localize(MAGIC_I18N_KEYS[skillLibel] ?? "CDE.Magics");
|
|
break;
|
|
case "magicspecial":
|
|
numberofdice = sys.magics[skillLibel]?.value ?? 0;
|
|
isMagicSpecial = true;
|
|
isMagic = true;
|
|
if (!sys.magics[skillLibel]?.speciality?.[specialLibel]?.check) {
|
|
ui.notifications.warn(game.i18n.localize("CDE.Error6"));
|
|
return;
|
|
}
|
|
title = `${game.i18n.localize(MAGIC_I18N_KEYS[skillLibel] ?? "CDE.Magics")} [${game.i18n.localize(game.system.CONST?.MAGICS?.[skillLibel]?.speciality?.[specialLibel]?.label ?? "")}]`;
|
|
break;
|
|
case "itemkungfu": {
|
|
const kfItem = actor.items.get(skillLibel);
|
|
if (!kfItem) {
|
|
ui.notifications.warn(game.i18n.localize("CDE.Error0"));
|
|
return;
|
|
}
|
|
const kfSkill = kfItem.system.skill ?? "kungfu";
|
|
numberofdice = sys.skills[kfSkill]?.value ?? 0;
|
|
title = `${kfItem.name} [${game.i18n.localize(sys.skills[kfSkill]?.label ?? "CDE.KungFu")}]`;
|
|
kfDefaultAspect = ASPECT_NAMES.indexOf(kfItem.system.aspect ?? "metal");
|
|
if (kfDefaultAspect < 0) kfDefaultAspect = 0;
|
|
break;
|
|
}
|
|
case "itemweapon": {
|
|
const wpItem = actor.items.get(skillLibel);
|
|
if (!wpItem) {
|
|
ui.notifications.warn(game.i18n.localize("CDE.Error0"));
|
|
return;
|
|
}
|
|
const wpType = wpItem.system.weaponType ?? "melee";
|
|
const wpSkill = WEAPON_TYPE_SKILL[wpType] ?? "kungfu";
|
|
numberofdice = sys.skills[wpSkill]?.value ?? 0;
|
|
const wpAspectRaw = wpItem.system.damageAspect ?? "metal";
|
|
const wpAspectIdx = WEAPON_ASPECT_INDEX[wpAspectRaw] ?? 0;
|
|
const wpRange = wpItem.system.range ?? "contact";
|
|
const WEAPON_TYPE_LABELS = {
|
|
melee: "CDE.WeaponMelee",
|
|
thrown: "CDE.WeaponThrown",
|
|
ranged: "CDE.WeaponRanged",
|
|
firearm: "CDE.WeaponFirearm"
|
|
};
|
|
const wParams = await showWeaponPrompt({
|
|
title: `${wpItem.name} [${game.i18n.localize(sys.skills[wpSkill]?.label ?? "CDE.WeaponRoll")}]`,
|
|
numberofdice,
|
|
weaponName: wpItem.name,
|
|
weaponTypeLabel: WEAPON_TYPE_LABELS[wpType] ?? "CDE.Weapon",
|
|
weaponAspectIcon: ASPECT_ICONS[ASPECT_NAMES[wpAspectIdx]] ?? "",
|
|
weaponAspectLabel: game.i18n.localize(ASPECT_LABELS[ASPECT_NAMES[wpAspectIdx]] ?? ""),
|
|
damageBase: wpItem.system.damageBase ?? 1,
|
|
weaponskill: wpSkill,
|
|
aspect: wpAspectIdx,
|
|
effectiverange: wpRange,
|
|
bonusmalus: 0,
|
|
woundmalus: 0,
|
|
bonusauspiciousdice: 0,
|
|
typeofthrow: typeOfThrow
|
|
});
|
|
if (!wParams) return;
|
|
const wpChosenSkill = wParams.weaponskill ?? wpSkill;
|
|
const wpSkillDice = sys.skills[wpChosenSkill]?.value ?? 0;
|
|
const wpAspFinal = Number(wParams.aspect ?? wpAspectIdx);
|
|
const wpAspectDice = sys.aspect[ASPECT_NAMES[wpAspFinal]]?.value ?? 0;
|
|
const wpRangeMalus = RANGE_MALUS[wParams.effectiverange ?? "contact"] ?? 0;
|
|
const wpBonusMalus = Number(wParams.bonusmalus ?? 0);
|
|
const wpWoundMalus = Number(wParams.woundmalus ?? 0);
|
|
const wpBonusAusp = Number(wParams.bonusauspiciousdice ?? 0);
|
|
const wpThrowMode = Number(wParams.typeofthrow ?? 0);
|
|
const wpDamageBase = wpItem.system.damageBase ?? 1;
|
|
const wpTotalDice = wpSkillDice + wpAspectDice + wpRangeMalus + wpBonusMalus - wpWoundMalus;
|
|
if (wpTotalDice <= 0) {
|
|
ui.notifications.warn(game.i18n.localize("CDE.Error0"));
|
|
return;
|
|
}
|
|
const wpRoll = new Roll(`${wpTotalDice}d10`);
|
|
await wpRoll.evaluate();
|
|
const wpAspectName = ASPECT_NAMES[wpAspFinal] ?? "metal";
|
|
const wpFaces = countFaces(wpRoll.dice[0]?.results ?? []);
|
|
const wpResults = computeWuXingResults(wpFaces, wpAspectName, wpBonusAusp);
|
|
if (!wpResults) return;
|
|
const wpModParts = [];
|
|
if (wpRangeMalus !== 0) wpModParts.push(`${wpRangeMalus} ${game.i18n.localize("CDE.RangePenalty")}`);
|
|
if (wpBonusMalus !== 0) wpModParts.push(`${wpBonusMalus > 0 ? "+" : ""}${wpBonusMalus} ${game.i18n.localize("CDE.BonusMalus")}`);
|
|
if (wpWoundMalus !== 0) wpModParts.push(`-${wpWoundMalus} ${game.i18n.localize("CDE.WoundMalus")}`);
|
|
if (wpBonusAusp !== 0) wpModParts.push(`+${wpBonusAusp} ${game.i18n.localize("CDE.BonusAuspiciousDice")}`);
|
|
const wpMsg = await sendResultMessage(actor, {
|
|
rollLabel: `${wpItem.name}`,
|
|
aspectName: wpAspectName,
|
|
aspectLabel: game.i18n.localize(ASPECT_LABELS[wpAspectName] ?? ""),
|
|
aspectIcon: ASPECT_ICONS[wpAspectName] ?? "",
|
|
totalDice: wpTotalDice,
|
|
modifiersText: wpModParts.length ? wpModParts.join(" \xB7 ") : "",
|
|
spellPower: null,
|
|
rollDifficulty: null,
|
|
actorName: actor.name ?? "",
|
|
actorImg: actor.img ?? "",
|
|
// weapon-specific
|
|
weaponName: wpItem.name,
|
|
damageBase: wpDamageBase,
|
|
totalDamage: wpResults.successesdice * wpDamageBase,
|
|
...wpResults,
|
|
aspect: wpAspectName,
|
|
d1: wpFaces[1],
|
|
d2: wpFaces[2],
|
|
d3: wpFaces[3],
|
|
d4: wpFaces[4],
|
|
d5: wpFaces[5],
|
|
d6: wpFaces[6],
|
|
d7: wpFaces[7],
|
|
d8: wpFaces[8],
|
|
d9: wpFaces[9],
|
|
d0: wpFaces[0]
|
|
}, wpRoll, ROLL_MODES[wpThrowMode] ?? "roll");
|
|
if (game.modules.get("dice-so-nice")?.active && wpMsg?.id) {
|
|
await game.dice3d.waitFor3DAnimationByMessageID(wpMsg.id);
|
|
}
|
|
return;
|
|
}
|
|
default:
|
|
ui.notifications.warn(`Unknown roll type: ${typeLibel}`);
|
|
return;
|
|
}
|
|
if (numberofdice <= 0 && typeLibel !== "aspect" && typeLibel !== "itemkungfu" && !isMagic) {
|
|
ui.notifications.warn(game.i18n.localize("CDE.Error0"));
|
|
return;
|
|
}
|
|
const MAGIC_ASPECTS = {
|
|
internalcinnabar: 0,
|
|
// metal
|
|
alchemy: 1,
|
|
// water
|
|
masteryoftheway: 2,
|
|
// earth
|
|
exorcism: 3,
|
|
// fire
|
|
geomancy: 4
|
|
// wood
|
|
};
|
|
let defaultAspect = typeLibel === "aspect" ? ["metal", "water", "earth", "fire", "wood"].indexOf(skillLibel) : 0;
|
|
if (isMagic && MAGIC_ASPECTS[skillLibel] !== void 0) {
|
|
defaultAspect = MAGIC_ASPECTS[skillLibel];
|
|
}
|
|
if (kfDefaultAspect >= 0) {
|
|
defaultAspect = kfDefaultAspect;
|
|
}
|
|
let defaultSpecialAspect = 0;
|
|
if (isMagicSpecial && specialLibel) {
|
|
const specialCfg = game.system.CONST?.MAGICS?.[skillLibel]?.speciality?.[specialLibel];
|
|
const aspectName = LABELELEMENT_TO_ASPECT[specialCfg?.labelelement];
|
|
if (aspectName) {
|
|
defaultSpecialAspect = ASPECT_NAMES.indexOf(aspectName);
|
|
}
|
|
}
|
|
let params;
|
|
if (isMagic) {
|
|
params = await showMagicPrompt({
|
|
title,
|
|
numberofdice,
|
|
aspectskill: defaultAspect,
|
|
bonusmalusskill: 0,
|
|
bonusauspiciousdice: 0,
|
|
aspectspeciality: defaultSpecialAspect,
|
|
rolldifficulty: 1,
|
|
bonusmalusspeciality: 0,
|
|
heispend: 0,
|
|
typeofthrow: typeOfThrow
|
|
});
|
|
} else {
|
|
params = await showSkillPrompt({
|
|
title,
|
|
numberofdice,
|
|
aspect: defaultAspect,
|
|
bonusmalus: 0,
|
|
woundmalus: 0,
|
|
bonusauspiciousdice: 0,
|
|
typeofthrow: typeOfThrow,
|
|
isSpecial
|
|
});
|
|
}
|
|
if (!params) return;
|
|
let aspectIndex, bonusMalus, bonusAuspicious, throwMode;
|
|
let spellAspectIndex = null;
|
|
let rollDifficulty = 1;
|
|
if (isMagic) {
|
|
const skillAspectIndex = Number(params.aspectskill ?? 0);
|
|
spellAspectIndex = Number(params.aspectspeciality ?? skillAspectIndex);
|
|
aspectIndex = skillAspectIndex;
|
|
bonusMalus = Number(params.bonusmalusskill ?? 0);
|
|
bonusAuspicious = Number(params.bonusauspiciousdice ?? 0);
|
|
rollDifficulty = Math.max(1, Number(params.rolldifficulty ?? 1));
|
|
throwMode = Number(params.typeofthrow ?? 0);
|
|
const aspectDice = sys.aspect[ASPECT_NAMES[aspectIndex]]?.value ?? 0;
|
|
const bonusSpec = Number(params.bonusmalusspeciality ?? 0);
|
|
const heiDice = Number(params.heispend ?? 0);
|
|
numberofdice = numberofdice + aspectDice + bonusMalus + 1 + bonusSpec + heiDice;
|
|
} else {
|
|
aspectIndex = Number(params.aspect ?? 0);
|
|
bonusMalus = Number(params.bonusmalus ?? 0);
|
|
const woundMalus = Number(params.woundmalus ?? 0);
|
|
bonusAuspicious = Number(params.bonusauspiciousdice ?? 0);
|
|
throwMode = Number(params.typeofthrow ?? 0);
|
|
const aspectDice = typeLibel !== "aspect" ? sys.aspect[ASPECT_NAMES[aspectIndex]]?.value ?? 0 : 0;
|
|
numberofdice = numberofdice + aspectDice + bonusMalus - woundMalus;
|
|
if (isSpecial) numberofdice += 1;
|
|
}
|
|
if (numberofdice <= 0) {
|
|
ui.notifications.warn(game.i18n.localize("CDE.Error0"));
|
|
return;
|
|
}
|
|
const roll = new Roll(`${numberofdice}d10`);
|
|
await roll.evaluate();
|
|
const rollModeKey = ROLL_MODES[throwMode] ?? "roll";
|
|
const wuXingAspectName = spellAspectIndex !== null ? ASPECT_NAMES[spellAspectIndex] : ASPECT_NAMES[aspectIndex];
|
|
const allResults = roll.dice[0]?.results ?? [];
|
|
const faces = countFaces(allResults);
|
|
const results = computeWuXingResults(faces, wuXingAspectName, bonusAuspicious);
|
|
if (!results) return;
|
|
const spellPower = isMagic ? results.successesdice * rollDifficulty : null;
|
|
const modParts = [];
|
|
if (isMagic) {
|
|
const bm = Number(params.bonusmalusskill ?? 0);
|
|
const bs = Number(params.bonusmalusspeciality ?? 0);
|
|
const hs = Number(params.heispend ?? 0);
|
|
const ba = Number(params.bonusauspiciousdice ?? 0);
|
|
if (bm !== 0) modParts.push(`${bm > 0 ? "+" : ""}${bm} ${game.i18n.localize("CDE.BonusMalus")}`);
|
|
if (bs !== 0) modParts.push(`${bs > 0 ? "+" : ""}${bs} ${game.i18n.localize("CDE.SpellBonus")}`);
|
|
if (ba !== 0) modParts.push(`+${ba} ${game.i18n.localize("CDE.BonusAuspiciousDice")}`);
|
|
if (hs !== 0) modParts.push(`${hs} ${game.i18n.localize("CDE.HeiSpend")}`);
|
|
if (rollDifficulty !== 1) modParts.push(`\xD7${rollDifficulty} ${game.i18n.localize("CDE.RollDifficulty")}`);
|
|
} else {
|
|
const bm = Number(params.bonusmalus ?? 0);
|
|
const wm = Number(params.woundmalus ?? 0);
|
|
const ba = Number(params.bonusauspiciousdice ?? 0);
|
|
if (bm !== 0) modParts.push(`${bm > 0 ? "+" : ""}${bm} ${game.i18n.localize("CDE.BonusMalus")}`);
|
|
if (wm !== 0) modParts.push(`-${wm} ${game.i18n.localize("CDE.WoundMalus")}`);
|
|
if (ba !== 0) modParts.push(`+${ba} ${game.i18n.localize("CDE.BonusAuspiciousDice")}`);
|
|
}
|
|
const msg = await sendResultMessage(actor, {
|
|
// Roll identity
|
|
rollLabel: title,
|
|
aspectName: wuXingAspectName,
|
|
aspectLabel: game.i18n.localize(ASPECT_LABELS[wuXingAspectName] ?? ""),
|
|
aspectIcon: ASPECT_ICONS[wuXingAspectName] ?? "",
|
|
totalDice: numberofdice,
|
|
modifiersText: modParts.length ? modParts.join(" \xB7 ") : "",
|
|
// Spell power (magic only)
|
|
spellPower,
|
|
rollDifficulty: isMagic ? rollDifficulty : null,
|
|
// Actor info
|
|
actorName: actor.name ?? "",
|
|
actorImg: actor.img ?? "",
|
|
// Wu Xing results
|
|
aspect: wuXingAspectName,
|
|
...results,
|
|
// Die face counts
|
|
d1: faces[1],
|
|
d2: faces[2],
|
|
d3: faces[3],
|
|
d4: faces[4],
|
|
d5: faces[5],
|
|
d6: faces[6],
|
|
d7: faces[7],
|
|
d8: faces[8],
|
|
d9: faces[9],
|
|
d0: faces[0]
|
|
}, roll, rollModeKey);
|
|
if (game.modules.get("dice-so-nice")?.active && msg?.id) {
|
|
await game.dice3d.waitFor3DAnimationByMessageID(msg.id);
|
|
}
|
|
}
|
|
|
|
// src/ui/sheets/actors/base.js
|
|
var { HandlebarsApplicationMixin } = foundry.applications.api;
|
|
var CDEBaseActorSheet = class _CDEBaseActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["fvtt-chroniques-de-l-etrange", "actor"],
|
|
position: { width: 920, height: 800 },
|
|
window: { resizable: true },
|
|
form: { submitOnChange: true },
|
|
dragDrop: [{ dragSelector: ".item, [data-drag='true']", dropSelector: null }],
|
|
actions: {
|
|
create: _CDEBaseActorSheet.#onItemCreate,
|
|
edit: _CDEBaseActorSheet.#onItemEdit,
|
|
delete: _CDEBaseActorSheet.#onItemDelete
|
|
}
|
|
};
|
|
tabGroups = { primary: "description" };
|
|
get title() {
|
|
return this.document.name;
|
|
}
|
|
async _prepareContext() {
|
|
const descriptionHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true });
|
|
const cssClass = this.options.classes?.join(" ") ?? "";
|
|
return {
|
|
actor: this.document,
|
|
system: this.document.system,
|
|
systemData: this.document.system,
|
|
systemFields: this.document.system.schema.fields,
|
|
items: this.document.items.contents,
|
|
descriptionHTML,
|
|
editable: this.isEditable,
|
|
cssClass
|
|
};
|
|
}
|
|
// Restore the active tab after every render (including re-renders from submitOnChange).
|
|
// AppV2 does NOT preserve tab state natively — we must re-apply it from this.tabGroups,
|
|
// which is dynamically updated by changeTab() when the user clicks a tab.
|
|
_onRender(context, options) {
|
|
super._onRender?.(context, options);
|
|
for (const [group, tab] of Object.entries(this.tabGroups)) {
|
|
this.changeTab(tab, group, { force: true });
|
|
}
|
|
}
|
|
static async #onItemCreate(event, target) {
|
|
const type = target.dataset.type ?? "item";
|
|
const cls = getDocumentClass("Item");
|
|
const labels = {
|
|
item: "CDE.ItemNew",
|
|
weapon: "CDE.WeaponNew",
|
|
armor: "CDE.ArmorNew",
|
|
sanhei: "CDE.SanheiNew",
|
|
ingredient: "CDE.IngredientNew",
|
|
kungfu: "CDE.KFNew",
|
|
spell: "CDE.SpellNew",
|
|
supernatural: "CDE.SupernaturalNew"
|
|
};
|
|
const name = game.i18n.localize(labels[type] ?? "CDE.ItemNew");
|
|
return cls.create({ name, type }, { parent: this.document });
|
|
}
|
|
static #onItemEdit(event, target) {
|
|
const itemId = target.closest(".item")?.dataset.itemId;
|
|
const item = this.document.items.get(itemId);
|
|
if (item) item.sheet.render(true);
|
|
}
|
|
static #onItemDelete(event, target) {
|
|
const itemId = target.closest(".item")?.dataset.itemId;
|
|
const item = this.document.items.get(itemId);
|
|
if (item) item.delete();
|
|
}
|
|
};
|
|
|
|
// src/ui/sheets/actors/character.js
|
|
var CDECharacterSheet = class extends CDEBaseActorSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["character"]
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-character-sheet.html" }
|
|
};
|
|
tabGroups = { primary: "description" };
|
|
async _prepareContext() {
|
|
const context = await super._prepareContext();
|
|
context.equipments = context.items.filter((item) => item.type === "item");
|
|
context.weapons = context.items.filter((item) => item.type === "weapon");
|
|
context.armors = context.items.filter((item) => item.type === "armor");
|
|
context.sanheis = context.items.filter((item) => item.type === "sanhei");
|
|
context.ingredients = context.items.filter((item) => item.type === "ingredient");
|
|
context.spells = context.items.filter((item) => item.type === "spell");
|
|
context.kungfus = context.items.filter((item) => item.type === "kungfu");
|
|
context.CDE = { MAGICS, SUBTYPES };
|
|
return context;
|
|
}
|
|
_onRender(context, options) {
|
|
super._onRender?.(context, options);
|
|
this.#bindInitiativeControls();
|
|
this.#bindPrefs();
|
|
this.#bindRollButtons();
|
|
}
|
|
#bindInitiativeControls() {
|
|
const buttons = this.element?.querySelectorAll(".click-initiative");
|
|
if (!buttons?.length) return;
|
|
buttons.forEach((button) => {
|
|
button.addEventListener("click", async () => {
|
|
const action = button.dataset.libelId;
|
|
let initiative = this.document.system.initiative ?? 1;
|
|
if (action === "plus") {
|
|
initiative = initiative >= 24 ? 1 : initiative + 1;
|
|
await this.document.update({ "system.initiative": initiative });
|
|
return;
|
|
}
|
|
if (action === "minus") {
|
|
initiative = initiative <= 1 ? 24 : initiative - 1;
|
|
await this.document.update({ "system.initiative": initiative });
|
|
return;
|
|
}
|
|
if (action === "create") {
|
|
await rollInitiativePC(this.document);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
#bindPrefs() {
|
|
const button = this.element?.querySelector(".click-prefs");
|
|
if (!button) return;
|
|
button.addEventListener("click", async () => {
|
|
const current = this.document.system.prefs?.typeofthrow ?? { choice: "0", check: true };
|
|
const html = `
|
|
<form class="flexcol">
|
|
<div class="form-group">
|
|
<label>${game.i18n.localize("CDE.ThrowType")}</label>
|
|
<select name="choice" value="${current.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 Dialog.prompt({
|
|
title: game.i18n.localize("CDE.Preferences"),
|
|
content: html,
|
|
label: game.i18n.localize("CDE.Validate"),
|
|
callback: (dlg) => {
|
|
const choice = dlg.querySelector("select[name='choice']")?.value ?? "0";
|
|
const check = dlg.querySelector("input[name='check']")?.checked ?? false;
|
|
return { choice, check };
|
|
}
|
|
});
|
|
if (prefs) {
|
|
await this.document.update({
|
|
"system.prefs.typeofthrow.choice": String(prefs.choice),
|
|
"system.prefs.typeofthrow.check": !!prefs.check
|
|
});
|
|
}
|
|
});
|
|
}
|
|
#bindRollButtons() {
|
|
const cells = this.element?.querySelectorAll("td.click[data-libel-id], td.click2[data-libel-id], .cde-roll-trigger[data-libel-id]");
|
|
if (!cells?.length) return;
|
|
cells.forEach((cell) => {
|
|
cell.addEventListener("click", (event) => {
|
|
event.preventDefault();
|
|
const rollKey = cell.dataset.libelId;
|
|
if (rollKey) rollForActor(this.document, rollKey);
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
// src/ui/sheets/actors/npc.js
|
|
var CDENpcSheet = class extends CDEBaseActorSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["npc"]
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-npc-sheet.html" }
|
|
};
|
|
tabGroups = { primary: "description" };
|
|
async _prepareContext() {
|
|
const context = await super._prepareContext();
|
|
context.supernaturals = context.items.filter((item) => item.type === "supernatural");
|
|
context.spells = context.items.filter((item) => item.type === "spell");
|
|
context.kungfus = context.items.filter((item) => item.type === "kungfu");
|
|
context.equipments = context.items.filter((item) => item.type === "item");
|
|
return context;
|
|
}
|
|
_onRender(context, options) {
|
|
super._onRender?.(context, options);
|
|
this.#bindInitiativeControls();
|
|
}
|
|
#bindInitiativeControls() {
|
|
const buttons = this.element?.querySelectorAll(".click-initiative-npc");
|
|
if (!buttons?.length) return;
|
|
buttons.forEach((button) => {
|
|
button.addEventListener("click", async () => {
|
|
const action = button.dataset.libelId;
|
|
let initiative = this.document.system.initiative ?? 1;
|
|
if (action === "plus") {
|
|
initiative = initiative >= 24 ? 1 : initiative + 1;
|
|
await this.document.update({ "system.initiative": initiative });
|
|
return;
|
|
}
|
|
if (action === "minus") {
|
|
initiative = initiative <= 1 ? 24 : initiative - 1;
|
|
await this.document.update({ "system.initiative": initiative });
|
|
return;
|
|
}
|
|
if (action === "create") {
|
|
await rollInitiativeNPC(this.document);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
// src/ui/sheets/actors/tinji.js
|
|
var CDETinjiSheet = class extends CDEBaseActorSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["tinji"]
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-tinji-sheet.html" }
|
|
};
|
|
tabGroups = { primary: "tinji" };
|
|
};
|
|
|
|
// src/ui/sheets/actors/loksyu.js
|
|
var CDELoksyuSheet = class extends CDEBaseActorSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["loksyu"]
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-loksyu-sheet.html" }
|
|
};
|
|
tabGroups = { primary: "loksyu" };
|
|
};
|
|
|
|
// src/ui/sheets/items/base.js
|
|
var { HandlebarsApplicationMixin: HandlebarsApplicationMixin2 } = foundry.applications.api;
|
|
var CDEBaseItemSheet = class extends HandlebarsApplicationMixin2(foundry.applications.sheets.ItemSheetV2) {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["fvtt-chroniques-de-l-etrange", "item"],
|
|
position: { width: 520, height: "auto" },
|
|
window: { resizable: true },
|
|
form: { submitOnChange: true },
|
|
actions: {}
|
|
};
|
|
tabGroups = { primary: "details" };
|
|
get title() {
|
|
return this.document.name;
|
|
}
|
|
async _prepareContext() {
|
|
const cssClass = this.options.classes?.join(" ") ?? "";
|
|
const enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true });
|
|
const enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.notes ?? "", { async: true });
|
|
return {
|
|
item: this.document,
|
|
system: this.document.system,
|
|
systemData: this.document.system,
|
|
systemFields: this.document.system.schema.fields,
|
|
editable: this.isEditable,
|
|
cssClass,
|
|
enrichedDescription,
|
|
enrichedNotes,
|
|
descriptionHTML: enrichedDescription,
|
|
notesHTML: enrichedNotes
|
|
};
|
|
}
|
|
// Restore the active tab after every render (including re-renders from submitOnChange).
|
|
_onRender(context, options) {
|
|
super._onRender?.(context, options);
|
|
for (const [group, tab] of Object.entries(this.tabGroups)) {
|
|
this.changeTab(tab, group, { force: true });
|
|
}
|
|
}
|
|
};
|
|
|
|
// src/ui/sheets/items/item.js
|
|
var CDEItemSheet = class extends CDEBaseItemSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["equipment"],
|
|
position: { width: 560, height: 460 }
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-item-sheet.html" }
|
|
};
|
|
};
|
|
|
|
// src/ui/sheets/items/kungfu.js
|
|
var CDEKungfuSheet = class extends CDEBaseItemSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["kungfu"],
|
|
position: { width: 720, height: 680 }
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-kungfu-sheet.html" }
|
|
};
|
|
async _prepareContext() {
|
|
const context = await super._prepareContext();
|
|
const techniques = this.document.system.techniques ?? {};
|
|
const enrich = (value) => foundry.applications.ux.TextEditor.implementation.enrichHTML(value ?? "", { async: true });
|
|
context.descriptionTechnique1HTML = await enrich(techniques.technique1?.technique);
|
|
context.descriptionTechnique2HTML = await enrich(techniques.technique2?.technique);
|
|
context.descriptionTechnique3HTML = await enrich(techniques.technique3?.technique);
|
|
return context;
|
|
}
|
|
};
|
|
|
|
// src/ui/sheets/items/spell.js
|
|
var CDESpellSheet = class extends CDEBaseItemSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["spell"],
|
|
position: { width: 660, height: 680 }
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-spell-sheet.html" }
|
|
};
|
|
async _prepareContext() {
|
|
const context = await super._prepareContext();
|
|
const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true });
|
|
context.spellDescriptionHTML = await enrich(this.document.system.description);
|
|
context.componentsDescriptionHTML = await enrich(this.document.system.components);
|
|
context.effectsDescriptionHTML = await enrich(this.document.system.effects);
|
|
context.examplesDescriptionHTML = await enrich(this.document.system.examples);
|
|
return context;
|
|
}
|
|
};
|
|
|
|
// src/ui/sheets/items/supernatural.js
|
|
var CDESupernaturalSheet = class extends CDEBaseItemSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["supernatural"],
|
|
position: { width: 560, height: 520 }
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-supernatural-sheet.html" }
|
|
};
|
|
async _prepareContext() {
|
|
const context = await super._prepareContext();
|
|
const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true });
|
|
context.effectsHTML = await enrich(this.document.system.effects);
|
|
return context;
|
|
}
|
|
};
|
|
|
|
// src/ui/sheets/items/weapon.js
|
|
var CDEWeaponSheet = class extends CDEBaseItemSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["weapon"],
|
|
position: { width: 580, height: 520 }
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-weapon-sheet.html" }
|
|
};
|
|
};
|
|
|
|
// src/ui/sheets/items/armor.js
|
|
var CDEArmorSheet = class extends CDEBaseItemSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["armor"],
|
|
position: { width: 520, height: 460 }
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-armor-sheet.html" }
|
|
};
|
|
};
|
|
|
|
// src/ui/sheets/items/sanhei.js
|
|
var CDESanheiSheet = class extends CDEBaseItemSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["sanhei"],
|
|
position: { width: 580, height: 620 }
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-sanhei-sheet.html" }
|
|
};
|
|
async _prepareContext() {
|
|
const context = await super._prepareContext();
|
|
const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true });
|
|
const props = this.document.system.properties;
|
|
context.prop1DescriptionHTML = await enrich(props.prop1.description);
|
|
context.prop2DescriptionHTML = await enrich(props.prop2.description);
|
|
context.prop3DescriptionHTML = await enrich(props.prop3.description);
|
|
context.propFields = this.document.system.schema.fields.properties.fields;
|
|
return context;
|
|
}
|
|
};
|
|
|
|
// src/ui/sheets/items/ingredient.js
|
|
var CDEIngredientSheet = class extends CDEBaseItemSheet {
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["ingredient"],
|
|
position: { width: 520, height: 460 }
|
|
};
|
|
static PARTS = {
|
|
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-ingredient-sheet.html" }
|
|
};
|
|
};
|
|
|
|
// src/migration.js
|
|
var MIGRATION_VERSION = "3.0.0";
|
|
function registerSettings() {
|
|
game.settings.register(SYSTEM_ID, "migrationVersion", {
|
|
name: "Migration version",
|
|
scope: "world",
|
|
config: false,
|
|
type: String,
|
|
default: "0.0.0"
|
|
});
|
|
}
|
|
async function migrateIfNeeded() {
|
|
const current = game.system.version ?? MIGRATION_VERSION;
|
|
const stored = game.settings.get(SYSTEM_ID, "migrationVersion") ?? "0.0.0";
|
|
if (!foundry.utils.isNewerVersion(current, stored)) return;
|
|
ui.notifications.info(`CHRONIQUESDELETRANGE | Migration vers ${current} en cours...`, { permanent: true });
|
|
await migrateActors();
|
|
await migrateItems();
|
|
await migrateCompendiumActors();
|
|
await migrateCompendiumItems();
|
|
await game.settings.set(SYSTEM_ID, "migrationVersion", current);
|
|
ui.notifications.info(`CHRONIQUESDELETRANGE | Migration vers ${current} termin\xE9e.`);
|
|
}
|
|
async function migrateActors() {
|
|
const updates = [];
|
|
for (const actor of game.actors.contents) {
|
|
const updateData = migrateActorData(actor);
|
|
if (Object.keys(updateData).length > 0) {
|
|
updates.push(actor.update(updateData, { enforceTypes: false }));
|
|
}
|
|
}
|
|
await Promise.all(updates);
|
|
}
|
|
async function migrateCompendiumActors() {
|
|
const packs = game.packs.filter((p) => p.documentName === "Actor" && p.metadata.system === SYSTEM_ID);
|
|
for (const pack of packs) {
|
|
const content = await pack.getDocuments();
|
|
for (const actor of content) {
|
|
const updateData = migrateActorData(actor);
|
|
if (Object.keys(updateData).length > 0) {
|
|
await actor.update(updateData, { pack: pack.collection, enforceTypes: false });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
async function migrateItems() {
|
|
const updates = [];
|
|
for (const item of game.items.contents) {
|
|
const updateData = migrateItemData(item);
|
|
if (Object.keys(updateData).length > 0) {
|
|
updates.push(item.update(updateData, { enforceTypes: false }));
|
|
}
|
|
}
|
|
await Promise.all(updates);
|
|
}
|
|
async function migrateCompendiumItems() {
|
|
const packs = game.packs.filter((p) => p.documentName === "Item" && p.metadata.system === SYSTEM_ID);
|
|
for (const pack of packs) {
|
|
const content = await pack.getDocuments();
|
|
for (const item of content) {
|
|
const updateData = migrateItemData(item);
|
|
if (Object.keys(updateData).length > 0) {
|
|
await item.update(updateData, { pack: pack.collection, enforceTypes: false });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function migrateActorData(actor) {
|
|
const updateData = {};
|
|
const system = actor.system ?? {};
|
|
const actorType = actor.type;
|
|
const legacyMagic = system.magics?.masteryofthway;
|
|
if (legacyMagic && !system.magics?.masteryoftheway) {
|
|
updateData["system.magics.masteryoftheway"] = legacyMagic;
|
|
updateData["system.magics.-=masteryofthway"] = null;
|
|
}
|
|
if ((actorType === "character" || actorType === "npc") && !system.prefs?.typeofthrow) {
|
|
const defaultCheck = actorType === "character";
|
|
updateData["system.prefs.typeofthrow"] = { check: defaultCheck, choice: "0" };
|
|
}
|
|
if (actorType === "npc") {
|
|
if (system.levelofthreat !== void 0 && system.threat === void 0) {
|
|
updateData["system.threat"] = system.levelofthreat;
|
|
updateData["system.-=levelofthreat"] = null;
|
|
}
|
|
if (system.powerofnuisance !== void 0 && system.nuisance === void 0) {
|
|
updateData["system.nuisance"] = system.powerofnuisance;
|
|
updateData["system.-=powerofnuisance"] = null;
|
|
}
|
|
}
|
|
if (actorType === "character" && typeof system.guardian === "string") {
|
|
const guardianNum = parseInt(system.guardian, 10);
|
|
if (!isNaN(guardianNum)) {
|
|
updateData["system.guardian"] = guardianNum;
|
|
}
|
|
}
|
|
return updateData;
|
|
}
|
|
function migrateItemData(item) {
|
|
const updateData = {};
|
|
const system = item.system ?? {};
|
|
return updateData;
|
|
}
|
|
|
|
// src/system.js
|
|
Hooks.once("i18nInit", preLocalizeConfig);
|
|
Hooks.once("init", async () => {
|
|
console.info(`CHRONIQUESDELETRANGE | Initializing ${SYSTEM_ID}`);
|
|
registerSettings();
|
|
game.system.CONST = { MAGICS, SUBTYPES };
|
|
CONFIG.Actor.dataModels = {
|
|
[ACTOR_TYPES.character]: CharacterDataModel,
|
|
[ACTOR_TYPES.npc]: NpcDataModel,
|
|
[ACTOR_TYPES.tinji]: TinjiDataModel,
|
|
[ACTOR_TYPES.loksyu]: LoksyuDataModel
|
|
};
|
|
CONFIG.Item.dataModels = {
|
|
[ITEM_TYPES.item]: EquipmentDataModel,
|
|
[ITEM_TYPES.kungfu]: KungfuDataModel,
|
|
[ITEM_TYPES.spell]: SpellDataModel,
|
|
[ITEM_TYPES.supernatural]: SupernaturalDataModel,
|
|
[ITEM_TYPES.weapon]: WeaponDataModel,
|
|
[ITEM_TYPES.armor]: ArmorDataModel,
|
|
[ITEM_TYPES.sanhei]: SanheiDataModel,
|
|
[ITEM_TYPES.ingredient]: IngredientDataModel
|
|
};
|
|
CONFIG.Actor.documentClass = CDEActor;
|
|
CONFIG.Item.documentClass = CDEItem;
|
|
CONFIG.ChatMessage.documentClass = CDEMessage;
|
|
configureRuntime();
|
|
foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Actor, "core", ActorSheet);
|
|
foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Item, "core", ItemSheet);
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDECharacterSheet, {
|
|
types: [ACTOR_TYPES.character],
|
|
makeDefault: true,
|
|
label: "CDE Character Sheet (V2)"
|
|
});
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDENpcSheet, {
|
|
types: [ACTOR_TYPES.npc],
|
|
makeDefault: true,
|
|
label: "CDE NPC Sheet (V2)"
|
|
});
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDETinjiSheet, {
|
|
types: [ACTOR_TYPES.tinji],
|
|
makeDefault: true,
|
|
label: "CDE Tinji Sheet (V2)"
|
|
});
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDELoksyuSheet, {
|
|
types: [ACTOR_TYPES.loksyu],
|
|
makeDefault: true,
|
|
label: "CDE Loksyu Sheet (V2)"
|
|
});
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEItemSheet, {
|
|
types: [ITEM_TYPES.item],
|
|
makeDefault: true,
|
|
label: "CDE Item Sheet (V2)"
|
|
});
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEKungfuSheet, {
|
|
types: [ITEM_TYPES.kungfu],
|
|
makeDefault: true,
|
|
label: "CDE KungFu Sheet (V2)"
|
|
});
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESpellSheet, {
|
|
types: [ITEM_TYPES.spell],
|
|
makeDefault: true,
|
|
label: "CDE Spell Sheet (V2)"
|
|
});
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESupernaturalSheet, {
|
|
types: [ITEM_TYPES.supernatural],
|
|
makeDefault: true,
|
|
label: "CDE Supernatural Sheet (V2)"
|
|
});
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEWeaponSheet, {
|
|
types: [ITEM_TYPES.weapon],
|
|
makeDefault: true,
|
|
label: "CDE Weapon Sheet (V2)"
|
|
});
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEArmorSheet, {
|
|
types: [ITEM_TYPES.armor],
|
|
makeDefault: true,
|
|
label: "CDE Armor Sheet (V2)"
|
|
});
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESanheiSheet, {
|
|
types: [ITEM_TYPES.sanhei],
|
|
makeDefault: true,
|
|
label: "CDE Sanhei Sheet (V2)"
|
|
});
|
|
foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEIngredientSheet, {
|
|
types: [ITEM_TYPES.ingredient],
|
|
makeDefault: true,
|
|
label: "CDE Ingredient Sheet (V2)"
|
|
});
|
|
await preloadPartials();
|
|
registerHandlebarsHelpers();
|
|
registerDice();
|
|
Hooks.on("renderSettings", (_app, html) => injectCompendiumLink(html));
|
|
console.info(`CHRONIQUESDELETRANGE | Initialized`);
|
|
});
|
|
Hooks.once("ready", async () => {
|
|
if (!game.modules.get("lib-wrapper")?.active && game.user.isGM) {
|
|
ui.notifications.error("System fvtt-chroniques-de-l-etrange requires the 'libWrapper' module. Please install and activate it.");
|
|
}
|
|
await migrateIfNeeded();
|
|
});
|
|
function injectCompendiumLink(html) {
|
|
const header = html[0]?.querySelector?.("h4.divider");
|
|
if (!header) return;
|
|
const section = document.createElement("section");
|
|
section.classList.add("settings", "flexcol");
|
|
section.innerHTML = `
|
|
<section class="links flexcol">
|
|
<img class="logo-info" src="systems/fvtt-chroniques-de-l-etrange/images/logo_jeu.png" />
|
|
<h4 class="divider"> Lien utile <i class="fa-light fa-up-right-from-square"></i> </h4>
|
|
</section>
|
|
<section class="settings flexcol">
|
|
<button type="button" data-action="open-cde-link">
|
|
<i class="fa fa-download"></i> Compendium pour Les CdE <i class="fa-light fa-up-right-from-square"></i>
|
|
</button>
|
|
<details>
|
|
<summary><small>Guide d'installation</small></summary>
|
|
<small style="text-align: center;">
|
|
<p>Rendez-vous sur le site de l'\xE9diteur, t\xE9l\xE9chargez les PDF contenant les liens vers les compendia, puis ajoutez leurs manifestes dans Foundry.</p>
|
|
</small>
|
|
</details>
|
|
</section>
|
|
`;
|
|
section.querySelector("button[data-action='open-cde-link']")?.addEventListener("click", () => {
|
|
window.open("https://antre-monde.com/les-chroniques-de-letrengae/", "_blank");
|
|
});
|
|
header.parentNode.insertBefore(section, header);
|
|
}
|
|
//# sourceMappingURL=system.js.map
|