mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 23:14: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>
130 lines
3.6 KiB
Lua
130 lines
3.6 KiB
Lua
local api = require('modules/clusterio/api')
|
|
local compat = require("modules/clusterio/compat")
|
|
|
|
--- @class (exact) EventData.on_server_startup:EventData
|
|
|
|
local function check_patch()
|
|
if compat.script_data.clusterio_patch_number ~= clusterio_patch_number then
|
|
compat.script_data.clusterio_patch_number = clusterio_patch_number
|
|
script.raise_event(api.events.on_server_startup, {
|
|
name = api.events.on_server_startup, tick = game.tick
|
|
})
|
|
end
|
|
end
|
|
|
|
local impl = {}
|
|
impl.events = {}
|
|
|
|
impl.events[defines.events.on_tick] = function()
|
|
check_patch()
|
|
-- Dispatch on_udp_packet_received for packets sent by the host
|
|
local clusterio = compat.script_data.clusterio
|
|
if clusterio and clusterio.host_udp_port then
|
|
helpers.recv_udp(0)
|
|
end
|
|
end
|
|
|
|
impl.events[api.events.on_server_startup] = function()
|
|
if not compat.script_data.clusterio then
|
|
compat.script_data.clusterio = {
|
|
instance_id = nil,
|
|
instance_name = nil,
|
|
host_udp_port = nil,
|
|
}
|
|
end
|
|
|
|
-- Replay will just desync after the save is patched
|
|
game.disable_replay()
|
|
end
|
|
|
|
impl.events[defines.events.on_player_joined_game] = function(event)
|
|
local player = game.players[event.player_index]
|
|
api.send_json("player_event", { type = "join", name = player.name })
|
|
end
|
|
|
|
local disconnect_reason_name = {}
|
|
if defines.disconnect_reason then
|
|
for name, i in pairs(defines.disconnect_reason) do
|
|
disconnect_reason_name[i] = name
|
|
end
|
|
end
|
|
|
|
impl.events[defines.events.on_player_left_game] = function(event)
|
|
local player = game.players[event.player_index]
|
|
api.send_json("player_event", {
|
|
type = "leave",
|
|
name = player.name,
|
|
reason = disconnect_reason_name[event.reason] or "quit",
|
|
})
|
|
end
|
|
|
|
impl.events[defines.events.on_player_banned] = function(event)
|
|
api.send_json("player_event", {
|
|
type = "BAN",
|
|
name = event.player_name,
|
|
reason = event.reason,
|
|
})
|
|
end
|
|
|
|
impl.events[defines.events.on_player_unbanned] = function(event)
|
|
api.send_json("player_event", { type = "UNBANNED", name = event.player_name })
|
|
end
|
|
|
|
impl.events[defines.events.on_player_promoted] = function(event)
|
|
local player = game.players[event.player_index]
|
|
api.send_json("player_event", { type = "PROMOTE", name = player.name })
|
|
end
|
|
|
|
impl.events[defines.events.on_player_demoted] = function(event)
|
|
local player = game.players[event.player_index]
|
|
api.send_json("player_event", { type = "DEMOTE", name = player.name })
|
|
end
|
|
|
|
-- Internal API
|
|
clusterio_private = {}
|
|
function clusterio_private.update_instance(new_id, new_name, host_udp_port)
|
|
check_patch()
|
|
local script_data = compat.script_data
|
|
script_data.clusterio.instance_id = new_id
|
|
script_data.clusterio.instance_name = new_name
|
|
script_data.clusterio.host_udp_port = host_udp_port
|
|
script.raise_event(api.events.on_instance_updated, {
|
|
instance_id = new_id,
|
|
instance_name = new_name,
|
|
})
|
|
end
|
|
|
|
|
|
-- This is not part of the add_remote_interface callback to ensure it is
|
|
-- available when the clusterio_lib mod is loaded. The reason this is
|
|
-- neccessary is that on_init for newly added mods happen before on_load
|
|
-- for existing mods, and the add_remote_interface callback is done in
|
|
-- on_load. See https://forums.factorio.com/viewtopic.php?f=25&t=81552 for
|
|
-- more details.
|
|
remote.add_interface('clusterio_api', {
|
|
get_events = function()
|
|
return api.events
|
|
end,
|
|
|
|
get_instance_id = function()
|
|
return compat.script_data.clusterio.instance_id
|
|
end,
|
|
|
|
get_instance_name = function()
|
|
return compat.script_data.clusterio.instance_name
|
|
end,
|
|
|
|
get_host_udp_port = function()
|
|
return compat.script_data.clusterio.host_udp_port
|
|
end,
|
|
|
|
get_file_no = function()
|
|
local script_data = compat.script_data
|
|
script_data.clusterio_file_no = (script_data.clusterio_file_no or 0) + 1
|
|
return script_data.clusterio_file_no
|
|
end,
|
|
})
|
|
|
|
|
|
return impl
|