mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-13 16:14:52 +09:00
exp_legacy recorded each player's locale in PlayerData statistics which nothing read. The lua side now sends one ipc event with the player's locale when they join and when they change language, the instance forwards it, and the controller stores it on the player record. The Scenario section of the user page shows the language by name with a flag made from regional indicator symbols. Locales with a region use it directly, the rest map to a country through a small table covering Factorio's locale list. Arabic, Catalan, Basque, and Esperanto have no single country and get no flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
4.8 KiB
TypeScript
122 lines
4.8 KiB
TypeScript
import type { InstancePluginContext } from "@clusterio/host";
|
|
import * as lib from "@clusterio/lib";
|
|
import * as messages from "./messages.js";
|
|
import { features, FeatureValue } from "./features.js";
|
|
|
|
/** Sent by the lua side when a player sets or removes their join message, see module/control/greetings.lua */
|
|
export type IpcJoinMessageSet = { player_name: string, message?: string };
|
|
/** Sent by the lua side when a player asks for their join message */
|
|
export type IpcJoinMessageGet = { player_name: string };
|
|
/** Sent by the lua side when a player joins or changes language, see module/control/player_settings.lua */
|
|
export type IpcLocale = { player_name: string, locale: string };
|
|
|
|
/** Json as a lua expression for a table, inside a long bracket the json does not contain */
|
|
export function luaJson(value: unknown) {
|
|
const json = JSON.stringify(value);
|
|
let level = "=";
|
|
while (json.includes(`]${level}]`)) {
|
|
level += "=";
|
|
}
|
|
return `helpers.json_to_table[${level}[${json}]${level}]`;
|
|
}
|
|
|
|
/** Sends feature config and player settings to the lua side, see module/features.lua. */
|
|
export default async function (context: InstancePluginContext) {
|
|
const { instance, logger, plugin } = context;
|
|
/** Rcon is up and the lua side can take updates */
|
|
let started = false;
|
|
/** Enabled state of features which need it on this side, by name */
|
|
const featureEnabled = new Map<string, boolean>();
|
|
|
|
/** Subscribe, then send every feature */
|
|
async function syncFeatures() {
|
|
await instance.sendTo("controller", new lib.SubscriptionRequest(
|
|
`exp_scenario:${messages.FeatureUpdatedEvent.name}`, "subscribe", Date.now(),
|
|
));
|
|
const records = await instance.sendTo("controller", new messages.FeatureListRequest());
|
|
await luaSendFeatures(records);
|
|
}
|
|
|
|
/** Every value of each feature is sent, so the lua side sets them all */
|
|
async function luaSendFeatures(records: messages.FeatureRecord[]) {
|
|
if (!records.length) {
|
|
return;
|
|
}
|
|
|
|
const updates = records.map(record => {
|
|
featureEnabled.set(record.id, record.enabled);
|
|
const values: Record<string, FeatureValue> = { enabled: record.enabled };
|
|
for (const field of features.find(feature => feature.name === record.id)?.fields ?? []) {
|
|
values[field.name] = field.name in record.values ? record.values[field.name] : field.default;
|
|
}
|
|
return { name: record.id, values };
|
|
});
|
|
await instance.sendRcon(`/sc exp_scenario.features.receive_update(${luaJson(updates)})`, true);
|
|
}
|
|
|
|
/** Run lua with the player as `p` if they are still in the game, the data is available as `d` */
|
|
async function luaWithPlayer(name: string, data: Record<string, unknown>, code: string) {
|
|
if (instance.status !== "running") {
|
|
return;
|
|
}
|
|
await instance.sendRcon(
|
|
`/sc local d = ${luaJson({ ...data, name })} local p = game.get_player(d.name) if p then ${code} end`, true,
|
|
);
|
|
}
|
|
|
|
async function onPlayerJoin(name: string) {
|
|
const record = await instance.sendTo("controller", new messages.PlayerGetRequest(name));
|
|
if (record.joinMessage && featureEnabled.get("greetings") !== false) {
|
|
await luaWithPlayer(name, { message: record.joinMessage }, "game.print(d.message, { color = p.color })");
|
|
}
|
|
}
|
|
|
|
instance.handle(messages.FeatureUpdatedEvent, async event => {
|
|
if (started) {
|
|
await luaSendFeatures(event.updates.filter(feature => !feature.isDeleted));
|
|
}
|
|
});
|
|
|
|
instance.server.handle("exp_scenario:join_message_set", async (event: IpcJoinMessageSet) => {
|
|
const request = new messages.JoinMessageSetRequest(event.player_name, event.message ?? null);
|
|
await instance.sendTo("controller", request);
|
|
});
|
|
|
|
instance.server.handle("exp_scenario:join_message_get", async (event: IpcJoinMessageGet) => {
|
|
const record = await instance.sendTo("controller", new messages.PlayerGetRequest(event.player_name));
|
|
await luaWithPlayer(
|
|
event.player_name,
|
|
record.joinMessage ? { message: record.joinMessage } : {},
|
|
"p.print(d.message and { \"exp_greetings.message-get\", d.message } or { \"exp_greetings.message-none\" })",
|
|
);
|
|
});
|
|
|
|
instance.server.handle("exp_scenario:locale", async (event: IpcLocale) => {
|
|
instance.sendTo("controller", new messages.PlayerLocaleEvent(event.player_name, event.locale));
|
|
});
|
|
|
|
instance.hooks.playerEvent.attach(plugin.name, async event => {
|
|
if (event.type === "join") {
|
|
await onPlayerJoin(event.name).catch(err => logger.error(
|
|
`Error showing the join message of ${event.name}:\n${err.stack ?? err.message}`,
|
|
));
|
|
}
|
|
});
|
|
|
|
instance.hooks.start.attach(plugin.name, async () => {
|
|
started = true;
|
|
await syncFeatures();
|
|
});
|
|
|
|
instance.hooks.exit.attach(plugin.name, async () => {
|
|
started = false;
|
|
});
|
|
|
|
// A new session means the controller restarted and forgot the subscription
|
|
instance.hooks.controllerConnectionEvent.attach(plugin.name, async event => {
|
|
if (event === "connect" && started) {
|
|
await syncFeatures();
|
|
}
|
|
});
|
|
}
|