Correction compendiums

This commit is contained in:
2026-04-27 21:30:33 +02:00
parent 1e252ff6f2
commit bc49286f91
76 changed files with 1645 additions and 73 deletions
+244 -2
View File
@@ -130,7 +130,8 @@ var TEMPLATE_PARTIALS = [
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-kungfus.html",
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-items.html",
"systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-loksyu-app.html",
"systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-tinji-app.html"
"systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-tinji-app.html",
"systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-wheel-app.html"
];
// src/config/settings.js
@@ -807,6 +808,20 @@ function registerHandlebarsHelpers() {
};
return game.i18n.localize(keys[activation] ?? "CDE.Activation");
});
Handlebars.registerHelper("cranPosition", function(cran, cx, cy, r) {
const angleDeg = 90 + cran * 15;
const angleRad = angleDeg * Math.PI / 180;
const x = Math.round(cx + r * Math.cos(angleRad));
const y = Math.round(cy - r * Math.sin(angleRad));
return { x, y };
});
Handlebars.registerHelper("fighterX", function(cx, index, total) {
const offset = total > 1 ? (index - (total - 1) / 2) * 34 : 0;
return Math.round(cx - 15 + offset);
});
Handlebars.registerHelper("fighterY", function(cy, index, total) {
return Math.round(cy - 50);
});
}
// src/ui/templates.js
@@ -2209,6 +2224,205 @@ var CDETinjiApp = class _CDETinjiApp extends foundry.applications.api.Handlebars
}
};
// src/documents/combat.js
var CDECombat = class extends Combat {
/**
* Override rollInitiative to open the PC or NPC initiative dialog
* for each selected combatant, then sync the result to the Combatant document.
*/
async rollInitiative(ids, options = {}) {
const combatantIds = typeof ids === "string" ? [ids] : ids;
for (const id of combatantIds) {
const combatant = this.combatants.get(id);
if (!combatant) continue;
const actor = combatant.actor;
if (!actor) continue;
if (actor.type === ACTOR_TYPES.character) {
await rollInitiativePC(actor);
} else {
await rollInitiativeNPC(actor);
}
}
return this;
}
/**
* Sort combatants: highest initiative first (furthest counter-clockwise = acts first).
* Ties: PCs before NPCs; among PCs, by name; among NPCs, by name.
* Calls super.setupTurns() first to ensure this.current is properly initialized.
*/
setupTurns() {
super.setupTurns();
this.turns = this.turns.slice().sort((a, b) => {
const ia = a.initiative ?? 0;
const ib = b.initiative ?? 0;
if (ia !== ib) return ib - ia;
const aIsPC = a.actor?.type === ACTOR_TYPES.character ? 1 : 0;
const bIsPC = b.actor?.type === ACTOR_TYPES.character ? 1 : 0;
if (aIsPC !== bIsPC) return bIsPC - aIsPC;
return (a.name ?? "").localeCompare(b.name ?? "");
});
return this.turns;
}
};
async function advanceCombatantPosition(combatant, cranCost) {
const current = combatant.initiative ?? combatant.actor?.system?.initiative ?? 1;
const newValue = (current - cranCost - 1 + 48) % 24 + 1;
await combatant.update({ initiative: newValue });
}
// src/ui/apps/wheel-app.js
var WHEEL_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-wheel-app.html";
var ACTION_COSTS = [
{ key: "draw", label: "CDE.ActionCostDraw", cost: 1 },
{ key: "changestyle", label: "CDE.ActionCostChangeStyle", cost: 1 },
{ key: "defense", label: "CDE.ActionCostDefense", cost: 1 },
{ key: "aim", label: "CDE.ActionCostAim", cost: 2 },
{ key: "help", label: "CDE.ActionCostHelp", cost: 2 },
{ key: "defally", label: "CDE.ActionCostDefendAlly", cost: 2 },
{ key: "move", label: "CDE.ActionCostMove", cost: 2 },
{ key: "attack", label: "CDE.ActionCostAttack", cost: 3 },
{ key: "delay", label: "CDE.ActionCostDelay", cost: 6 }
];
var WHEEL_SEGMENTS = [
{ label: "M\xE9tal", color: "#b8c4cc", textColor: "#1a1a1a", crans: [1, 2, 3, 4] },
{ label: "Eau", color: "#3a7bd5", textColor: "#ffffff", crans: [5, 6, 7, 8] },
{ label: "Terre", color: "#c8a84b", textColor: "#1a1a1a", crans: [9, 10, 11, 12] },
{ label: "Feu", color: "#d94f3d", textColor: "#ffffff", crans: [13, 14, 15, 16] },
{ label: "Bois", color: "#4a9b5a", textColor: "#ffffff", crans: [17, 18, 19, 20] },
{ label: "Rep\xE8re", color: "#1a1a2e", textColor: "#aaaaaa", crans: [21, 22, 23, 24] }
];
function segmentForCran(cran) {
return WHEEL_SEGMENTS.find((s) => s.crans.includes(cran)) ?? WHEEL_SEGMENTS[0];
}
var CDEWheelApp = class _CDEWheelApp extends foundry.applications.api.ApplicationV2 {
static DEFAULT_OPTIONS = {
id: "cde-wheel-app",
classes: ["cde-wheel-app"],
tag: "div",
window: {
title: "CDE.InitiativeWheel",
icon: "fas fa-circle-notch",
resizable: true
},
position: { width: 820, height: 620 },
actions: {
advanceCran: _CDEWheelApp.#advanceCran,
setSurprised: _CDEWheelApp.#setSurprised,
rollInitiative: _CDEWheelApp.#rollInitiative
}
};
/** @type {CDEWheelApp|null} */
static #instance = null;
/** Open (or bring to front) the singleton instance. */
static open() {
if (!_CDEWheelApp.#instance || _CDEWheelApp.#instance.rendered === false) {
_CDEWheelApp.#instance = new _CDEWheelApp();
_CDEWheelApp.#instance.render(true);
} else {
_CDEWheelApp.#instance.bringToFront();
}
return _CDEWheelApp.#instance;
}
/** Currently selected combatant id (for action panel). */
#selectedId = null;
async _prepareContext(options) {
const combat = game.combat;
const combatants = combat ? [...combat.combatants.values()] : [];
const sorted = [...combatants].sort((a, b) => (b.initiative ?? 0) - (a.initiative ?? 0));
const cranData = this.#buildCranData(combatants);
const selected = this.#selectedId ? combatants.find((c) => c.id === this.#selectedId) : null;
const actionCosts = ACTION_COSTS.map((a) => ({
...a,
label: game.i18n.localize(a.label)
}));
return {
hasCombat: !!combat,
combatants: sorted.map((c) => ({
id: c.id,
name: c.name,
img: c.token?.texture?.src ?? c.actor?.img ?? "icons/svg/mystery-man.svg",
initiative: c.initiative ?? "\u2014",
segment: segmentForCran(c.initiative ?? 1),
isActive: combat?.current?.combatantId === c.id,
isSelected: c.id === this.#selectedId,
hasInitiative: c.initiative != null
})),
cranData,
selected,
selectedName: selected?.name ?? null,
actionCosts
};
}
async _renderHTML(context, options) {
return foundry.applications.handlebars.renderTemplate(WHEEL_TEMPLATE, context);
}
_replaceHTML(result, content, options) {
content.innerHTML = result;
this.#bindEvents(content);
}
/** Build per-cran data for the SVG wheel. */
#buildCranData(combatants) {
const data = [];
for (let cran = 1; cran <= 24; cran++) {
const segment = segmentForCran(cran);
const fighters = combatants.filter((c) => Math.round(c.initiative) === cran);
data.push({ cran, segment, fighters });
}
return data;
}
/** Bind click events for combatant selection. */
#bindEvents(content) {
content.querySelectorAll("[data-select-combatant]").forEach((el) => {
el.addEventListener("click", () => {
this.#selectedId = el.dataset.selectCombatant;
this.render();
});
});
}
/** Action: advance selected combatant by given cran cost. */
static async #advanceCran(event, element) {
const app = _CDEWheelApp.#instance;
if (!app?.#selectedId) return;
const cost = parseInt(element.dataset.cost, 10);
if (!cost || isNaN(cost)) return;
const combatant = game.combat?.combatants.get(app.#selectedId);
if (!combatant) return;
await advanceCombatantPosition(combatant, cost);
}
/** Action: set selected combatant to surprised (position 1 = reference). */
static async #setSurprised(event, element) {
const app = _CDEWheelApp.#instance;
if (!app?.#selectedId) return;
const combatant = game.combat?.combatants.get(app.#selectedId);
if (!combatant) return;
await combatant.update({ initiative: 1 });
}
/** Action: open the initiative dialog for the selected combatant. */
static async #rollInitiative(event, element) {
const app = _CDEWheelApp.#instance;
if (!app?.#selectedId) return;
const combatant = game.combat?.combatants.get(app.#selectedId);
if (!combatant) return;
await game.combat.rollInitiative([app.#selectedId]);
}
/** Re-render when combat state changes. */
static registerHooks() {
const refresh = () => {
if (_CDEWheelApp.#instance?.rendered) _CDEWheelApp.#instance.render();
};
Hooks.on("updateCombat", refresh);
Hooks.on("updateCombatant", refresh);
Hooks.on("createCombatant", refresh);
Hooks.on("deleteCombatant", refresh);
Hooks.on("updateActor", (_actor, diff) => {
if (foundry.utils.hasProperty(diff, "system.initiative")) refresh();
});
Hooks.on("deleteCombat", () => {
if (_CDEWheelApp.#instance?.rendered) _CDEWheelApp.#instance.render();
});
}
};
// src/ui/roll-actions.js
var RESULT_TEMPLATE3 = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-dice-result.html";
function injectRollActions(message, html) {
@@ -2362,7 +2576,8 @@ Hooks.once("init", async () => {
console.info(`CHRONIQUESDELETRANGE | Initializing ${SYSTEM_ID}`);
registerSettings();
game.system.CONST = { MAGICS, SUBTYPES };
game.cde = { CDELoksyuApp, CDETinjiApp };
game.cde = { CDELoksyuApp, CDETinjiApp, CDEWheelApp };
CONFIG.Combat.documentClass = CDECombat;
CONFIG.Actor.dataModels = {
[ACTOR_TYPES.character]: CharacterDataModel,
[ACTOR_TYPES.npc]: NpcDataModel
@@ -2440,6 +2655,7 @@ Hooks.once("init", async () => {
});
Hooks.once("ready", async () => {
await migrateIfNeeded();
CDEWheelApp.registerHooks();
});
Hooks.on("renderChatLog", (_app, html) => {
const el = html instanceof HTMLElement ? html : html[0] ?? html;
@@ -2454,10 +2670,14 @@ Hooks.on("renderChatLog", (_app, html) => {
<button type="button" class="cde-chat-btn cde-chat-btn--tinji">
<i class="fas fa-star"></i> ${game.i18n.localize("CDE.TinJi2")}
</button>
<button type="button" class="cde-chat-btn cde-chat-btn--wheel">
<i class="fas fa-circle-notch"></i> ${game.i18n.localize("CDE.InitiativeWheel")}
</button>
`;
wrapper.addEventListener("click", (ev) => {
if (ev.target.closest(".cde-chat-btn--loksyu")) CDELoksyuApp.open();
if (ev.target.closest(".cde-chat-btn--tinji")) CDETinjiApp.open();
if (ev.target.closest(".cde-chat-btn--wheel")) CDEWheelApp.open();
});
const anchor = el.querySelector(".chat-form") ?? el.querySelector(".chat-message-form") ?? el.querySelector("form");
if (anchor) anchor.parentElement.insertBefore(wrapper, anchor);
@@ -2472,6 +2692,28 @@ Hooks.on("updateSetting", (setting) => {
refreshAllRollActions();
}
});
Hooks.on("updateActor", (actor, diff) => {
if (!foundry.utils.hasProperty(diff, "system.initiative")) return;
if (!game.combat) return;
const initiative = actor.system.initiative;
const combatant = game.combat.combatants.find((c) => c.actor?.id === actor.id);
if (combatant && combatant.initiative !== initiative) {
combatant.update({ initiative }).catch(() => {
});
}
});
Hooks.on("updateCombatant", (combatant, diff) => {
if (!("initiative" in diff)) return;
const initiative = combatant.initiative;
if (initiative == null) return;
setTimeout(() => {
const actor = combatant.actor;
if (actor && actor.system?.initiative !== initiative) {
actor.update({ "system.initiative": initiative }).catch(() => {
});
}
}, 0);
});
/**
* Chroniques de l'Étrange — Système FoundryVTT
*
+3 -3
View File
File diff suppressed because one or more lines are too long