feat(magic): reorder schools, fix Wu Xing aspect & power formula

- `magicOrder` ArrayField + ▲/▼ buttons for manual reordering
- Magic rolls use school's aspect for Wu Xing, not speciality's element
- Spell power: `difficulty × (aspectValue + freePowerLevels)` (not `successes × diff`)
- Prompt replaces `aspectspeciality`/`bonusmalusspeciality`/`heispend` with `freepowerlevels`

fix: code review issues
- combat.js: guard undefined `ids` in rollInitiative
- rolling.js: catch Dice So Nice promise, normalize French→English kungfu aspects
- weapon/armor/ingredient: `{ min: 0 }` on quantity
- character.js/npc.js: catch rollForActor fire-and-forget promises
- roll-actions.js/tinji-app.js: await ChatMessage.create
- sanhei.js: null guard on properties
- spell.js/kungfu.js: fix aspect name comments (French→English)
This commit is contained in:
2026-06-10 15:54:31 +02:00
parent 188717c925
commit 75f79c1c08
21 changed files with 304 additions and 189 deletions
+25
View File
@@ -2179,6 +2179,25 @@ section.npc .cde-neon-tabs .item.active {
.cde-magic-toggle:hover i { .cde-magic-toggle:hover i {
color: #e2e8f4; color: #e2e8f4;
} }
.cde-magic-order-btn {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #7d94b8;
border-radius: 3px;
flex-shrink: 0;
transition: color 0.12s, background 0.12s;
}
.cde-magic-order-btn i {
font-size: 9px;
}
.cde-magic-order-btn:hover {
color: #e2e8f4;
background: rgba(38, 56, 83, 0.25);
}
.cde-magic-specialities { .cde-magic-specialities {
border-top: 1px solid #1a2436; border-top: 1px solid #1a2436;
padding: 4px 0; padding: 4px 0;
@@ -3523,6 +3542,12 @@ ol.item-list li.item .item-controls a.item-control:hover {
color: var(--rr-accent, #e2e8f4); color: var(--rr-accent, #e2e8f4);
text-shadow: 0 0 12px var(--rr-accent, transparent); text-shadow: 0 0 12px var(--rr-accent, transparent);
} }
.cde-roll-result .cde-rr-hero .cde-rr-spell-power .cde-rr-spell-power-formula {
font-size: 10px;
color: #7d94b8;
margin: 2px 0;
white-space: nowrap;
}
.cde-roll-result .cde-rr-hero .cde-rr-spell-power .cde-rr-spell-power-label { .cde-roll-result .cde-rr-hero .cde-rr-spell-power .cde-rr-spell-power-label {
font-size: 9px; font-size: 9px;
font-weight: 700; font-weight: 700;
+24
View File
@@ -2253,6 +2253,23 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
&:hover i { color: @cde-text; } &:hover i { color: @cde-text; }
} }
.cde-magic-order-btn {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: @cde-muted;
border-radius: 3px;
flex-shrink: 0;
transition: color 0.12s, background 0.12s;
i { font-size: 9px; }
&:hover { color: @cde-text; background: fade(@cde-border-hi, 25%); }
}
// Specialities list // Specialities list
.cde-magic-specialities { .cde-magic-specialities {
border-top: 1px solid @cde-border; border-top: 1px solid @cde-border;
@@ -3582,6 +3599,13 @@ ol.item-list {
text-shadow: 0 0 12px var(--rr-accent, transparent); text-shadow: 0 0 12px var(--rr-accent, transparent);
} }
.cde-rr-spell-power-formula {
font-size: 10px;
color: @cde-muted;
margin: 2px 0;
white-space: nowrap;
}
.cde-rr-spell-power-label { .cde-rr-spell-power-label {
font-size: 9px; font-size: 9px;
font-weight: 700; font-weight: 700;
+94 -53
View File
@@ -774,9 +774,8 @@ var CharacterDataModel = class extends foundry.abstract.TypeDataModel {
typeofthrow: numberField(0), typeofthrow: numberField(0),
aspectskill: numberField(0), aspectskill: numberField(0),
bonusmalusskill: numberField(0), bonusmalusskill: numberField(0),
aspectspeciality: numberField(0),
rolldifficulty: numberField(0), rolldifficulty: numberField(0),
bonusmalusspeciality: numberField(0) freepowerlevels: numberField(0)
}) })
}), }),
aspect: new fields.SchemaField({ aspect: new fields.SchemaField({
@@ -815,6 +814,10 @@ var CharacterDataModel = class extends foundry.abstract.TypeDataModel {
nine: componentField(), nine: componentField(),
zero: componentField() zero: componentField()
}), }),
magicOrder: new fields.ArrayField(
new fields.StringField({ required: true, nullable: false, initial: "" }),
{ required: true, initial: [] }
),
magics: new fields.SchemaField({ magics: new fields.SchemaField({
internalcinnabar: magicField(), internalcinnabar: magicField(),
alchemy: magicField(), alchemy: magicField(),
@@ -931,7 +934,7 @@ var KungfuDataModel = class extends foundry.abstract.TypeDataModel {
orientation: stringField("yin"), orientation: stringField("yin"),
// yin | yang | yinyang // yin | yang | yinyang
aspect: stringField("metal"), aspect: stringField("metal"),
// metal | eau | terre | feu | bois // metal | water | earth | fire | wood
skill: stringField("kungfu"), skill: stringField("kungfu"),
// kungfu | rangedcombat // kungfu | rangedcombat
speciality: stringField(""), speciality: stringField(""),
@@ -957,7 +960,7 @@ var SpellDataModel = class extends foundry.abstract.TypeDataModel {
description: htmlField(""), description: htmlField(""),
specialityname: stringField(""), specialityname: stringField(""),
associatedelement: stringField("metal"), associatedelement: stringField("metal"),
// metal | eau | terre | feu | bois // metal | water | earth | fire | wood
hei: stringField(""), hei: stringField(""),
realizationtimeritual: stringField(""), realizationtimeritual: stringField(""),
realizationtimeaccelerated: stringField(""), realizationtimeaccelerated: stringField(""),
@@ -1012,7 +1015,7 @@ var WeaponDataModel = class extends foundry.abstract.TypeDataModel {
// contact | courte | mediane | longue | extreme // contact | courte | mediane | longue | extreme
obtainLevel: intField(0, { min: 0, max: 5 }), obtainLevel: intField(0, { min: 0, max: 5 }),
obtainDifficulty: intField(0, { min: 0, max: 3 }), obtainDifficulty: intField(0, { min: 0, max: 3 }),
quantity: intField(1), quantity: intField(1, { min: 0 }),
notes: htmlField("") notes: htmlField("")
}; };
} }
@@ -1032,7 +1035,7 @@ var ArmorDataModel = class extends foundry.abstract.TypeDataModel {
domain: stringField(""), domain: stringField(""),
obtainLevel: intField(0, { min: 0, max: 5 }), obtainLevel: intField(0, { min: 0, max: 5 }),
obtainDifficulty: intField(0, { min: 0, max: 3 }), obtainDifficulty: intField(0, { min: 0, max: 3 }),
quantity: intField(1), quantity: intField(1, { min: 0 }),
notes: htmlField("") notes: htmlField("")
}; };
} }
@@ -1078,7 +1081,7 @@ var IngredientDataModel = class extends foundry.abstract.TypeDataModel {
school: stringField("all"), school: stringField("all"),
obtainLevel: intField(0, { min: 0, max: 5 }), obtainLevel: intField(0, { min: 0, max: 5 }),
obtainDifficulty: intField(0, { min: 0, max: 3 }), obtainDifficulty: intField(0, { min: 0, max: 3 }),
quantity: intField(1), quantity: intField(1, { min: 0 }),
notes: htmlField("") notes: htmlField("")
}; };
} }
@@ -1568,20 +1571,16 @@ async function showMagicPrompt(params) {
aspectskill: Number(params.aspectskill ?? 0), aspectskill: Number(params.aspectskill ?? 0),
bonusmalusskill: params.bonusmalusskill ?? 0, bonusmalusskill: params.bonusmalusskill ?? 0,
bonusauspiciousdice: params.bonusauspiciousdice ?? 0, bonusauspiciousdice: params.bonusauspiciousdice ?? 0,
aspectspeciality: Number(params.aspectspeciality ?? 0),
rolldifficulty: params.rolldifficulty ?? 1, rolldifficulty: params.rolldifficulty ?? 1,
bonusmalusspeciality: params.bonusmalusspeciality ?? 0, freepowerlevels: params.freepowerlevels ?? 0,
heispend: params.heispend ?? 0,
typeofthrow: Number(params.typeofthrow ?? 0) typeofthrow: Number(params.typeofthrow ?? 0)
}, },
fields: [ fields: [
"aspectskill", "aspectskill",
"bonusmalusskill", "bonusmalusskill",
"bonusauspiciousdice", "bonusauspiciousdice",
"aspectspeciality",
"rolldifficulty", "rolldifficulty",
"bonusmalusspeciality", "freepowerlevels",
"heispend",
"typeofthrow" "typeofthrow"
] ]
}); });
@@ -1711,7 +1710,9 @@ async function rollForActor(actor, rollKey) {
const kfSkill = kfItem.system.skill ?? "kungfu"; const kfSkill = kfItem.system.skill ?? "kungfu";
numberofdice = sys.skills?.[kfSkill]?.value ?? 0; numberofdice = sys.skills?.[kfSkill]?.value ?? 0;
title = `${kfItem.name} [${game.i18n.localize(sys.skills?.[kfSkill]?.label ?? "CDE.KungFu")}]`; title = `${kfItem.name} [${game.i18n.localize(sys.skills?.[kfSkill]?.label ?? "CDE.KungFu")}]`;
kfDefaultAspect = ASPECT_NAMES.indexOf(kfItem.system.aspect ?? "metal"); const kfAspect = kfItem.system.aspect?.toLowerCase() ?? "metal";
const ASPECT_NORMALIZE = { eau: "water", terre: "earth", feu: "fire", bois: "wood" };
kfDefaultAspect = ASPECT_NAMES.indexOf(ASPECT_NORMALIZE[kfAspect] ?? kfAspect);
if (kfDefaultAspect < 0) kfDefaultAspect = 0; if (kfDefaultAspect < 0) kfDefaultAspect = 0;
break; break;
} }
@@ -1813,7 +1814,10 @@ async function rollForActor(actor, rollKey) {
d0: wpFaces[0] d0: wpFaces[0]
}, wpRoll, ROLL_MODES[wpThrowMode] ?? "roll"); }, wpRoll, ROLL_MODES[wpThrowMode] ?? "roll");
if (game.modules.get("dice-so-nice")?.active && wpMsg?.id) { if (game.modules.get("dice-so-nice")?.active && wpMsg?.id) {
try {
await game.dice3d.waitFor3DAnimationByMessageID(wpMsg.id); await game.dice3d.waitFor3DAnimationByMessageID(wpMsg.id);
} catch (_e) {
}
} }
if ((wpResults.loksyudice ?? 0) > 0) await updateLoksyuFromRoll(wpAspectName, wpFaces); if ((wpResults.loksyudice ?? 0) > 0) await updateLoksyuFromRoll(wpAspectName, wpFaces);
if ((wpResults.tinjidice ?? 0) > 0) await updateTinjiFromRoll(wpResults.tinjidice); if ((wpResults.tinjidice ?? 0) > 0) await updateTinjiFromRoll(wpResults.tinjidice);
@@ -1846,14 +1850,6 @@ async function rollForActor(actor, rollKey) {
if (kfDefaultAspect >= 0) { if (kfDefaultAspect >= 0) {
defaultAspect = kfDefaultAspect; 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; let params;
if (isMagic) { if (isMagic) {
params = await showMagicPrompt({ params = await showMagicPrompt({
@@ -1862,10 +1858,8 @@ async function rollForActor(actor, rollKey) {
aspectskill: defaultAspect, aspectskill: defaultAspect,
bonusmalusskill: 0, bonusmalusskill: 0,
bonusauspiciousdice: 0, bonusauspiciousdice: 0,
aspectspeciality: defaultSpecialAspect,
rolldifficulty: 1, rolldifficulty: 1,
bonusmalusspeciality: 0, freepowerlevels: 0,
heispend: 0,
typeofthrow: typeOfThrow typeofthrow: typeOfThrow
}); });
} else { } else {
@@ -1882,20 +1876,16 @@ async function rollForActor(actor, rollKey) {
} }
if (!params) return; if (!params) return;
let aspectIndex, bonusMalus, bonusAuspicious, throwMode; let aspectIndex, bonusMalus, bonusAuspicious, throwMode;
let spellAspectIndex = null;
let rollDifficulty = 1; let rollDifficulty = 1;
if (isMagic) { if (isMagic) {
const skillAspectIndex = Number(params.aspectskill ?? 0); const skillAspectIndex = Number(params.aspectskill ?? 0);
spellAspectIndex = Number(params.aspectspeciality ?? skillAspectIndex);
aspectIndex = skillAspectIndex; aspectIndex = skillAspectIndex;
bonusMalus = Number(params.bonusmalusskill ?? 0); bonusMalus = Number(params.bonusmalusskill ?? 0);
bonusAuspicious = Number(params.bonusauspiciousdice ?? 0); bonusAuspicious = Number(params.bonusauspiciousdice ?? 0);
rollDifficulty = Math.max(1, Number(params.rolldifficulty ?? 1)); rollDifficulty = Math.max(1, Number(params.rolldifficulty ?? 1));
throwMode = Number(params.typeofthrow ?? 0); throwMode = Number(params.typeofthrow ?? 0);
const aspectDice = sys.aspect?.[ASPECT_NAMES[aspectIndex]]?.value ?? 0; const aspectDice = sys.aspect?.[ASPECT_NAMES[aspectIndex]]?.value ?? 0;
const bonusSpec = Number(params.bonusmalusspeciality ?? 0); numberofdice = numberofdice + aspectDice + bonusMalus + 1;
const heiDice = Number(params.heispend ?? 0);
numberofdice = numberofdice + aspectDice + bonusMalus + 1 + bonusSpec + heiDice;
} else { } else {
aspectIndex = Number(params.aspect ?? 0); aspectIndex = Number(params.aspect ?? 0);
bonusMalus = Number(params.bonusmalus ?? 0); bonusMalus = Number(params.bonusmalus ?? 0);
@@ -1913,22 +1903,33 @@ async function rollForActor(actor, rollKey) {
const roll = new Roll(`${numberofdice}d10`); const roll = new Roll(`${numberofdice}d10`);
await roll.evaluate(); await roll.evaluate();
const rollModeKey = ROLL_MODES[throwMode] ?? "roll"; const rollModeKey = ROLL_MODES[throwMode] ?? "roll";
const wuXingAspectName = spellAspectIndex !== null ? ASPECT_NAMES[spellAspectIndex] : ASPECT_NAMES[aspectIndex]; let spellPower = null;
let spellPowerAspectName = null;
let spellPowerAspectValue = null;
if (isMagic) {
if (isMagicSpecial && specialLibel) {
const specialCfg = MAGICS?.[skillLibel]?.speciality?.[specialLibel];
const elemName = LABELELEMENT_TO_ASPECT[specialCfg?.labelelement];
if (elemName) spellPowerAspectName = elemName;
}
if (!spellPowerAspectName) spellPowerAspectName = ASPECT_NAMES[aspectIndex];
spellPowerAspectValue = sys.aspect?.[spellPowerAspectName]?.value ?? 0;
const freePowerLevels = Number(params.freepowerlevels ?? 0);
spellPower = rollDifficulty * (spellPowerAspectValue + freePowerLevels);
}
const wuXingAspectName = ASPECT_NAMES[aspectIndex];
const allResults = roll.dice[0]?.results ?? []; const allResults = roll.dice[0]?.results ?? [];
const faces = countFaces(allResults); const faces = countFaces(allResults);
const results = computeWuXingResults(faces, wuXingAspectName, bonusAuspicious); const results = computeWuXingResults(faces, wuXingAspectName, bonusAuspicious);
if (!results) return; if (!results) return;
const spellPower = isMagic ? results.successesdice * rollDifficulty : null;
const modParts = []; const modParts = [];
if (isMagic) { if (isMagic) {
const bm = Number(params.bonusmalusskill ?? 0); const bm = Number(params.bonusmalusskill ?? 0);
const bs = Number(params.bonusmalusspeciality ?? 0);
const hs = Number(params.heispend ?? 0);
const ba = Number(params.bonusauspiciousdice ?? 0); const ba = Number(params.bonusauspiciousdice ?? 0);
const fp = Number(params.freepowerlevels ?? 0);
if (bm !== 0) modParts.push(`${bm > 0 ? "+" : ""}${bm} ${game.i18n.localize("CDE.BonusMalus")}`); 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 (ba !== 0) modParts.push(`+${ba} ${game.i18n.localize("CDE.BonusAuspiciousDice")}`);
if (hs !== 0) modParts.push(`${hs} ${game.i18n.localize("CDE.HeiSpend")}`); if (fp !== 0) modParts.push(`+${fp} ${game.i18n.localize("CDE.FreePowerLevels")}`);
if (rollDifficulty !== 1) modParts.push(`\xD7${rollDifficulty} ${game.i18n.localize("CDE.RollDifficulty")}`); if (rollDifficulty !== 1) modParts.push(`\xD7${rollDifficulty} ${game.i18n.localize("CDE.RollDifficulty")}`);
} else { } else {
const bm = Number(params.bonusmalus ?? 0); const bm = Number(params.bonusmalus ?? 0);
@@ -1948,6 +1949,9 @@ async function rollForActor(actor, rollKey) {
modifiersText: modParts.length ? modParts.join(" \xB7 ") : "", modifiersText: modParts.length ? modParts.join(" \xB7 ") : "",
// Spell power (magic only) // Spell power (magic only)
spellPower, spellPower,
spellPowerAspectLabel: spellPowerAspectName ? game.i18n.localize(ASPECT_LABELS[spellPowerAspectName] ?? "") : "",
spellPowerAspectValue,
spellPowerFreeLevels: isMagic ? Number(params.freepowerlevels ?? 0) : 0,
rollDifficulty: isMagic ? rollDifficulty : null, rollDifficulty: isMagic ? rollDifficulty : null,
// Actor info // Actor info
actorName: actor.name ?? "", actorName: actor.name ?? "",
@@ -1968,7 +1972,10 @@ async function rollForActor(actor, rollKey) {
d0: faces[0] d0: faces[0]
}, roll, rollModeKey); }, roll, rollModeKey);
if (game.modules.get("dice-so-nice")?.active && msg?.id) { if (game.modules.get("dice-so-nice")?.active && msg?.id) {
try {
await game.dice3d.waitFor3DAnimationByMessageID(msg.id); await game.dice3d.waitFor3DAnimationByMessageID(msg.id);
} catch (_e) {
}
} }
if ((results.loksyudice ?? 0) > 0) await updateLoksyuFromRoll(wuXingAspectName, faces); if ((results.loksyudice ?? 0) > 0) await updateLoksyuFromRoll(wuXingAspectName, faces);
if ((results.tinjidice ?? 0) > 0) await updateTinjiFromRoll(results.tinjidice); if ((results.tinjidice ?? 0) > 0) await updateTinjiFromRoll(results.tinjidice);
@@ -2064,9 +2071,13 @@ var CDEBaseActorSheet = class _CDEBaseActorSheet extends HandlebarsApplicationMi
}; };
// src/ui/sheets/actors/character.js // src/ui/sheets/actors/character.js
var CDECharacterSheet = class extends CDEBaseActorSheet { var CDECharacterSheet = class _CDECharacterSheet extends CDEBaseActorSheet {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ["character"] classes: ["character"],
actions: {
moveMagicUp: _CDECharacterSheet.#onMoveMagicUp,
moveMagicDown: _CDECharacterSheet.#onMoveMagicDown
}
}; };
static PARTS = { static PARTS = {
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-character-sheet.html" } main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-character-sheet.html" }
@@ -2089,8 +2100,7 @@ var CDECharacterSheet = class extends CDEBaseActorSheet {
spellsByDiscipline[disc].push(spell); spellsByDiscipline[disc].push(spell);
} }
const systemMagics = context.systemData.magics ?? {}; const systemMagics = context.systemData.magics ?? {};
context.magicsDisplay = Object.fromEntries( const magicEntries = Object.entries(MAGICS).map(([magicKey, magicDef]) => {
Object.entries(MAGICS).map(([magicKey, magicDef]) => {
const magicData = systemMagics[magicKey] ?? {}; const magicData = systemMagics[magicKey] ?? {};
return [ return [
magicKey, magicKey,
@@ -2106,8 +2116,19 @@ var CDECharacterSheet = class extends CDEBaseActorSheet {
grimoire: spellsByDiscipline[magicKey] ?? [] grimoire: spellsByDiscipline[magicKey] ?? []
} }
]; ];
}) });
); const order = context.systemData.magicOrder ?? [];
if (order.length > 0) {
magicEntries.sort((a, b) => {
const ia = order.indexOf(a[0]);
const ib = order.indexOf(b[0]);
if (ia === -1 && ib === -1) return 0;
if (ia === -1) return 1;
if (ib === -1) return -1;
return ia - ib;
});
}
context.magicsDisplay = Object.fromEntries(magicEntries);
return context; return context;
} }
_onRender(context, options) { _onRender(context, options) {
@@ -2191,7 +2212,7 @@ var CDECharacterSheet = class extends CDEBaseActorSheet {
cell.addEventListener("click", (event) => { cell.addEventListener("click", (event) => {
event.preventDefault(); event.preventDefault();
const rollKey = cell.dataset.libelId; const rollKey = cell.dataset.libelId;
if (rollKey) rollForActor(this.document, rollKey); if (rollKey) rollForActor(this.document, rollKey)?.catch((err) => console.error("Roll failed:", err));
}); });
}); });
} }
@@ -2205,6 +2226,26 @@ var CDECharacterSheet = class extends CDEBaseActorSheet {
}).render(true); }).render(true);
}); });
} }
static async #onMoveMagicUp(event, target) {
const key = target.dataset.magicKey;
let order = this.document.system.magicOrder ?? [];
if (!order.length) order = [...Object.keys(MAGICS)];
else order = [...order];
const idx = order.indexOf(key);
if (idx <= 0) return;
[order[idx - 1], order[idx]] = [order[idx], order[idx - 1]];
await this.document.update({ "system.magicOrder": order });
}
static async #onMoveMagicDown(event, target) {
const key = target.dataset.magicKey;
let order = this.document.system.magicOrder ?? [];
if (!order.length) order = [...Object.keys(MAGICS)];
else order = [...order];
const idx = order.indexOf(key);
if (idx === -1 || idx >= order.length - 1) return;
[order[idx], order[idx + 1]] = [order[idx + 1], order[idx]];
await this.document.update({ "system.magicOrder": order });
}
#bindComponentRandomize() { #bindComponentRandomize() {
const btn = this.element?.querySelector("[data-action='randomize-component']"); const btn = this.element?.querySelector("[data-action='randomize-component']");
if (!btn) return; if (!btn) return;
@@ -2274,7 +2315,7 @@ var CDENpcSheet = class extends CDEBaseActorSheet {
cell.addEventListener("click", (event) => { cell.addEventListener("click", (event) => {
event.preventDefault(); event.preventDefault();
const rollKey = cell.dataset.libelId; const rollKey = cell.dataset.libelId;
if (rollKey) rollForActor(this.document, rollKey); if (rollKey) rollForActor(this.document, rollKey)?.catch((err) => console.error("Roll failed:", err));
}); });
}); });
} }
@@ -2461,11 +2502,11 @@ var CDESanheiSheet = class extends CDEBaseItemSheet {
async _prepareContext() { async _prepareContext() {
const context = await super._prepareContext(); const context = await super._prepareContext();
const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true }); const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true });
const props = this.document.system.properties; const props = this.document.system.properties ?? {};
context.prop1DescriptionHTML = await enrich(props.prop1.description); context.prop1DescriptionHTML = await enrich(props.prop1?.description);
context.prop2DescriptionHTML = await enrich(props.prop2.description); context.prop2DescriptionHTML = await enrich(props.prop2?.description);
context.prop3DescriptionHTML = await enrich(props.prop3.description); context.prop3DescriptionHTML = await enrich(props.prop3?.description);
context.propFields = this.document.system.schema.fields.properties.fields; context.propFields = this.document.system.schema.fields.properties?.fields;
return context; return context;
} }
}; };
@@ -2673,7 +2714,7 @@ var CDETinjiApp = class _CDETinjiApp extends foundry.applications.api.Handlebars
return; return;
} }
await setTinjiValue(current - 1); await setTinjiValue(current - 1);
ChatMessage.create({ await ChatMessage.create({
user: game.user.id, user: game.user.id,
content: `<div class="cde-tinji-spend-msg"> content: `<div class="cde-tinji-spend-msg">
<i class="fas fa-star"></i> <i class="fas fa-star"></i>
@@ -2692,7 +2733,7 @@ var CDECombat = class extends Combat {
* for each selected combatant, then sync the result to the Combatant document. * for each selected combatant, then sync the result to the Combatant document.
*/ */
async rollInitiative(ids, options = {}) { async rollInitiative(ids, options = {}) {
const combatantIds = typeof ids === "string" ? [ids] : ids; const combatantIds = ids ? typeof ids === "string" ? [ids] : ids : this.combatants.map((c) => c.id);
for (const id of combatantIds) { for (const id of combatantIds) {
const combatant = this.combatants.get(id); const combatant = this.combatants.get(id);
if (!combatant) continue; if (!combatant) continue;
@@ -2994,7 +3035,7 @@ async function _drawFromLoksyu(message, aspect, type, aspectLabel) {
} }
const remain = entry.yin + entry.yang; const remain = entry.yin + entry.yang;
const typeLabel = type === "success" ? game.i18n.localize("CDE.Successes") : game.i18n.localize("CDE.AuspiciousDie"); const typeLabel = type === "success" ? game.i18n.localize("CDE.Successes") : game.i18n.localize("CDE.AuspiciousDie");
ChatMessage.create({ await ChatMessage.create({
user: game.user.id, user: game.user.id,
content: `<div class="cde-loksyu-draw-msg"> content: `<div class="cde-loksyu-draw-msg">
<div class="cde-loksyu-draw-header"> <div class="cde-loksyu-draw-header">
@@ -3020,7 +3061,7 @@ async function _spendTinjiPostRoll() {
return; return;
} }
await setTinjiValue(current - 1); await setTinjiValue(current - 1);
ChatMessage.create({ await ChatMessage.create({
user: game.user.id, user: game.user.id,
content: `<div class="cde-tinji-spend-msg"> content: `<div class="cde-tinji-spend-msg">
<span class="cde-tinji-icon">\u5929</span> <span class="cde-tinji-icon">\u5929</span>
+2 -2
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -217,8 +217,11 @@
"CDE.MsgMagic2": "s'élève à ", "CDE.MsgMagic2": "s'élève à ",
"CDE.MsgMagic3": ". La puissance à invoquer est de ", "CDE.MsgMagic3": ". La puissance à invoquer est de ",
"CDE.MsgMagic4": ", si toutefois le sort est lancé avec succès.", "CDE.MsgMagic4": ", si toutefois le sort est lancé avec succès.",
"CDE.MoveUp": "Monter",
"CDE.MoveDown": "Descendre",
"CDE.NPCName": "Nom du PNJ", "CDE.NPCName": "Nom du PNJ",
"CDE.FatSi": "Fat Si", "CDE.FatSi": "Fat Si",
"CDE.FreePowerLevels": "Niveaux de puissance gratuits",
"CDE.PNJ": "PNJ", "CDE.PNJ": "PNJ",
"CDE.Name": "Nom", "CDE.Name": "Nom",
"CDE.Necromancy": "Nécromancie", "CDE.Necromancy": "Nécromancie",
+5 -2
View File
@@ -122,9 +122,8 @@ export default class CharacterDataModel extends foundry.abstract.TypeDataModel {
typeofthrow: numberField(0), typeofthrow: numberField(0),
aspectskill: numberField(0), aspectskill: numberField(0),
bonusmalusskill: numberField(0), bonusmalusskill: numberField(0),
aspectspeciality: numberField(0),
rolldifficulty: numberField(0), rolldifficulty: numberField(0),
bonusmalusspeciality: numberField(0), freepowerlevels: numberField(0),
}), }),
}), }),
aspect: new fields.SchemaField({ aspect: new fields.SchemaField({
@@ -163,6 +162,10 @@ export default class CharacterDataModel extends foundry.abstract.TypeDataModel {
nine: componentField(), nine: componentField(),
zero: componentField(), zero: componentField(),
}), }),
magicOrder: new fields.ArrayField(
new fields.StringField({ required: true, nullable: false, initial: "" }),
{ required: true, initial: [] }
),
magics: new fields.SchemaField({ magics: new fields.SchemaField({
internalcinnabar: magicField(), internalcinnabar: magicField(),
alchemy: magicField(), alchemy: magicField(),
+1 -1
View File
@@ -25,7 +25,7 @@ export default class ArmorDataModel extends foundry.abstract.TypeDataModel {
domain: stringField(""), domain: stringField(""),
obtainLevel: intField(0, { min: 0, max: 5 }), obtainLevel: intField(0, { min: 0, max: 5 }),
obtainDifficulty: intField(0, { min: 0, max: 3 }), obtainDifficulty: intField(0, { min: 0, max: 3 }),
quantity: intField(1), quantity: intField(1, { min: 0 }),
notes: htmlField(""), notes: htmlField(""),
} }
} }
+1 -1
View File
@@ -24,7 +24,7 @@ export default class IngredientDataModel extends foundry.abstract.TypeDataModel
school: stringField("all"), school: stringField("all"),
obtainLevel: intField(0, { min: 0, max: 5 }), obtainLevel: intField(0, { min: 0, max: 5 }),
obtainDifficulty: intField(0, { min: 0, max: 3 }), obtainDifficulty: intField(0, { min: 0, max: 3 }),
quantity: intField(1), quantity: intField(1, { min: 0 }),
notes: htmlField(""), notes: htmlField(""),
} }
} }
+1 -1
View File
@@ -30,7 +30,7 @@ export default class KungfuDataModel extends foundry.abstract.TypeDataModel {
reference: stringField(""), reference: stringField(""),
description: htmlField(""), description: htmlField(""),
orientation: stringField("yin"), // yin | yang | yinyang orientation: stringField("yin"), // yin | yang | yinyang
aspect: stringField("metal"), // metal | eau | terre | feu | bois aspect: stringField("metal"), // metal | water | earth | fire | wood
skill: stringField("kungfu"), // kungfu | rangedcombat skill: stringField("kungfu"), // kungfu | rangedcombat
speciality: stringField(""), speciality: stringField(""),
style: stringField(""), style: stringField(""),
+1 -1
View File
@@ -21,7 +21,7 @@ export default class SpellDataModel extends foundry.abstract.TypeDataModel {
reference: stringField(""), reference: stringField(""),
description: htmlField(""), description: htmlField(""),
specialityname: stringField(""), specialityname: stringField(""),
associatedelement: stringField("metal"), // metal | eau | terre | feu | bois associatedelement: stringField("metal"), // metal | water | earth | fire | wood
hei: stringField(""), hei: stringField(""),
realizationtimeritual: stringField(""), realizationtimeritual: stringField(""),
realizationtimeaccelerated: stringField(""), realizationtimeaccelerated: stringField(""),
+1 -1
View File
@@ -30,7 +30,7 @@ export default class WeaponDataModel extends foundry.abstract.TypeDataModel {
range: stringField("contact"), // contact | courte | mediane | longue | extreme range: stringField("contact"), // contact | courte | mediane | longue | extreme
obtainLevel: intField(0, { min: 0, max: 5 }), obtainLevel: intField(0, { min: 0, max: 5 }),
obtainDifficulty: intField(0, { min: 0, max: 3 }), obtainDifficulty: intField(0, { min: 0, max: 3 }),
quantity: intField(1), quantity: intField(1, { min: 0 }),
notes: htmlField(""), notes: htmlField(""),
} }
} }
+3 -1
View File
@@ -31,7 +31,9 @@ export class CDECombat extends Combat {
* for each selected combatant, then sync the result to the Combatant document. * for each selected combatant, then sync the result to the Combatant document.
*/ */
async rollInitiative(ids, options = {}) { async rollInitiative(ids, options = {}) {
const combatantIds = typeof ids === "string" ? [ids] : ids const combatantIds = ids
? (typeof ids === "string" ? [ids] : ids)
: this.combatants.map(c => c.id)
for (const id of combatantIds) { for (const id of combatantIds) {
const combatant = this.combatants.get(id) const combatant = this.combatants.get(id)
if (!combatant) continue if (!combatant) continue
+1 -1
View File
@@ -105,7 +105,7 @@ export class CDETinjiApp extends foundry.applications.api.HandlebarsApplicationM
return return
} }
await setTinjiValue(current - 1) await setTinjiValue(current - 1)
ChatMessage.create({ await ChatMessage.create({
user: game.user.id, user: game.user.id,
content: `<div class="cde-tinji-spend-msg"> content: `<div class="cde-tinji-spend-msg">
<i class="fas fa-star"></i> <i class="fas fa-star"></i>
+2 -2
View File
@@ -177,7 +177,7 @@ async function _drawFromLoksyu(message, aspect, type, aspectLabel) {
? game.i18n.localize("CDE.Successes") ? game.i18n.localize("CDE.Successes")
: game.i18n.localize("CDE.AuspiciousDie") : game.i18n.localize("CDE.AuspiciousDie")
ChatMessage.create({ await ChatMessage.create({
user: game.user.id, user: game.user.id,
content: `<div class="cde-loksyu-draw-msg"> content: `<div class="cde-loksyu-draw-msg">
<div class="cde-loksyu-draw-header"> <div class="cde-loksyu-draw-header">
@@ -207,7 +207,7 @@ async function _spendTinjiPostRoll() {
return return
} }
await setTinjiValue(current - 1) await setTinjiValue(current - 1)
ChatMessage.create({ await ChatMessage.create({
user: game.user.id, user: game.user.id,
content: `<div class="cde-tinji-spend-msg"> content: `<div class="cde-tinji-spend-msg">
<span class="cde-tinji-icon">天</span> <span class="cde-tinji-icon">天</span>
+38 -41
View File
@@ -168,15 +168,12 @@ async function showMagicPrompt(params) {
aspectskill: Number(params.aspectskill ?? 0), aspectskill: Number(params.aspectskill ?? 0),
bonusmalusskill: params.bonusmalusskill ?? 0, bonusmalusskill: params.bonusmalusskill ?? 0,
bonusauspiciousdice: params.bonusauspiciousdice ?? 0, bonusauspiciousdice: params.bonusauspiciousdice ?? 0,
aspectspeciality: Number(params.aspectspeciality ?? 0),
rolldifficulty: params.rolldifficulty ?? 1, rolldifficulty: params.rolldifficulty ?? 1,
bonusmalusspeciality: params.bonusmalusspeciality ?? 0, freepowerlevels: params.freepowerlevels ?? 0,
heispend: params.heispend ?? 0,
typeofthrow: Number(params.typeofthrow ?? 0), typeofthrow: Number(params.typeofthrow ?? 0),
}, },
fields: ["aspectskill", "bonusmalusskill", "bonusauspiciousdice", fields: ["aspectskill", "bonusmalusskill", "bonusauspiciousdice",
"aspectspeciality", "rolldifficulty", "bonusmalusspeciality", "rolldifficulty", "freepowerlevels", "typeofthrow"],
"heispend", "typeofthrow"],
}) })
} }
@@ -318,7 +315,9 @@ export async function rollForActor(actor, rollKey) {
const kfSkill = kfItem.system.skill ?? "kungfu" const kfSkill = kfItem.system.skill ?? "kungfu"
numberofdice = sys.skills?.[kfSkill]?.value ?? 0 numberofdice = sys.skills?.[kfSkill]?.value ?? 0
title = `${kfItem.name} [${game.i18n.localize(sys.skills?.[kfSkill]?.label ?? "CDE.KungFu")}]` title = `${kfItem.name} [${game.i18n.localize(sys.skills?.[kfSkill]?.label ?? "CDE.KungFu")}]`
kfDefaultAspect = ASPECT_NAMES.indexOf(kfItem.system.aspect ?? "metal") const kfAspect = kfItem.system.aspect?.toLowerCase() ?? "metal"
const ASPECT_NORMALIZE = { eau: "water", terre: "earth", feu: "fire", bois: "wood" }
kfDefaultAspect = ASPECT_NAMES.indexOf(ASPECT_NORMALIZE[kfAspect] ?? kfAspect)
if (kfDefaultAspect < 0) kfDefaultAspect = 0 if (kfDefaultAspect < 0) kfDefaultAspect = 0
break break
} }
@@ -427,7 +426,7 @@ export async function rollForActor(actor, rollKey) {
}, wpRoll, ROLL_MODES[wpThrowMode] ?? "roll") }, wpRoll, ROLL_MODES[wpThrowMode] ?? "roll")
if (game.modules.get("dice-so-nice")?.active && wpMsg?.id) { if (game.modules.get("dice-so-nice")?.active && wpMsg?.id) {
await game.dice3d.waitFor3DAnimationByMessageID(wpMsg.id) try { await game.dice3d.waitFor3DAnimationByMessageID(wpMsg.id) } catch (_e) { /* DSN not available */ }
} }
// Auto-update Loksyu/TinJi singletons from weapon roll faces // Auto-update Loksyu/TinJi singletons from weapon roll faces
if ((wpResults.loksyudice ?? 0) > 0) await updateLoksyuFromRoll(wpAspectName, wpFaces) if ((wpResults.loksyudice ?? 0) > 0) await updateLoksyuFromRoll(wpAspectName, wpFaces)
@@ -439,8 +438,7 @@ export async function rollForActor(actor, rollKey) {
return return
} }
// For magic rolls the prompt allows adding HEI dice, so don't block early. // For magic rolls / itemkungfu, allow 0 base dice (user can add bonus dice in the prompt).
// For itemkungfu, allow 0 base dice (user can add bonus dice in the prompt).
if (numberofdice <= 0 && typeLibel !== "aspect" && typeLibel !== "itemkungfu" && !isMagic) { if (numberofdice <= 0 && typeLibel !== "aspect" && typeLibel !== "itemkungfu" && !isMagic) {
ui.notifications.warn(game.i18n.localize("CDE.Error0")) ui.notifications.warn(game.i18n.localize("CDE.Error0"))
return return
@@ -465,16 +463,6 @@ export async function rollForActor(actor, rollKey) {
defaultAspect = kfDefaultAspect defaultAspect = kfDefaultAspect
} }
let defaultSpecialAspect = 0
if (isMagicSpecial && specialLibel) {
// Look up the speciality's element from the MAGICS config constant
const specialCfg = MAGICS?.[skillLibel]?.speciality?.[specialLibel]
const aspectName = LABELELEMENT_TO_ASPECT[specialCfg?.labelelement]
if (aspectName) {
defaultSpecialAspect = ASPECT_NAMES.indexOf(aspectName)
}
}
// ---- Show roll prompt ---- // ---- Show roll prompt ----
let params let params
@@ -485,10 +473,8 @@ export async function rollForActor(actor, rollKey) {
aspectskill: defaultAspect, aspectskill: defaultAspect,
bonusmalusskill: 0, bonusmalusskill: 0,
bonusauspiciousdice: 0, bonusauspiciousdice: 0,
aspectspeciality: defaultSpecialAspect,
rolldifficulty: 1, rolldifficulty: 1,
bonusmalusspeciality: 0, freepowerlevels: 0,
heispend: 0,
typeofthrow: typeOfThrow, typeofthrow: typeOfThrow,
}) })
} else { } else {
@@ -508,22 +494,18 @@ export async function rollForActor(actor, rollKey) {
// ---- Compute total dice and roll ---- // ---- Compute total dice and roll ----
let aspectIndex, bonusMalus, bonusAuspicious, throwMode let aspectIndex, bonusMalus, bonusAuspicious, throwMode
let spellAspectIndex = null // magic only: aspect of the speciality for Wu Xing
let rollDifficulty = 1 // magic only: multiplier applied to successes let rollDifficulty = 1 // magic only: multiplier applied to successes
if (isMagic) { if (isMagic) {
const skillAspectIndex = Number(params.aspectskill ?? 0) const skillAspectIndex = Number(params.aspectskill ?? 0)
spellAspectIndex = Number(params.aspectspeciality ?? skillAspectIndex) aspectIndex = skillAspectIndex // used for both dice pool and Wu Xing cycle
aspectIndex = skillAspectIndex // used only for skill dice pool
bonusMalus = Number(params.bonusmalusskill ?? 0) bonusMalus = Number(params.bonusmalusskill ?? 0)
bonusAuspicious = Number(params.bonusauspiciousdice ?? 0) bonusAuspicious = Number(params.bonusauspiciousdice ?? 0)
rollDifficulty = Math.max(1, Number(params.rolldifficulty ?? 1)) rollDifficulty = Math.max(1, Number(params.rolldifficulty ?? 1))
throwMode = Number(params.typeofthrow ?? 0) throwMode = Number(params.typeofthrow ?? 0)
// magic: magic skill + aspect + bonuses + 1 (speciality base) + HEI spent // magic: magic skill + aspect + bonuses + 1 (speciality base) + HEI spent
const aspectDice = sys.aspect?.[ASPECT_NAMES[aspectIndex]]?.value ?? 0 const aspectDice = sys.aspect?.[ASPECT_NAMES[aspectIndex]]?.value ?? 0
const bonusSpec = Number(params.bonusmalusspeciality ?? 0) numberofdice = numberofdice + aspectDice + bonusMalus + 1
const heiDice = Number(params.heispend ?? 0)
numberofdice = numberofdice + aspectDice + bonusMalus + 1 + bonusSpec + heiDice
} else { } else {
aspectIndex = Number(params.aspect ?? 0) aspectIndex = Number(params.aspect ?? 0)
bonusMalus = Number(params.bonusmalus ?? 0) bonusMalus = Number(params.bonusmalus ?? 0)
@@ -550,31 +532,43 @@ export async function rollForActor(actor, rollKey) {
const rollModeKey = ROLL_MODES[throwMode] ?? "roll" const rollModeKey = ROLL_MODES[throwMode] ?? "roll"
// ---- Compute spell power (magic only) ----
// Power = rollDifficulty × character aspect value for the speciality's
// associated element (or the school's aspect for base magic rolls).
let spellPower = null
let spellPowerAspectName = null
let spellPowerAspectValue = null
if (isMagic) {
if (isMagicSpecial && specialLibel) {
const specialCfg = MAGICS?.[skillLibel]?.speciality?.[specialLibel]
const elemName = LABELELEMENT_TO_ASPECT[specialCfg?.labelelement]
if (elemName) spellPowerAspectName = elemName
}
if (!spellPowerAspectName) spellPowerAspectName = ASPECT_NAMES[aspectIndex]
spellPowerAspectValue = sys.aspect?.[spellPowerAspectName]?.value ?? 0
const freePowerLevels = Number(params.freepowerlevels ?? 0)
spellPower = rollDifficulty * (spellPowerAspectValue + freePowerLevels)
}
// ---- Compute Wu Xing results ---- // ---- Compute Wu Xing results ----
// For magic rolls, the spell's aspect (aspectspeciality) governs the Wu Xing // The Wu Xing cycle always uses the roll's aspect (skill aspect for magic,
// cycle (which faces count as successes/auspicious/etc.), not the skill aspect. // skill/resource aspect otherwise) to determine which faces count as
const wuXingAspectName = spellAspectIndex !== null // successes/auspicious/etc.
? ASPECT_NAMES[spellAspectIndex] const wuXingAspectName = ASPECT_NAMES[aspectIndex]
: ASPECT_NAMES[aspectIndex]
const allResults = roll.dice[0]?.results ?? [] const allResults = roll.dice[0]?.results ?? []
const faces = countFaces(allResults) const faces = countFaces(allResults)
const results = computeWuXingResults(faces, wuXingAspectName, bonusAuspicious) const results = computeWuXingResults(faces, wuXingAspectName, bonusAuspicious)
if (!results) return if (!results) return
// For magic, successesdice × rollDifficulty = spell power
const spellPower = isMagic ? results.successesdice * rollDifficulty : null
// ---- Build modifier summary text ---- // ---- Build modifier summary text ----
const modParts = [] const modParts = []
if (isMagic) { if (isMagic) {
const bm = Number(params.bonusmalusskill ?? 0) const bm = Number(params.bonusmalusskill ?? 0)
const bs = Number(params.bonusmalusspeciality ?? 0)
const hs = Number(params.heispend ?? 0)
const ba = Number(params.bonusauspiciousdice ?? 0) const ba = Number(params.bonusauspiciousdice ?? 0)
const fp = Number(params.freepowerlevels ?? 0)
if (bm !== 0) modParts.push(`${bm > 0 ? "+" : ""}${bm} ${game.i18n.localize("CDE.BonusMalus")}`) 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 (ba !== 0) modParts.push(`+${ba} ${game.i18n.localize("CDE.BonusAuspiciousDice")}`)
if (hs !== 0) modParts.push(`${hs} ${game.i18n.localize("CDE.HeiSpend")}`) if (fp !== 0) modParts.push(`+${fp} ${game.i18n.localize("CDE.FreePowerLevels")}`)
if (rollDifficulty !== 1) modParts.push(`×${rollDifficulty} ${game.i18n.localize("CDE.RollDifficulty")}`) if (rollDifficulty !== 1) modParts.push(`×${rollDifficulty} ${game.i18n.localize("CDE.RollDifficulty")}`)
} else { } else {
const bm = Number(params.bonusmalus ?? 0) const bm = Number(params.bonusmalus ?? 0)
@@ -596,6 +590,9 @@ export async function rollForActor(actor, rollKey) {
modifiersText: modParts.length ? modParts.join(" · ") : "", modifiersText: modParts.length ? modParts.join(" · ") : "",
// Spell power (magic only) // Spell power (magic only)
spellPower, spellPower,
spellPowerAspectLabel: spellPowerAspectName ? game.i18n.localize(ASPECT_LABELS[spellPowerAspectName] ?? "") : "",
spellPowerAspectValue,
spellPowerFreeLevels: isMagic ? Number(params.freepowerlevels ?? 0) : 0,
rollDifficulty: isMagic ? rollDifficulty : null, rollDifficulty: isMagic ? rollDifficulty : null,
// Actor info // Actor info
actorName: actor.name ?? "", actorName: actor.name ?? "",
@@ -610,7 +607,7 @@ export async function rollForActor(actor, rollKey) {
// ---- Wait for Dice So Nice animation ---- // ---- Wait for Dice So Nice animation ----
if (game.modules.get("dice-so-nice")?.active && msg?.id) { if (game.modules.get("dice-so-nice")?.active && msg?.id) {
await game.dice3d.waitFor3DAnimationByMessageID(msg.id) try { await game.dice3d.waitFor3DAnimationByMessageID(msg.id) } catch (_e) { /* DSN not available */ }
} }
// ---- Auto-update Loksyu / TinJi singletons ---- // ---- Auto-update Loksyu / TinJi singletons ----
+40 -4
View File
@@ -19,6 +19,10 @@ import { CDEBaseActorSheet } from "./base.js"
export class CDECharacterSheet extends CDEBaseActorSheet { export class CDECharacterSheet extends CDEBaseActorSheet {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ["character"], classes: ["character"],
actions: {
moveMagicUp: CDECharacterSheet.#onMoveMagicUp,
moveMagicDown: CDECharacterSheet.#onMoveMagicDown,
},
} }
static PARTS = { static PARTS = {
@@ -48,8 +52,7 @@ export class CDECharacterSheet extends CDEBaseActorSheet {
// Build magicsDisplay: only include the 5 relevant specialities per magic type + grimoire // Build magicsDisplay: only include the 5 relevant specialities per magic type + grimoire
const systemMagics = context.systemData.magics ?? {} const systemMagics = context.systemData.magics ?? {}
context.magicsDisplay = Object.fromEntries( const magicEntries = Object.entries(MAGICS).map(([magicKey, magicDef]) => {
Object.entries(MAGICS).map(([magicKey, magicDef]) => {
const magicData = systemMagics[magicKey] ?? {} const magicData = systemMagics[magicKey] ?? {}
return [ return [
magicKey, magicKey,
@@ -66,7 +69,18 @@ export class CDECharacterSheet extends CDEBaseActorSheet {
}, },
] ]
}) })
) const order = context.systemData.magicOrder ?? []
if (order.length > 0) {
magicEntries.sort((a, b) => {
const ia = order.indexOf(a[0])
const ib = order.indexOf(b[0])
if (ia === -1 && ib === -1) return 0
if (ia === -1) return 1
if (ib === -1) return -1
return ia - ib
})
}
context.magicsDisplay = Object.fromEntries(magicEntries)
return context return context
} }
@@ -155,7 +169,7 @@ export class CDECharacterSheet extends CDEBaseActorSheet {
cell.addEventListener("click", (event) => { cell.addEventListener("click", (event) => {
event.preventDefault() event.preventDefault()
const rollKey = cell.dataset.libelId const rollKey = cell.dataset.libelId
if (rollKey) rollForActor(this.document, rollKey) if (rollKey) rollForActor(this.document, rollKey)?.catch(err => console.error("Roll failed:", err))
}) })
}) })
} }
@@ -171,6 +185,28 @@ export class CDECharacterSheet extends CDEBaseActorSheet {
}) })
} }
static async #onMoveMagicUp(event, target) {
const key = target.dataset.magicKey
let order = this.document.system.magicOrder ?? []
if (!order.length) order = [...Object.keys(MAGICS)]
else order = [...order]
const idx = order.indexOf(key)
if (idx <= 0) return
[order[idx - 1], order[idx]] = [order[idx], order[idx - 1]]
await this.document.update({ "system.magicOrder": order })
}
static async #onMoveMagicDown(event, target) {
const key = target.dataset.magicKey
let order = this.document.system.magicOrder ?? []
if (!order.length) order = [...Object.keys(MAGICS)]
else order = [...order]
const idx = order.indexOf(key)
if (idx === -1 || idx >= order.length - 1) return
[order[idx], order[idx + 1]] = [order[idx + 1], order[idx]]
await this.document.update({ "system.magicOrder": order })
}
#bindComponentRandomize() { #bindComponentRandomize() {
const btn = this.element?.querySelector("[data-action='randomize-component']") const btn = this.element?.querySelector("[data-action='randomize-component']")
if (!btn) return if (!btn) return
+1 -1
View File
@@ -50,7 +50,7 @@ export class CDENpcSheet extends CDEBaseActorSheet {
cell.addEventListener("click", (event) => { cell.addEventListener("click", (event) => {
event.preventDefault() event.preventDefault()
const rollKey = cell.dataset.libelId const rollKey = cell.dataset.libelId
if (rollKey) rollForActor(this.document, rollKey) if (rollKey) rollForActor(this.document, rollKey)?.catch(err => console.error("Roll failed:", err))
}) })
}) })
} }
+5 -5
View File
@@ -26,11 +26,11 @@ export class CDESanheiSheet extends CDEBaseItemSheet {
async _prepareContext() { async _prepareContext() {
const context = await super._prepareContext() const context = await super._prepareContext()
const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true }) const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true })
const props = this.document.system.properties const props = this.document.system.properties ?? {}
context.prop1DescriptionHTML = await enrich(props.prop1.description) context.prop1DescriptionHTML = await enrich(props.prop1?.description)
context.prop2DescriptionHTML = await enrich(props.prop2.description) context.prop2DescriptionHTML = await enrich(props.prop2?.description)
context.prop3DescriptionHTML = await enrich(props.prop3.description) context.prop3DescriptionHTML = await enrich(props.prop3?.description)
context.propFields = this.document.system.schema.fields.properties.fields context.propFields = this.document.system.schema.fields.properties?.fields
return context return context
} }
} }
@@ -81,6 +81,14 @@
title="{{ localize 'CDE.Roll' }} {{getMagicLabel key}}"> title="{{ localize 'CDE.Roll' }} {{getMagicLabel key}}">
<i class="fas fa-dice-d10"></i> <i class="fas fa-dice-d10"></i>
</a> </a>
<a class="cde-magic-order-btn" data-action="moveMagicUp" data-magic-key="{{key}}"
title="{{ localize 'CDE.MoveUp' }}">
<i class="fas fa-chevron-up"></i>
</a>
<a class="cde-magic-order-btn" data-action="moveMagicDown" data-magic-key="{{key}}"
title="{{ localize 'CDE.MoveDown' }}">
<i class="fas fa-chevron-down"></i>
</a>
<label class="cde-magic-toggle" title="{{ localize 'CDE.PracticeSpecialty' }}"> <label class="cde-magic-toggle" title="{{ localize 'CDE.PracticeSpecialty' }}">
<input type="checkbox" name="system.magics.{{key}}.visible" {{checked magic.visible}} /> <input type="checkbox" name="system.magics.{{key}}.visible" {{checked magic.visible}} />
<i class="fas {{#if magic.visible}}fa-chevron-up{{else}}fa-chevron-down{{/if}}"></i> <i class="fas {{#if magic.visible}}fa-chevron-up{{else}}fa-chevron-down{{/if}}"></i>
+1
View File
@@ -30,6 +30,7 @@
{{#if spellPower}} {{#if spellPower}}
<div class="cde-rr-spell-power"> <div class="cde-rr-spell-power">
<span class="cde-rr-spell-power-count">{{spellPower}}</span> <span class="cde-rr-spell-power-count">{{spellPower}}</span>
<span class="cde-rr-spell-power-formula">{{spellPowerAspectLabel}} ({{spellPowerAspectValue}}{{#if spellPowerFreeLevels}} + {{spellPowerFreeLevels}}{{/if}}) × {{rollDifficulty}}</span>
<span class="cde-rr-spell-power-label">{{ localize "CDE.SpellPower" }}</span> <span class="cde-rr-spell-power-label">{{ localize "CDE.SpellPower" }}</span>
</div> </div>
{{/if}} {{/if}}
+9 -34
View File
@@ -54,16 +54,6 @@
<div class="cde-roll-section cde-roll-section--separator"> <div class="cde-roll-section cde-roll-section--separator">
<p class="cde-roll-section-title">② {{ localize "CDE.TwoPowerOfSpell" }}</p> <p class="cde-roll-section-title">② {{ localize "CDE.TwoPowerOfSpell" }}</p>
<div class="cde-roll-fields"> <div class="cde-roll-fields">
<div class="cde-roll-field">
<label>{{ localize "CDE.AspectSpeciality" }}</label>
<select name="aspectspeciality">
<option value="0" {{#if (eq aspectspeciality 0)}}selected{{/if}}>{{ localize "CDE.Metal" }}</option>
<option value="1" {{#if (eq aspectspeciality 1)}}selected{{/if}}>{{ localize "CDE.Water" }}</option>
<option value="2" {{#if (eq aspectspeciality 2)}}selected{{/if}}>{{ localize "CDE.Earth" }}</option>
<option value="3" {{#if (eq aspectspeciality 3)}}selected{{/if}}>{{ localize "CDE.Fire" }}</option>
<option value="4" {{#if (eq aspectspeciality 4)}}selected{{/if}}>{{ localize "CDE.Wood" }}</option>
</select>
</div>
<div class="cde-roll-field"> <div class="cde-roll-field">
<label>{{ localize "CDE.RollDifficulty" }} (×)</label> <label>{{ localize "CDE.RollDifficulty" }} (×)</label>
<select name="rolldifficulty"> <select name="rolldifficulty">
@@ -75,32 +65,17 @@
</select> </select>
</div> </div>
<div class="cde-roll-field"> <div class="cde-roll-field">
<label>{{ localize "CDE.BonusMalus" }}</label> <label>{{ localize "CDE.FreePowerLevels" }}</label>
<select name="bonusmalusspeciality"> <select name="freepowerlevels">
<option value="-5" {{#if (eq bonusmalusspeciality -5)}}selected{{/if}}>5 dés</option> <option value="0" {{#if (eq freepowerlevels 0)}}selected{{/if}}>0</option>
<option value="-4" {{#if (eq bonusmalusspeciality -4)}}selected{{/if}}>4 dés</option> <option value="1" {{#if (eq freepowerlevels 1)}}selected{{/if}}>+1</option>
<option value="-3" {{#if (eq bonusmalusspeciality -3)}}selected{{/if}}>3 dés</option> <option value="2" {{#if (eq freepowerlevels 2)}}selected{{/if}}>+2</option>
<option value="-2" {{#if (eq bonusmalusspeciality -2)}}selected{{/if}}>2 dés</option> <option value="3" {{#if (eq freepowerlevels 3)}}selected{{/if}}>+3</option>
<option value="-1" {{#if (eq bonusmalusspeciality -1)}}selected{{/if}}>1 dé</option> <option value="4" {{#if (eq freepowerlevels 4)}}selected{{/if}}>+4</option>
<option value="0" {{#if (eq bonusmalusspeciality 0)}}selected{{/if}}>0 (aucun)</option> <option value="5" {{#if (eq freepowerlevels 5)}}selected{{/if}}>+5</option>
<option value="1" {{#if (eq bonusmalusspeciality 1)}}selected{{/if}}>+1 dé</option>
<option value="2" {{#if (eq bonusmalusspeciality 2)}}selected{{/if}}>+2 dés</option>
<option value="3" {{#if (eq bonusmalusspeciality 3)}}selected{{/if}}>+3 dés</option>
<option value="4" {{#if (eq bonusmalusspeciality 4)}}selected{{/if}}>+4 dés</option>
<option value="5" {{#if (eq bonusmalusspeciality 5)}}selected{{/if}}>+5 dés</option>
</select>
</div>
<div class="cde-roll-field">
<label>{{ localize "CDE.HeiSpend" }}</label>
<select name="heispend">
<option value="0" {{#if (eq heispend 0)}}selected{{/if}}>0 Hei</option>
<option value="1" {{#if (eq heispend 1)}}selected{{/if}}>1 Hei</option>
<option value="2" {{#if (eq heispend 2)}}selected{{/if}}>2 Hei</option>
<option value="3" {{#if (eq heispend 3)}}selected{{/if}}>3 Hei</option>
<option value="4" {{#if (eq heispend 4)}}selected{{/if}}>4 Hei</option>
<option value="5" {{#if (eq heispend 5)}}selected{{/if}}>5 Hei</option>
</select> </select>
</div> </div>
</div> </div>
<p class="cde-roll-hint"><i>{{ localize "CDE.DoNotModify" }}</i></p> <p class="cde-roll-hint"><i>{{ localize "CDE.DoNotModify" }}</i></p>
</div> </div>