Correction compendiums
This commit is contained in:
Vendored
+244
-2
@@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user