Use socket to manage loksyu

This commit is contained in:
2026-06-14 22:54:37 +02:00
parent 4cb8e26333
commit 50038a13f9
68 changed files with 464 additions and 233 deletions
+118 -20
View File
@@ -1561,6 +1561,7 @@ async function rollInitiativeNPC(actor) {
}
// src/ui/apps/singletons.js
var SOCKET_CHANNEL = `system.${SYSTEM_ID}`;
function getLoksyuData() {
return game.settings.get(SYSTEM_ID, "loksyuData") ?? {
wood: { yin: 0, yang: 0 },
@@ -1570,16 +1571,75 @@ function getLoksyuData() {
water: { yin: 0, yang: 0 }
};
}
async function setLoksyuData(data) {
async function writeLoksyuData(data) {
await game.settings.set(SYSTEM_ID, "loksyuData", data);
Hooks.callAll("cde:loksyuUpdated", data);
}
async function writeTinjiValue(value) {
value = Math.max(0, value);
await game.settings.set(SYSTEM_ID, "tinjiData", value);
Hooks.callAll("cde:tinjiUpdated", value);
}
async function setLoksyuData(data) {
if (game.user.isGM) return writeLoksyuData(data);
game.socket.emit(SOCKET_CHANNEL, { action: "setLoksyuData", data });
}
function getTinjiValue() {
return game.settings.get(SYSTEM_ID, "tinjiData") ?? 0;
}
async function setTinjiValue(value) {
await game.settings.set(SYSTEM_ID, "tinjiData", Math.max(0, value));
Hooks.callAll("cde:tinjiUpdated", Math.max(0, value));
if (game.user.isGM) return writeTinjiValue(value);
game.socket.emit(SOCKET_CHANNEL, { action: "setTinjiValue", value });
}
function requestLoksyuDraw(aspect, order) {
game.socket.emit(SOCKET_CHANNEL, { action: "loksyuDraw", aspect, order });
}
function requestTinjiSpend() {
game.socket.emit(SOCKET_CHANNEL, { action: "tinjiSpend" });
}
function registerSingletonSocket() {
game.socket.on(SOCKET_CHANNEL, async (payload) => {
if (!game.user.isGM) return;
switch (payload.action) {
case "setLoksyuData":
await writeLoksyuData(payload.data);
break;
case "setTinjiValue":
await writeTinjiValue(payload.value);
break;
case "updateLoksyuFromRoll":
await updateLoksyuFromRoll(payload.activeAspect, payload.faces);
break;
case "updateTinjiFromRoll":
await updateTinjiFromRoll(payload.delta);
break;
case "loksyuDraw": {
const data = getLoksyuData();
const entry = data[payload.aspect] ?? { yin: 0, yang: 0 };
const order = payload.order ?? "yang-first";
if (order === "yin-first") {
if (entry.yin > 0) entry.yin--;
else entry.yang--;
} else if (order === "balanced") {
if (entry.yin > entry.yang) entry.yin--;
else if (entry.yang > entry.yin) entry.yang--;
else if (entry.yang > 0) entry.yang--;
else entry.yin--;
} else {
if (entry.yang > 0) entry.yang--;
else entry.yin--;
}
data[payload.aspect] = entry;
await writeLoksyuData(data);
break;
}
case "tinjiSpend": {
const cur = getTinjiValue();
if (cur > 0) await writeTinjiValue(cur - 1);
break;
}
}
});
}
async function updateLoksyuFromRoll(activeAspect, faces) {
const cycle = WU_XING_CYCLE[activeAspect];
@@ -1590,18 +1650,23 @@ async function updateLoksyuFromRoll(activeAspect, faces) {
const yinCount = faces[yinFace] ?? 0;
const yangCount = faces[yangFace] ?? 0;
if (yinCount === 0 && yangCount === 0) return;
const data = getLoksyuData();
const current = data[lokAspect] ?? { yin: 0, yang: 0 };
data[lokAspect] = {
yin: (current.yin ?? 0) + yinCount,
yang: (current.yang ?? 0) + yangCount
};
await setLoksyuData(data);
if (game.user.isGM) {
const data = getLoksyuData();
const current = data[lokAspect] ?? { yin: 0, yang: 0 };
data[lokAspect] = { yin: (current.yin ?? 0) + yinCount, yang: (current.yang ?? 0) + yangCount };
await writeLoksyuData(data);
} else {
game.socket.emit(SOCKET_CHANNEL, { action: "updateLoksyuFromRoll", activeAspect, faces });
}
}
async function updateTinjiFromRoll(count) {
if (!count || count <= 0) return;
const current = getTinjiValue();
await setTinjiValue(current + count);
if (game.user.isGM) {
const current = getTinjiValue();
await writeTinjiValue(current + count);
} else {
game.socket.emit(SOCKET_CHANNEL, { action: "updateTinjiFromRoll", delta: count });
}
}
// src/ui/rolling.js
@@ -2685,6 +2750,8 @@ var CDELoksyuApp = class _CDELoksyuApp extends foundry.applications.api.Handleba
};
/** @type {Function|null} bound hook handler */
_updateHook = null;
/** @type {Function|null} updateSetting hook handler (for socket-propagated writes) */
_settingHook = null;
/** Singleton accessor — open or bring to front */
static open() {
const existing = Array.from(foundry.applications.instances.values()).find(
@@ -2719,13 +2786,24 @@ var CDELoksyuApp = class _CDELoksyuApp extends foundry.applications.api.Handleba
_onRender(context, options) {
super._onRender(context, options);
this.#bindInputs();
this._updateHook = Hooks.on("cde:loksyuUpdated", () => this.render());
if (!this._updateHook) {
this._updateHook = Hooks.on("cde:loksyuUpdated", () => this.render());
}
if (!this._settingHook) {
this._settingHook = Hooks.on("updateSetting", (setting) => {
if (setting.key === `${SYSTEM_ID}.loksyuData`) this.render();
});
}
}
_onClose(options) {
if (this._updateHook !== null) {
Hooks.off("cde:loksyuUpdated", this._updateHook);
this._updateHook = null;
}
if (this._settingHook !== null) {
Hooks.off("updateSetting", this._settingHook);
this._settingHook = null;
}
super._onClose(options);
}
#bindInputs() {
@@ -2794,6 +2872,8 @@ var CDETinjiApp = class _CDETinjiApp extends foundry.applications.api.Handlebars
};
/** @type {Function|null} */
_updateHook = null;
/** @type {Function|null} */
_settingHook = null;
static open() {
const existing = Array.from(foundry.applications.instances.values()).find(
(app2) => app2 instanceof _CDETinjiApp
@@ -2815,13 +2895,24 @@ var CDETinjiApp = class _CDETinjiApp extends foundry.applications.api.Handlebars
_onRender(context, options) {
super._onRender(context, options);
this.#bindDirectInput();
this._updateHook = Hooks.on("cde:tinjiUpdated", () => this.render());
if (!this._updateHook) {
this._updateHook = Hooks.on("cde:tinjiUpdated", () => this.render());
}
if (!this._settingHook) {
this._settingHook = Hooks.on("updateSetting", (setting) => {
if (setting.key === `${SYSTEM_ID}.tinjiData`) this.render();
});
}
}
_onClose(options) {
if (this._updateHook !== null) {
Hooks.off("cde:tinjiUpdated", this._updateHook);
this._updateHook = null;
}
if (this._settingHook !== null) {
Hooks.off("updateSetting", this._settingHook);
this._settingHook = null;
}
super._onClose(options);
}
#bindDirectInput() {
@@ -3078,8 +3169,7 @@ function refreshRollActions(rollCard, aspect, message) {
const tinji = getTinjiValue();
const successAvail = (loksyu[aspect]?.yin ?? 0) + (loksyu[aspect]?.yang ?? 0);
const fasteAvail = (loksyu[fasteAspect]?.yin ?? 0) + (loksyu[fasteAspect]?.yang ?? 0);
const isGM = game.user.isGM;
const hasSomething = successAvail > 0 || fasteAvail > 0 || isGM && tinji > 0;
const hasSomething = successAvail > 0 || fasteAvail > 0 || tinji > 0;
if (!hasSomething) return;
const aspLabel = game.i18n.localize(ASPECT_LABELS[aspect]);
const fasteLabel = game.i18n.localize(ASPECT_LABELS[fasteAspect]);
@@ -3098,7 +3188,7 @@ function refreshRollActions(rollCard, aspect, message) {
<span class="cde-roll-action-count">${fasteAvail}</span>
</button>`;
}
if (isGM && tinji > 0) {
if (tinji > 0) {
btns += `<button class="cde-roll-action-btn cde-roll-action--tinji" data-action="tinji">
<span class="cde-roll-action-tinji-char">\u5929</span>
<span class="cde-roll-action-label">${game.i18n.localize("CDE.TinJi2")}</span>
@@ -3151,7 +3241,11 @@ async function _drawFromLoksyu(message, aspect, type, aspectLabel) {
else entry.yin--;
}
data[aspect] = entry;
await setLoksyuData(data);
if (game.user.isGM) {
await setLoksyuData(data);
} else {
requestLoksyuDraw(aspect, order);
}
const flags = message?.flags?.[SYSTEM_ID];
if (flags?.rollResult && message.isOwner) {
const updated = foundry.utils.deepClone(flags.rollResult);
@@ -3190,13 +3284,16 @@ async function _drawFromLoksyu(message, aspect, type, aspectLabel) {
});
}
async function _spendTinjiPostRoll() {
if (!game.user.isGM) return;
const current = getTinjiValue();
if (current <= 0) {
ui.notifications.warn(game.i18n.localize("CDE.TinjiEmpty"));
return;
}
await setTinjiValue(current - 1);
if (game.user.isGM) {
await setTinjiValue(current - 1);
} else {
requestTinjiSpend();
}
await ChatMessage.create({
user: game.user.id,
content: `<div class="cde-tinji-spend-msg">
@@ -3354,6 +3451,7 @@ Hooks.once("ready", async () => {
await migrateIfNeeded();
await loadWelcomeSceneIfNeeded();
CDEWheelApp.registerHooks();
registerSingletonSocket();
if (game.user.isGM) showWelcomeMessage();
});
Hooks.on("renderChatLog", (_app, html) => {