mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 07:04:53 +09:00
Add opt-in UDP messaging between the host and the Factorio server using the --enable-lua-udp option of Factorio 2.0.59+. When factorio.enable_lua_udp is set the instance binds a localhost UDP socket, starts Factorio with --enable-lua-udp on factorio.lua_udp_port (random if null, like the RCON port) and tells the clusterio module the host port via clusterio_private.update_instance. Data from Factorio is sent with clusterio_api.send_udp(channel, data) in the same IPC format as send_json, so plugins receive it through the existing ipc-<channel> events. Packets not sent from the Lua UDP port are ignored, as that is the only unauthenticated way into the IPC handler. Data to Factorio is sent with the new sendUdp method on instances and instance plugins, and is dispatched in-game as on_udp_packet_received events by polling helpers.recv_udp every tick from the clusterio module. Plugins requiring UDP declare the new LuaUdp feature flag, which like SavePatching and ScriptCommands refuses to load the plugin on instances without the option enabled. Verified against a headless 2.1.17 server: send_udp and recv_udp are silent no-ops when the option is off, so a stale port in a save that is moved to an instance without UDP does no harm. The integration test matrix now starts the instance with UDP enabled on Factorio 2.0.59+ and round trips a player event over it. Closes #969 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
146 lines
4.0 KiB
TypeScript
146 lines
4.0 KiB
TypeScript
/**
|
|
* Plugin interfaces and utilities.
|
|
* @module lib/plugin
|
|
*/
|
|
import type { Static } from "@sinclair/typebox";
|
|
import type { Logger } from "./logging.js";
|
|
import type { ConfigSchema, FieldDefinition } from "./config/index.js";
|
|
import type { PermissionDefinition } from "./permissions.js";
|
|
import type { PlayerStats, Role } from "./data/index.js";
|
|
import type { SyncHook } from "./SyncHook.js";
|
|
|
|
export const PluginFeatureFlags = [
|
|
/** The plugin requires module code to be patched into the save */
|
|
"SavePatching",
|
|
/** The plugin requires access to script commands over rcon */
|
|
"ScriptCommands",
|
|
/** The plugin requires UDP messaging with the Factorio server */
|
|
"LuaUdp",
|
|
] as const;
|
|
|
|
/** Used to define the plugin export in plugins */
|
|
export type PluginDeclaration = {
|
|
name: string;
|
|
title: string;
|
|
description?: string;
|
|
|
|
controllerEntrypoint?: string;
|
|
controllerConfigFields?: Record<string, FieldDefinition>;
|
|
|
|
hostEntrypoint?: string;
|
|
hostConfigFields?: Record<string, FieldDefinition>;
|
|
|
|
instanceEntrypoint?: string;
|
|
instanceConfigFields?: Record<string, FieldDefinition>;
|
|
|
|
ctlEntrypoint?: string;
|
|
webEntrypoint?: string;
|
|
ctlConfigFields?: Record<string, FieldDefinition>;
|
|
|
|
migrationEntrypoint?: string;
|
|
|
|
features?: (typeof PluginFeatureFlags)[number][];
|
|
|
|
messages?: any[];
|
|
permissions?: PermissionDefinition[];
|
|
routes?: string[];
|
|
}
|
|
|
|
/**
|
|
* Check if a plugin is expected to ship a web build.
|
|
*
|
|
* Mirrors the rule used by the create tool: a web build is generated when the
|
|
* plugin has a web or controller entrypoint or defines config fields.
|
|
*/
|
|
export function pluginNeedsWebBuild(info: PluginDeclaration) {
|
|
return Boolean(
|
|
info.webEntrypoint
|
|
|| info.controllerEntrypoint
|
|
|| info.controllerConfigFields
|
|
|| info.hostConfigFields
|
|
|| info.instanceConfigFields
|
|
|| info.ctlConfigFields
|
|
);
|
|
}
|
|
|
|
export type PluginNodeEnvInfo = PluginDeclaration & {
|
|
/**
|
|
* Path to the folder with the static files that should be hosted on the web
|
|
* server in order for the web interface to be able to load the plugin.
|
|
*/
|
|
webStaticPath: string;
|
|
/**
|
|
* Absolute path to the package.json file for the plugin.
|
|
*/
|
|
packagePath: string;
|
|
requirePath: string;
|
|
version: string;
|
|
manifest: any;
|
|
/**
|
|
* NPM package this plugin is published as. Not present if the package
|
|
* is private or the path used to load it does not match the name of the
|
|
* package.
|
|
*/
|
|
npmPackage?: string;
|
|
};
|
|
|
|
export type PluginWebpackEnvInfo = PluginDeclaration & {
|
|
container?: any;
|
|
package?: any;
|
|
enabled?: boolean;
|
|
error?: string;
|
|
};
|
|
|
|
/**
|
|
* Information about the event.
|
|
*/
|
|
export interface PlayerEvent {
|
|
type: "join" | "leave" | "import" | "promote" | "demote" | "ban" | "unban" | "whitelisted" | "unwhitelisted";
|
|
/** Name of the player that caused the event */
|
|
name: string,
|
|
/**
|
|
* Only present for type "leave" and "ban". Reason for player leaving the
|
|
* game, one of the possible reasons in defines.disconnect_reason
|
|
* or "server_quit" if the server exits while the player is online.
|
|
* When type is "ban" this is the reason given for the ban.
|
|
*/
|
|
reason?: string;
|
|
/**
|
|
* Statistics recorded for this player on the instance this event
|
|
* originated from.
|
|
*/
|
|
stats: PlayerStats,
|
|
}
|
|
|
|
export type PluginLoadContext<
|
|
Context extends object,
|
|
Info extends PluginDeclaration = PluginNodeEnvInfo
|
|
> = Context & {
|
|
logger: Logger;
|
|
plugin: Info;
|
|
};
|
|
|
|
/** Hooks for migrating data that Clusterio stores on behalf of plugins */
|
|
export type MigrationHooks = {
|
|
controllerConfig: SyncHook<ConfigSchema>;
|
|
hostConfig: SyncHook<ConfigSchema>;
|
|
instanceConfig: SyncHook<ConfigSchema>;
|
|
role: SyncHook<Static<typeof Role.jsonSchema>>;
|
|
};
|
|
|
|
export type MigrationContext = PluginLoadContext<{ hooks: MigrationHooks }>;
|
|
|
|
export type PluginClass<
|
|
Context extends object,
|
|
Info extends PluginDeclaration,
|
|
> = {
|
|
new (...args: any[]): any;
|
|
fromContext(context: PluginLoadContext<Context, Info>): {
|
|
init(): Promise<void>;
|
|
detachHooks(): void;
|
|
};
|
|
};
|
|
|
|
export type PluginType =
|
|
Extract<keyof PluginDeclaration, `${string}Entrypoint`> extends `${infer P}Entrypoint` ? P : never;
|