mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Merge pull request #686 from Cooldude2606/feature/2.0-compat
Add 2.0 support for clusterio api and 1st party plugins
This commit is contained in:
22 files changed
+510
-224
No files matched your search
@@ -28,6 +28,7 @@ Many thanks to the following for contributing to this release:
|
||||
### Major Features
|
||||
|
||||
- Added support for 2.0 builtin mods during mod pack creation. [#684](https://github.com/clusterio/clusterio/pull/684).
|
||||
- Added basic 2.0 support to clusterio lua api and 1st party plugins [#685](https://github.com/clusterio/clusterio/pull/685).
|
||||
|
||||
### Features
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
local clusterio_api = require("modules/clusterio/api")
|
||||
local compat = require("modules/clusterio/compat")
|
||||
|
||||
--- Top level module table, contains event handlers and public methods
|
||||
--- @class __plugin_name__
|
||||
local MyModule = {
|
||||
events = {},
|
||||
on_nth_tick = {},
|
||||
}
|
||||
|
||||
--- global is 'synced' between players, you should use your plugin name to avoid name conflicts
|
||||
--- script_data is 'synced' between players, you should use your plugin name to avoid name conflicts
|
||||
-- The following helper function is desync safe and avoids name conflicts after being called during on_server_startup
|
||||
-- There are edge cases in the following events: pre player left, player left, console command, and config changed
|
||||
-- If you use any of these events and access globalData then make sure to call this function first
|
||||
local globalData = {}
|
||||
local function setupGlobalData()
|
||||
if global["__plugin_name__"] == nil then
|
||||
global["__plugin_name__"] = {
|
||||
local script_data = {}
|
||||
local function setup_script_data()
|
||||
local _script_data = compat.script_data()
|
||||
if _script_data["__plugin_name__"] == nil then
|
||||
_script_data["__plugin_name__"] = {
|
||||
-- starting values go here
|
||||
}
|
||||
end
|
||||
@@ -23,7 +24,8 @@ end
|
||||
--- The on_load function is called independently for each client when they first load the map
|
||||
-- It should be used to restore global aliases and metatables not registered with script.register_metatable
|
||||
function MyModule.on_load()
|
||||
globalData = global["__plugin_name__"]
|
||||
local _script_data = compat.script_data()
|
||||
script_data = _script_data["__plugin_name__"]
|
||||
end
|
||||
|
||||
--- Public methods should be available though your top level module table
|
||||
@@ -37,26 +39,43 @@ local function bar()
|
||||
end
|
||||
|
||||
--- Clusterio provides a few custom events, on_server_startup is the most useful and should be used in place of on_init
|
||||
MyModule.events[clusterio_api.events.on_server_startup] = function(event)
|
||||
setupGlobalData()
|
||||
game.print(game.table_to_json(event))
|
||||
--- @param event EventData.on_server_startup
|
||||
local function on_server_startup(event)
|
||||
setup_script_data()
|
||||
game.print(compat.table_to_json(event))
|
||||
end
|
||||
|
||||
--- Factorio events are accessible through defines.events, you can have one handler per event per module
|
||||
MyModule.events[defines.events.on_player_crafted_item] = function(event)
|
||||
game.print(game.table_to_json(event))
|
||||
--- Triggered every time a player crafts an item, see below for how handlers are registered
|
||||
--- @param event EventData.on_player_crafted_item
|
||||
local function on_player_crafted_item(event)
|
||||
game.print(compat.table_to_json(event))
|
||||
//%if instance
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
clusterio_api.send_json("__plugin_name__-plugin_example_ipc", {
|
||||
tick = game.tick, player_name = game.get_player(event.player_index).name
|
||||
tick = game.tick, player_name = player.name
|
||||
})
|
||||
//%endif
|
||||
end
|
||||
|
||||
--- Nth tick is a special case that requires its own table, the index represents the time period between calls in ticks
|
||||
MyModule.on_nth_tick[300] = function()
|
||||
--- Run bar every 5 seconds, see below for how handlers are registered
|
||||
local function on_nth_tick_300()
|
||||
game.print(game.tick)
|
||||
bar()
|
||||
end
|
||||
|
||||
--- Factorio events are accessible through defines.events, you can have one handler per event per module
|
||||
local e = defines.events
|
||||
local events = {
|
||||
[clusterio_api.events.on_server_startup] = on_server_startup,
|
||||
[e.on_player_crafted_item] = on_player_crafted_item,
|
||||
}
|
||||
|
||||
--- Nth tick is a special case that requires its own table, the index represents the time period between calls in ticks
|
||||
local on_nth_tick = {
|
||||
[300] = on_nth_tick_300,
|
||||
}
|
||||
|
||||
--- Always return the top level module table from control, this is how clusterio will access your event handlers
|
||||
MyModule.events = events --- @package
|
||||
MyModule.on_nth_tick = on_nth_tick --- @package
|
||||
return MyModule
|
||||
@@ -1,3 +1,4 @@
|
||||
--- Can contain anything you want to allow other plugins to have access to, this example exposes the control file
|
||||
--- If you only export a single file then it may be better to rename that file to module_exports and save the redirection
|
||||
-- Access the exports from other modules using require("modules/__plugin_name__")
|
||||
return require("modules/__plugin_name__/control")
|
||||
@@ -1,4 +1,5 @@
|
||||
-- Important: Keep this API in sync with modules/clusterio/api.lua
|
||||
local compat = require("compat")
|
||||
local api = {}
|
||||
|
||||
|
||||
@@ -48,7 +49,7 @@ function api.send_json(channel, data)
|
||||
return "\\x" .. string.format("%02x", match:byte())
|
||||
end)
|
||||
|
||||
data = game.table_to_json(data)
|
||||
data = compat.table_to_json(data)
|
||||
|
||||
-- If there's more than about 4kB of data users running with the Windows
|
||||
-- console open will start to experience stuttering, otherwise we could
|
||||
@@ -58,7 +59,7 @@ function api.send_json(channel, data)
|
||||
else
|
||||
local file_no = remote.call("clusterio_api", "get_file_no")
|
||||
local file_name = "clst_" .. file_no .. ".json"
|
||||
game.write_file(file_name, data, false, 0)
|
||||
compat.write_file(file_name, data, false, 0)
|
||||
print("\f$ipc:" .. channel .. "?f" .. file_name)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
{
|
||||
"version": "0.1.2",
|
||||
"factorio_version": "1.1"
|
||||
},
|
||||
{
|
||||
"version": "0.1.3",
|
||||
"factorio_version": "2.0"
|
||||
}
|
||||
],
|
||||
"title": "Clusterio Library (Alpha)",
|
||||
@@ -19,6 +23,7 @@
|
||||
"homepage": "https://github.com/clusterio/clusterio",
|
||||
"description": "Library for interfacing with Clusterio. Warning: work in progress alpha.",
|
||||
"additional_files": {
|
||||
"compat.lua": ["..", "..", "modules", "clusterio", "compat.lua"],
|
||||
"serialize.lua": ["..", "..", "modules", "clusterio", "serialize.lua"]
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
local function send_json(channel, data)
|
||||
data = game.table_to_json(data)
|
||||
data = helpers and helpers.table_to_json(data) or game.table_to_json(data)
|
||||
print("\f$ipc:" .. channel .. "?j" .. data)
|
||||
end
|
||||
|
||||
script.on_init(function()
|
||||
send_json("mod_list", game.active_mods)
|
||||
for name, mod_setting in pairs(game.mod_setting_prototypes) do
|
||||
send_json("mod_list", script.active_mods)
|
||||
local mod_settings = prototypes and prototypes.mod_setting or game.mod_setting_prototypes
|
||||
for name, mod_setting in pairs(mod_settings) do
|
||||
send_json("mod_setting_mod", { name = name, mod = mod_setting.mod })
|
||||
end
|
||||
end)
|
||||
@@ -1,6 +1,8 @@
|
||||
-- Important: Keep this API in sync with mod/api.lua
|
||||
local compat = require("modules/clusterio/compat")
|
||||
local api = {}
|
||||
|
||||
clusterio_patch_number = clusterio_patch_number or 0 --- @type number
|
||||
|
||||
api.events = {
|
||||
on_instance_updated = script.generate_event_name(),
|
||||
@@ -8,11 +10,11 @@ api.events = {
|
||||
}
|
||||
|
||||
function api.get_instance_name()
|
||||
return global.clusterio.instance_name
|
||||
return compat.script_data().clusterio.instance_name
|
||||
end
|
||||
|
||||
function api.get_instance_id()
|
||||
return global.clusterio.instance_id
|
||||
return compat.script_data().clusterio.instance_id
|
||||
end
|
||||
|
||||
function api.send_json(channel, data)
|
||||
@@ -23,7 +25,7 @@ function api.send_json(channel, data)
|
||||
return "\\x" .. string.format("%02x", match:byte())
|
||||
end)
|
||||
|
||||
data = game.table_to_json(data)
|
||||
data = compat.table_to_json(data)
|
||||
|
||||
-- If there's more than about 4kB of data users running with the Windows
|
||||
-- console open will start to experience stuttering, otherwise we could
|
||||
@@ -31,9 +33,10 @@ function api.send_json(channel, data)
|
||||
if #data < 4000 then
|
||||
print("\f$ipc:" .. channel .. "?j" .. data)
|
||||
else
|
||||
global.clusterio_file_no = (global.clusterio_file_no or 0) + 1
|
||||
local file_name = "clst_" .. global.clusterio_file_no .. ".json"
|
||||
game.write_file(file_name, data, false, 0)
|
||||
local script_data = compat.script_data()
|
||||
script_data.clusterio_file_no = (script_data.clusterio_file_no or 0) + 1
|
||||
local file_name = "clst_" .. script_data.clusterio_file_no .. ".json"
|
||||
compat.write_file(file_name, data, false, 0)
|
||||
print("\f$ipc:" .. channel .. "?f" .. file_name)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
--[[
|
||||
Adds various functions used to ensure compatibly between version including some polyfills
|
||||
]]
|
||||
|
||||
--- @diagnostic disable: deprecated
|
||||
|
||||
--- @class LibCompat
|
||||
local compat = {}
|
||||
|
||||
--- @alias VersionTable { [1]: number, [2]: number, [3]: number }
|
||||
|
||||
--- Concert a version string into a version table
|
||||
--- @param version string
|
||||
--- @return VersionTable
|
||||
local function version_to_table(version)
|
||||
local index = 0
|
||||
local rtn = { 0, 0, 0 }
|
||||
for part in version:gmatch("%d+") do
|
||||
index = index + 1
|
||||
rtn[index] = tonumber(part)
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
--- The current version of factorio, the earliest supported is 0.17.69
|
||||
local current_version_str = "0.17.69"
|
||||
local current_version = version_to_table(current_version_str)
|
||||
do
|
||||
local success, base_version = pcall(function()
|
||||
return script.active_mods.base
|
||||
end)
|
||||
if success then
|
||||
current_version_str = base_version
|
||||
current_version = version_to_table(current_version_str)
|
||||
end
|
||||
end
|
||||
|
||||
--- Raise an error because the current version is unsupported
|
||||
--- @return any
|
||||
local function unsupported_version()
|
||||
error("Unsupported version: " .. current_version_str, 3)
|
||||
end
|
||||
|
||||
--- Returns true if the current version is equal to the provided version
|
||||
--- @param version string Version to compare against
|
||||
--- @return boolean # True if versions are equal
|
||||
function compat.version_eq(version)
|
||||
local version_tbl = version_to_table(version)
|
||||
for i = 1, 3 do
|
||||
if version_tbl[i] ~= current_version[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Returns true if the current version is greater than or equal to the the provided version
|
||||
--- @param version string Version to compare against
|
||||
--- @return boolean # True if the version is greater or equal to provided
|
||||
function compat.version_ge(version)
|
||||
local version_tbl = version_to_table(version)
|
||||
for i = 1, 3 do
|
||||
if version_tbl[i] > current_version[i] then
|
||||
return false
|
||||
elseif version_tbl[i] < current_version[i] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Returns true if the current version is less than or equal to the the provided version
|
||||
--- @param version string Version to compare against
|
||||
--- @return boolean # True if the version is less or equal to provided
|
||||
function compat.version_le(version)
|
||||
local version_tbl = version_to_table(version)
|
||||
for i = 1, 3 do
|
||||
if version_tbl[i] < current_version[i] then
|
||||
return false
|
||||
elseif version_tbl[i] > current_version[i] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- The major versions of factorio we support
|
||||
local major_v2 = compat.version_ge("2.0.0")
|
||||
local major_v1_1 = not major_v2 and compat.version_ge("1.1.0")
|
||||
-- local major_v1 = not major_v1_1 and compat.version_ge("1.0.0")
|
||||
|
||||
--- Returns the table reference used to store script data
|
||||
--- @return table script_data
|
||||
function compat.script_data()
|
||||
if major_v1_1 then
|
||||
--- @diagnostic disable-next-line
|
||||
return global
|
||||
elseif major_v2 then
|
||||
return storage
|
||||
end
|
||||
return unsupported_version()
|
||||
end
|
||||
|
||||
--- Returns the table reference used to store prototypes
|
||||
--- @param category string Category of prototype to access, eg "item", "fluid", "recipe"
|
||||
--- @return table # Table of prototype data
|
||||
function compat.prototype_data(category)
|
||||
if major_v1_1 then
|
||||
return game[category .. "_prototypes"]
|
||||
elseif major_v2 then
|
||||
return prototypes[category]
|
||||
end
|
||||
return unsupported_version()
|
||||
end
|
||||
|
||||
--- Returns the table reference used to list active mods
|
||||
--- @return table # Table of active mods
|
||||
function compat.active_mods()
|
||||
if major_v1_1 or major_v2 then
|
||||
return script.active_mods
|
||||
end
|
||||
return unsupported_version()
|
||||
end
|
||||
|
||||
--- @param tbl table
|
||||
--- @return string
|
||||
function compat.table_to_json(tbl)
|
||||
return unsupported_version()
|
||||
end
|
||||
|
||||
--- @param json string
|
||||
--- @return table
|
||||
function compat.json_to_table(json)
|
||||
return unsupported_version()
|
||||
end
|
||||
|
||||
--- @param filename string
|
||||
--- @param data LocalisedString
|
||||
--- @param append boolean?
|
||||
--- @param for_player uint?
|
||||
function compat.write_file(filename, data, append, for_player)
|
||||
unsupported_version()
|
||||
end
|
||||
|
||||
--- Select the appropriate polyfill implementation
|
||||
if major_v1_1 then
|
||||
-- Game is not always available so it needs to be called within another function
|
||||
-- Can not use ... here because of luals type suggestion breaking when it is used
|
||||
-- TODO maybe include a version that does not require game?
|
||||
|
||||
compat.table_to_json = function(tbl) return game.table_to_json(tbl) end
|
||||
compat.json_to_table = function(json) return game.json_to_table(json) end
|
||||
compat.write_file = function(filename, data, append, for_player) return game.write_file(filename, data, append, for_player) end
|
||||
elseif major_v2 then
|
||||
compat.table_to_json = helpers.table_to_json
|
||||
compat.json_to_table = helpers.json_to_table
|
||||
compat.write_file = helpers.write_file
|
||||
end
|
||||
|
||||
return compat
|
||||
@@ -1,10 +1,15 @@
|
||||
local api = require('modules/clusterio/api')
|
||||
local compat = require("modules/clusterio/compat")
|
||||
|
||||
--- @class (exact) EventData.on_server_startup:EventData
|
||||
|
||||
local function check_patch()
|
||||
if global.clusterio_patch_number ~= clusterio_patch_number then
|
||||
global.clusterio_patch_number = clusterio_patch_number
|
||||
script.raise_event(api.events.on_server_startup, {})
|
||||
local script_data = compat.script_data()
|
||||
if script_data.clusterio_patch_number ~= clusterio_patch_number then
|
||||
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
|
||||
|
||||
@@ -14,8 +19,9 @@ impl.events = {}
|
||||
impl.events[defines.events.on_tick] = check_patch
|
||||
|
||||
impl.events[api.events.on_server_startup] = function()
|
||||
if not global.clusterio then
|
||||
global.clusterio = {
|
||||
local script_data = compat.script_data()
|
||||
if not script_data.clusterio then
|
||||
script_data.clusterio = {
|
||||
instance_id = nil,
|
||||
instance_name = nil,
|
||||
}
|
||||
@@ -49,9 +55,10 @@ end
|
||||
-- Internal API
|
||||
clusterio_private = {}
|
||||
function clusterio_private.update_instance(new_id, new_name)
|
||||
local script_data = compat.script_data()
|
||||
check_patch()
|
||||
global.clusterio.instance_id = new_id
|
||||
global.clusterio.instance_name = new_name
|
||||
script_data.clusterio.instance_id = new_id
|
||||
script_data.clusterio.instance_name = new_name
|
||||
script.raise_event(api.events.on_instance_updated, {
|
||||
instance_id = new_id,
|
||||
instance_name = new_name,
|
||||
@@ -71,16 +78,17 @@ remote.add_interface('clusterio_api', {
|
||||
end,
|
||||
|
||||
get_instance_id = function()
|
||||
return global.clusterio.instance_id
|
||||
return compat.script_data().clusterio.instance_id
|
||||
end,
|
||||
|
||||
get_instance_name = function()
|
||||
return global.clusterio.instance_name
|
||||
return compat.script_data().clusterio.instance_name
|
||||
end,
|
||||
|
||||
get_file_no = function()
|
||||
global.clusterio_file_no = (global.clusterio_file_no or 0) + 1
|
||||
return global.clusterio_file_no
|
||||
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,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,44 +1,22 @@
|
||||
-- Library for serializing items in Factorio
|
||||
-- Based on code from playerManager and trainTeleports
|
||||
local compat = require("modules/clusterio/compat")
|
||||
local serialize = {}
|
||||
|
||||
local function version_to_table(version)
|
||||
local t = {}
|
||||
for p in string.gmatch(version, "%d+") do
|
||||
t[#t + 1] = tonumber(p)
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- 0.17 compatibility
|
||||
local supports_bar, get_bar, set_bar, version
|
||||
if (pcall(function() local mods = script.active_mods end)) then
|
||||
local supports_bar, get_bar, set_bar
|
||||
if compat.version_ge("1.0.0") then
|
||||
supports_bar = "supports_bar"
|
||||
get_bar = "get_bar"
|
||||
set_bar = "set_bar"
|
||||
version = version_to_table(script.active_mods.base)
|
||||
else
|
||||
supports_bar = "hasbar"
|
||||
get_bar = "getbar"
|
||||
set_bar = "setbar"
|
||||
version = version_to_table("0.17.69")
|
||||
end
|
||||
|
||||
-- returns true if the game version is greater than or equal to the given version
|
||||
local function version_ge(comp)
|
||||
comp = version_to_table(comp)
|
||||
for i=1, 3 do
|
||||
if comp[i] > version[i] then
|
||||
return false
|
||||
elseif comp[i] < version[i] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local has_create_grid = version_ge("1.1.7")
|
||||
|
||||
local has_create_grid = compat.version_ge("1.1.7")
|
||||
local has_quality = compat.version_ge("2.0.0")
|
||||
|
||||
-- Equipment Grids are serialized into an array of equipment entries
|
||||
-- where ench entry is a table with the following fields:
|
||||
@@ -109,6 +87,7 @@ end
|
||||
-- Item stacks are serialized into a table with the following fields:
|
||||
-- n: name
|
||||
-- c: count
|
||||
-- q: quality (optional)
|
||||
-- h: health (optional)
|
||||
-- d: durability (optional)
|
||||
-- a: ammo count (optional)
|
||||
@@ -141,8 +120,9 @@ function serialize.serialize_item_stack(slot, entry)
|
||||
|
||||
entry.n = slot.name
|
||||
entry.c = slot.count
|
||||
if has_quality and slot.quality.level ~= 0 then entry.q = slot.quality.name end
|
||||
if slot.health < 1 then entry.h = slot.health end
|
||||
if slot.durability then entry.d = slot.durability end
|
||||
if slot.type == "tool" and slot.durability then entry.d = slot.durability end
|
||||
if slot.type == "ammo" then entry.a = slot.ammo end
|
||||
if slot.is_item_with_label then
|
||||
local label = {}
|
||||
@@ -178,6 +158,7 @@ function serialize.deserialize_item_stack(slot, entry)
|
||||
name = entry.n,
|
||||
count = entry.c,
|
||||
}
|
||||
if entry.q then item_stack.quality = entry.q end
|
||||
if entry.h then item_stack.health = entry.h end
|
||||
if entry.d then item_stack.durability = entry.d end
|
||||
if entry.a then item_stack.ammo = entry.a end
|
||||
@@ -244,7 +225,7 @@ function serialize.serialize_inventory(inventory)
|
||||
end
|
||||
|
||||
if item.n or item.f or item.e then
|
||||
local item_serialized = game.table_to_json(item)
|
||||
local item_serialized = compat.table_to_json(item)
|
||||
if item_serialized == previous_serialized then
|
||||
local previous_item = serialized.i[#serialized.i]
|
||||
previous_item.r = (previous_item.r or 0) + 1
|
||||
@@ -305,5 +286,4 @@ function serialize.deserialize_inventory(inventory, serialized)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return serialize
|
||||
@@ -346,9 +346,8 @@ function reorderDependencies(modules: SaveModule[]) {
|
||||
const knownScenarios: Record<string, lib.ModuleInfo> = {
|
||||
// First seen in 0.17.63
|
||||
"4e866186ebe297f1038fd325b09df1a1f5e2fdd1": new lib.ModuleInfo("freeplay", "0.17.63", [], ["scenario"]),
|
||||
// First seen in 2.0 TODO Uncomment once clusterio lib supports 2.0
|
||||
// The rest of the code has already been updated to support the new control.lua file for 2.0
|
||||
// "bcbdde18ce4ec16ebfd93bd694cd9b12ef969c9a": new lib.ModuleInfo("freeplay", "2.0.0", [], ["scenario"]),
|
||||
// First seen in 2.0
|
||||
"bcbdde18ce4ec16ebfd93bd694cd9b12ef969c9a": new lib.ModuleInfo("freeplay", "2.0.0", [], ["scenario"]),
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
return {
|
||||
local compat = require("modules/clusterio/compat")
|
||||
|
||||
local keys = {
|
||||
"character_crafting_speed_modifier",
|
||||
"character_mining_speed_modifier",
|
||||
"character_additional_mining_categories",
|
||||
@@ -13,5 +15,10 @@ return {
|
||||
"character_trash_slot_count_bonus",
|
||||
"character_maximum_following_robot_count_bonus",
|
||||
"character_health_bonus",
|
||||
"character_personal_logistic_requests_enabled",
|
||||
}
|
||||
|
||||
if compat.version_le("1.1.110") then
|
||||
keys[#keys + 1] = "character_personal_logistic_requests_enabled"
|
||||
end
|
||||
|
||||
return keys
|
||||
@@ -1,6 +1,7 @@
|
||||
local progress_dialog = require("modules/inventory_sync/gui/progress_dialog")
|
||||
local ensure_character = require("modules/inventory_sync/ensure_character")
|
||||
local serialize = require("modules/inventory_sync/serialize")
|
||||
local restore_position = require("modules/inventory_sync/restore_position")
|
||||
local get_script_data = require("modules/inventory_sync/get_script_data")
|
||||
|
||||
|
||||
local function download_inventory(player_name, data, number, total)
|
||||
@@ -10,13 +11,14 @@ local function download_inventory(player_name, data, number, total)
|
||||
return
|
||||
end
|
||||
|
||||
local record = global.inventory_sync.active_downloads[player_name]
|
||||
local script_data = get_script_data()
|
||||
local record = script_data.active_downloads[player_name]
|
||||
if record == nil then
|
||||
rcon.print("No active download is in progress for " .. player_name)
|
||||
return
|
||||
end
|
||||
|
||||
local player_record = global.inventory_sync.players[player_name]
|
||||
local player_record = script_data.players[player_name]
|
||||
if record.restart then
|
||||
rcon.print("Restarting outdated download")
|
||||
inventory_sync.initiate_inventory_download(player, player_record, record.generation)
|
||||
@@ -36,7 +38,7 @@ local function download_inventory(player_name, data, number, total)
|
||||
-- Restore player position and driving state
|
||||
restore_position(record, player)
|
||||
|
||||
global.inventory_sync.active_downloads[player_name] = nil
|
||||
script_data.active_downloads[player_name] = nil
|
||||
player_record.dirty = player.connected
|
||||
player_record.sync = true
|
||||
player_record.generation = record.generation
|
||||
@@ -60,8 +62,8 @@ local function download_inventory(player_name, data, number, total)
|
||||
progress_dialog.remove(player)
|
||||
|
||||
-- Remove download session and store finished download
|
||||
global.inventory_sync.active_downloads[player_name] = nil
|
||||
global.inventory_sync.finished_downloads[player_name] = record
|
||||
script_data.active_downloads[player_name] = nil
|
||||
script_data.finished_downloads[player_name] = record
|
||||
|
||||
local ticks = game.ticks_played - record.started
|
||||
player.print("Imported player data in " .. ticks .. " ticks")
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
local compat = require("modules/clusterio/compat")
|
||||
|
||||
--- @param no_early_return boolean? True to always setup data
|
||||
return function(no_early_return)
|
||||
local script_data = compat.script_data()
|
||||
local inventory_sync = script_data.inventory_sync
|
||||
if inventory_sync and not no_early_return then
|
||||
return inventory_sync
|
||||
end
|
||||
|
||||
inventory_sync = inventory_sync or {}
|
||||
inventory_sync.players = inventory_sync.players or {}
|
||||
inventory_sync.players_waiting_for_acquire = inventory_sync.players_waiting_for_acquire or {}
|
||||
inventory_sync.players_in_cutscene_to_sync = inventory_sync.players_in_cutscene_to_sync or {}
|
||||
|
||||
inventory_sync.active_downloads = inventory_sync.active_downloads or {}
|
||||
inventory_sync.finished_downloads = inventory_sync.finished_downloads or {}
|
||||
inventory_sync.active_uploads = inventory_sync.active_uploads or {}
|
||||
|
||||
script_data.inventory_sync = inventory_sync
|
||||
return inventory_sync
|
||||
end
|
||||
@@ -1,7 +1,10 @@
|
||||
local ensure_character = require("modules/inventory_sync/ensure_character")
|
||||
local get_script_data = require("modules/inventory_sync/get_script_data")
|
||||
|
||||
local dialog_failed_download = {}
|
||||
|
||||
--- @param player LuaPlayer
|
||||
--- @param acquire_response table
|
||||
function dialog_failed_download.create(player, acquire_response)
|
||||
if player.gui.screen.dialog_failed_download then
|
||||
player.gui.screen.dialog_failed_download.destroy()
|
||||
@@ -34,7 +37,7 @@ function dialog_failed_download.create(player, acquire_response)
|
||||
type = "empty-widget",
|
||||
style = "draggable_space_header",
|
||||
}
|
||||
filler.style.horizontally_stretchable = "on"
|
||||
filler.style.horizontally_stretchable = true
|
||||
filler.style.right_margin = 4
|
||||
filler.style.height = 24
|
||||
filler.drag_target = frame
|
||||
@@ -98,7 +101,7 @@ function dialog_failed_download.create(player, acquire_response)
|
||||
type = "empty-widget",
|
||||
style = "draggable_space_header",
|
||||
}
|
||||
dialog_filler.style.horizontally_stretchable = "on"
|
||||
dialog_filler.style.horizontally_stretchable = true
|
||||
dialog_filler.style.right_margin = 4
|
||||
dialog_filler.style.height = 28
|
||||
dialog_filler.drag_target = frame
|
||||
@@ -121,23 +124,24 @@ dialog_failed_download.events = {
|
||||
end
|
||||
|
||||
if event.element.name == "inventory_sync_failed_download_abort" then
|
||||
-- Give the player a charatcer and let them play with that
|
||||
local player = game.get_player(event.player_index)
|
||||
-- Give the player a character and let them play with that
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
ensure_character(player)
|
||||
local inventory_sync = get_script_data()
|
||||
player.gui.screen.dialog_failed_download.destroy()
|
||||
global.inventory_sync.players[player.name].dirty = true
|
||||
global.inventory_sync.players[player.name].sync = false
|
||||
inventory_sync.players[player.name].dirty = true
|
||||
inventory_sync.players[player.name].sync = false
|
||||
|
||||
elseif event.element.name == "inventory_sync_failed_download_retry" then
|
||||
-- Retry acquiring the player from the controller
|
||||
local player = game.get_player(event.player_index)
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
inventory_sync.acquire(player)
|
||||
player.gui.screen.dialog_failed_download.destroy()
|
||||
end
|
||||
end,
|
||||
|
||||
[defines.events.on_player_left_game] = function(event)
|
||||
local player = game.get_player(event.player_index)
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
if player.gui.screen.dialog_failed_download then
|
||||
player.gui.screen.dialog_failed_download.destroy()
|
||||
end
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
-- Show inventory download progressbar
|
||||
local progress_dialog = {}
|
||||
|
||||
--- @param player LuaPlayer
|
||||
--- @param progress number
|
||||
--- @param total number
|
||||
function progress_dialog.create(player, progress, total)
|
||||
local frame = player.gui.screen.add {
|
||||
name = "inventory_sync_progress",
|
||||
@@ -24,7 +27,7 @@ function progress_dialog.create(player, progress, total)
|
||||
type = "empty-widget",
|
||||
style = "draggable_space_header",
|
||||
}
|
||||
filler.style.horizontally_stretchable = "on"
|
||||
filler.style.horizontally_stretchable = true
|
||||
filler.style.right_margin = 4
|
||||
filler.style.height = 24
|
||||
filler.drag_target = frame
|
||||
@@ -54,12 +57,16 @@ function progress_dialog.create(player, progress, total)
|
||||
player.opened = frame
|
||||
end
|
||||
|
||||
--- @param player LuaPlayer
|
||||
function progress_dialog.remove(player)
|
||||
if player.gui.screen.inventory_sync_progress then
|
||||
player.gui.screen.inventory_sync_progress.destroy()
|
||||
end
|
||||
end
|
||||
|
||||
--- @param player LuaPlayer
|
||||
--- @param progress number
|
||||
--- @param total number
|
||||
function progress_dialog.update(player, progress, total)
|
||||
-- Be defensive here in case an old GUI is present
|
||||
local frame = player.gui.screen.inventory_sync_progress
|
||||
@@ -88,6 +95,9 @@ function progress_dialog.update(player, progress, total)
|
||||
return true
|
||||
end
|
||||
|
||||
--- @param player LuaPlayer
|
||||
--- @param progress number
|
||||
--- @param total number
|
||||
function progress_dialog.display(player, progress, total)
|
||||
if not progress_dialog.update(player, progress, total) then
|
||||
progress_dialog.remove(player)
|
||||
|
||||
@@ -7,21 +7,24 @@ When a player joins the game, request the player data from controller and deseri
|
||||
|
||||
]]
|
||||
|
||||
local compat = require("modules/clusterio/compat")
|
||||
local clusterio_api = require("modules/clusterio/api")
|
||||
local download_inventory = require("modules/inventory_sync/download_inventory")
|
||||
local serialize = require("modules/inventory_sync/serialize")
|
||||
local restore_position = require("modules/inventory_sync/restore_position")
|
||||
local get_script_data = require("modules/inventory_sync/get_script_data")
|
||||
local progress_dialog = require("modules/inventory_sync/gui/progress_dialog")
|
||||
local dialog_failed_download = require("modules/inventory_sync/gui/dialog_failed_download")
|
||||
|
||||
|
||||
-- Returns true if the player is currently in a cutscene
|
||||
local function is_in_cutscene(player)
|
||||
return player.controller_type == defines.controllers.cutscene
|
||||
end
|
||||
|
||||
-- Create player record for bookeeping
|
||||
-- Create player record for bookkeeping
|
||||
local function create_player(player, dirty)
|
||||
global.inventory_sync.players[player.name] = {
|
||||
local script_data = get_script_data(true) -- no early return to support loading from single player
|
||||
script_data.players[player.name] = {
|
||||
dirty = dirty, -- Player inventory has changes that should be persisted
|
||||
sync = false, -- Player inventory is synced from the controller
|
||||
generation = 0,
|
||||
@@ -30,12 +33,13 @@ end
|
||||
|
||||
-- Remove all stored player data by this module
|
||||
local function remove_player(player)
|
||||
global.inventory_sync.players_waiting_for_acquire[player.name] = nil
|
||||
global.inventory_sync.players_in_cutscene_to_sync[player.name] = nil
|
||||
global.inventory_sync.active_downloads[player.name] = nil
|
||||
global.inventory_sync.finished_downloads[player.name] = nil
|
||||
global.inventory_sync.active_uploads[player.name] = nil
|
||||
global.inventory_sync.players[player.name] = nil
|
||||
local script_data = get_script_data()
|
||||
script_data.players_waiting_for_acquire[player.name] = nil
|
||||
script_data.players_in_cutscene_to_sync[player.name] = nil
|
||||
script_data.active_downloads[player.name] = nil
|
||||
script_data.finished_downloads[player.name] = nil
|
||||
script_data.active_uploads[player.name] = nil
|
||||
script_data.players[player.name] = nil
|
||||
end
|
||||
|
||||
|
||||
@@ -56,7 +60,7 @@ function inventory_sync.serialize_player(player, player_record)
|
||||
return
|
||||
end
|
||||
|
||||
-- mod-compat: Exit Space Exploration satelite view to restore the real character
|
||||
-- mod-compat: Exit Space Exploration satellite view to restore the real character
|
||||
if remote.interfaces["space-exploration"] then
|
||||
local status, result = pcall(function()
|
||||
if remote.call("space-exploration", "remote_view_is_active", { player = player }) then
|
||||
@@ -75,31 +79,6 @@ function inventory_sync.serialize_player(player, player_record)
|
||||
return serialized_player
|
||||
end
|
||||
|
||||
local can_enter_vehicle = {
|
||||
[defines.controllers.character] = true,
|
||||
[defines.controllers.god] = true,
|
||||
[defines.controllers.editor] = true,
|
||||
}
|
||||
|
||||
local function restore_position(player, record)
|
||||
if record.vehicle and record.vehicle.valid and can_enter_vehicle[player.controller_type] then
|
||||
player.teleport(record.vehicle.position, record.vehicle.surface)
|
||||
player.driving = true
|
||||
|
||||
-- Teleport to safe location if unable to enter vehicle
|
||||
if not player.driving and player.controller_type == defines.controllers.character then
|
||||
local safe_position = record.vehicle.surface.find_non_colliding_position(
|
||||
player.character.name, player.position, 32, 1/8
|
||||
)
|
||||
if safe_position then
|
||||
player.teleport(safe_position, player.surface)
|
||||
end
|
||||
end
|
||||
elseif record.surface and record.position then
|
||||
player.teleport(record.position, record.surface)
|
||||
end
|
||||
end
|
||||
|
||||
function inventory_sync.deserialize_player(player, finished_record)
|
||||
-- Editor mode is not supported.
|
||||
if player.controller_type == defines.controllers.editor then
|
||||
@@ -113,7 +92,8 @@ function inventory_sync.deserialize_player(player, finished_record)
|
||||
|
||||
-- Stash temporary inventory if it exists
|
||||
local stashed_corpse
|
||||
local player_record = global.inventory_sync.players[player.name]
|
||||
local script_data = get_script_data()
|
||||
local player_record = script_data.players[player.name]
|
||||
if player_record.dirty and player.character then
|
||||
local character = player.character
|
||||
local surface = character.surface
|
||||
@@ -125,7 +105,7 @@ function inventory_sync.deserialize_player(player, finished_record)
|
||||
end
|
||||
|
||||
-- Deserialize downloaded player data
|
||||
local serialized_player = game.json_to_table(finished_record.data)
|
||||
local serialized_player = compat.json_to_table(finished_record.data)
|
||||
serialize.deserialize_player(player, serialized_player)
|
||||
|
||||
-- Restore player position and driving state
|
||||
@@ -137,7 +117,7 @@ function inventory_sync.deserialize_player(player, finished_record)
|
||||
local stash = stashed_corpse.get_inventory(defines.inventory.character_corpse)
|
||||
if main then
|
||||
for i = 1, #stash do
|
||||
-- Try transfering a stack
|
||||
-- Try transferring a stack
|
||||
local source = stash[i]
|
||||
if source and source.valid_for_read then
|
||||
local target = main.find_empty_stack()
|
||||
@@ -153,7 +133,7 @@ function inventory_sync.deserialize_player(player, finished_record)
|
||||
stashed_corpse.destroy()
|
||||
else
|
||||
player.print(
|
||||
"Your temprorary inventory did not fit into your synced one and the remaining items have " ..
|
||||
"Your temporary inventory did not fit into your synced one and the remaining items have " ..
|
||||
"been placed in a corpse below you."
|
||||
)
|
||||
end
|
||||
@@ -162,30 +142,12 @@ end
|
||||
|
||||
inventory_sync.events = {}
|
||||
inventory_sync.events[clusterio_api.events.on_server_startup] = function(event)
|
||||
-- Set up global table
|
||||
if not global.inventory_sync then
|
||||
global.inventory_sync = {}
|
||||
end
|
||||
|
||||
if not global.inventory_sync.players then
|
||||
global.inventory_sync.players = {}
|
||||
end
|
||||
|
||||
global.inventory_sync.players_waiting_for_acquire = {}
|
||||
global.inventory_sync.players_in_cutscene_to_sync = {}
|
||||
|
||||
if not global.inventory_sync.active_downloads then
|
||||
global.inventory_sync.active_downloads = {}
|
||||
end
|
||||
if not global.inventory_sync.finished_downloads then
|
||||
global.inventory_sync.finished_downloads = {}
|
||||
end
|
||||
if not global.inventory_sync.active_uploads then
|
||||
global.inventory_sync.active_uploads = {}
|
||||
end
|
||||
local script_data = get_script_data(true)
|
||||
script_data.players_waiting_for_acquire = {}
|
||||
script_data.players_in_cutscene_to_sync = {}
|
||||
|
||||
for _, player in pairs(game.players) do
|
||||
if not global.inventory_sync.players[player.name] then
|
||||
if not script_data.players[player.name] then
|
||||
create_player(player, true)
|
||||
end
|
||||
end
|
||||
@@ -201,7 +163,8 @@ inventory_sync.events[defines.events.on_player_created] = function(event)
|
||||
end
|
||||
|
||||
function inventory_sync.acquire(player)
|
||||
global.inventory_sync.players_waiting_for_acquire[player.name] = {
|
||||
local script_data = get_script_data()
|
||||
script_data.players_waiting_for_acquire[player.name] = {
|
||||
start_tick = game.ticks_played,
|
||||
}
|
||||
clusterio_api.send_json("inventory_sync_acquire", {
|
||||
@@ -210,10 +173,11 @@ function inventory_sync.acquire(player)
|
||||
end
|
||||
|
||||
function inventory_sync.check_players_waiting_for_acquire()
|
||||
for player_name, record in pairs(global.inventory_sync.players_waiting_for_acquire) do
|
||||
local script_data = get_script_data()
|
||||
for player_name, record in pairs(script_data.players_waiting_for_acquire) do
|
||||
-- Check if the acquire player request timed out
|
||||
if game.ticks_played > record.start_tick + 600 then
|
||||
inventory_sync.acquire_response(game.table_to_json({
|
||||
inventory_sync.acquire_response(compat.table_to_json({
|
||||
status = "timeout",
|
||||
player_name = player_name,
|
||||
}))
|
||||
@@ -222,33 +186,35 @@ function inventory_sync.check_players_waiting_for_acquire()
|
||||
end
|
||||
|
||||
function inventory_sync.acquire_response(data)
|
||||
local response = game.json_to_table(data)
|
||||
if not global.inventory_sync.players_waiting_for_acquire[response.player_name] then
|
||||
local script_data = get_script_data()
|
||||
local response = assert(compat.json_to_table(data))
|
||||
if not script_data.players_waiting_for_acquire[response.player_name] then
|
||||
clusterio_api.send_json("inventory_sync_release", { player_name = response.player_name })
|
||||
return
|
||||
end
|
||||
global.inventory_sync.players_waiting_for_acquire[response.player_name] = nil
|
||||
script_data.players_waiting_for_acquire[response.player_name] = nil
|
||||
|
||||
local player = game.get_player(response.player_name)
|
||||
if is_in_cutscene(player) then
|
||||
response.player = player
|
||||
global.inventory_sync.players_in_cutscene_to_sync[response.player_name] = response
|
||||
script_data.players_in_cutscene_to_sync[response.player_name] = response
|
||||
else
|
||||
inventory_sync.sync_player(response)
|
||||
end
|
||||
end
|
||||
|
||||
function inventory_sync.check_players_in_cutscene()
|
||||
for player_name, response in pairs(global.inventory_sync.players_in_cutscene_to_sync) do
|
||||
local script_data = get_script_data()
|
||||
for player_name, response in pairs(script_data.players_in_cutscene_to_sync) do
|
||||
if not is_in_cutscene(response.player) then
|
||||
global.inventory_sync.players_in_cutscene_to_sync[player_name] = nil
|
||||
script_data.players_in_cutscene_to_sync[player_name] = nil
|
||||
inventory_sync.sync_player(response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function inventory_sync.sync_player(acquire_response)
|
||||
local player = game.get_player(acquire_response.player_name)
|
||||
local player = assert(game.get_player(acquire_response.player_name))
|
||||
|
||||
-- Editor mode is not supported.
|
||||
if player.controller_type == defines.controllers.editor then
|
||||
@@ -261,7 +227,8 @@ function inventory_sync.sync_player(acquire_response)
|
||||
return
|
||||
end
|
||||
|
||||
local finished_record = global.inventory_sync.finished_downloads[acquire_response.player_name]
|
||||
local script_data = get_script_data()
|
||||
local finished_record = script_data.finished_downloads[acquire_response.player_name]
|
||||
if finished_record then
|
||||
if acquire_response.status == "acquired" and finished_record.generation == acquire_response.generation then
|
||||
inventory_sync.finish_download(player, finished_record)
|
||||
@@ -270,10 +237,10 @@ function inventory_sync.sync_player(acquire_response)
|
||||
|
||||
-- The acquisition either failed or the current finished download is
|
||||
-- stale, delete it and try downloading it again.
|
||||
global.inventory_sync.finished_downloads[acquire_response.player_name] = nil
|
||||
script_data.finished_downloads[acquire_response.player_name] = nil
|
||||
end
|
||||
|
||||
local download_record = global.inventory_sync.active_downloads[acquire_response.player_name]
|
||||
local download_record = script_data.active_downloads[acquire_response.player_name]
|
||||
if download_record then
|
||||
-- Ignore the response if it affirms the current active download
|
||||
if acquire_response.status == "acquired" and download_record.generation == acquire_response.generation then
|
||||
@@ -303,17 +270,17 @@ function inventory_sync.sync_player(acquire_response)
|
||||
if acquire_response.status == "busy" then
|
||||
-- Somehow another instance has claimed the player while we're
|
||||
-- downloading it. Abort it and send failure to the player.
|
||||
global.inventory_sync.active_downloads[acquire_response.player_name] = nil
|
||||
script_data.active_downloads[acquire_response.player_name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local player_record = global.inventory_sync.players[acquire_response.player_name]
|
||||
local player_record = script_data.players[acquire_response.player_name]
|
||||
if acquire_response.status ~= "acquired" then
|
||||
if player_record.sync and not player_record.dirty then
|
||||
if player.controller_type == defines.controllers.character then
|
||||
local character = player.character
|
||||
player.set_controller({ type = defines.controllers.spectator })
|
||||
character.destroy()
|
||||
if character then character.destroy() end
|
||||
end
|
||||
player_record.sync = false
|
||||
end
|
||||
@@ -347,9 +314,10 @@ function inventory_sync.finish_download(player, finished_record)
|
||||
log("ERROR: Deserializing player " .. player.name .. " failed: " .. result)
|
||||
player.print("ERROR: Deserializing player data failed: " .. result)
|
||||
end
|
||||
global.inventory_sync.finished_downloads[player.name] = nil
|
||||
local script_data = get_script_data()
|
||||
script_data.finished_downloads[player.name] = nil
|
||||
|
||||
local player_record = global.inventory_sync.players[player.name]
|
||||
local player_record = script_data.players[player.name]
|
||||
player_record.dirty = true
|
||||
player_record.sync = true
|
||||
player_record.generation = finished_record.generation
|
||||
@@ -357,18 +325,19 @@ end
|
||||
|
||||
-- Download inventory from controller
|
||||
inventory_sync.events[defines.events.on_player_joined_game] = function(event)
|
||||
local player = game.get_player(event.player_index)
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
local script_data = get_script_data()
|
||||
|
||||
-- It's possible Factorio doesn't invoke the on_player_created event when loading a save in single player
|
||||
if not global.inventory_sync.players[player.name] then
|
||||
if not script_data.players[player.name] then
|
||||
create_player(player, false)
|
||||
end
|
||||
|
||||
-- Send acquire request even if an active download is currently in plogress
|
||||
inventory_sync.acquire(player, false)
|
||||
-- Send acquire request even if an active download is currently in progress
|
||||
inventory_sync.acquire(player)
|
||||
|
||||
-- Clear active upload if it existsi
|
||||
global.inventory_sync.active_uploads[player.name] = nil
|
||||
-- Clear active upload if it exists
|
||||
script_data.active_uploads[player.name] = nil
|
||||
end
|
||||
|
||||
inventory_sync.on_nth_tick = {}
|
||||
@@ -390,17 +359,18 @@ end
|
||||
|
||||
-- Upload inventory when a player leaves the game. Triggers on restart after crash if player was online during crash.
|
||||
inventory_sync.events[defines.events.on_pre_player_left_game] = function(event)
|
||||
-- for some reason, on_pre_player_left_game gets called before on_server_startup so global isn't ready yet
|
||||
if not global.inventory_sync then
|
||||
log("ERROR: Global inventory sync not defined")
|
||||
-- for some reason, on_pre_player_left_game gets called before on_server_startup so script_data isn't ready yet
|
||||
if not compat.script_data().inventory_sync then
|
||||
log("ERROR: script data for inventory sync is not defined")
|
||||
return
|
||||
end
|
||||
|
||||
local player = game.get_player(event.player_index)
|
||||
local player_record = global.inventory_sync.players[player.name]
|
||||
local script_data = get_script_data()
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
local player_record = script_data.players[player.name]
|
||||
|
||||
-- If player has an active download release our acquisition but let the download continue
|
||||
if global.inventory_sync.active_downloads[player.name] then
|
||||
if script_data.active_downloads[player.name] then
|
||||
clusterio_api.send_json("inventory_sync_release", { player_name = player.name })
|
||||
return
|
||||
end
|
||||
@@ -416,7 +386,7 @@ inventory_sync.events[defines.events.on_pre_player_left_game] = function(event)
|
||||
player.print("ERROR: Serializing player data failed: " .. result)
|
||||
return
|
||||
end
|
||||
global.inventory_sync.active_uploads[player.name] = {
|
||||
script_data.active_uploads[player.name] = {
|
||||
serialized = result,
|
||||
last_attempt = game.ticks_played,
|
||||
timeout = math.random(600, 1200), -- Start with 10-20 seconds for the timeout
|
||||
@@ -432,7 +402,8 @@ function inventory_sync.confirm_upload(player_name, generation)
|
||||
log("no player, or player connected")
|
||||
return
|
||||
end
|
||||
local player_record = global.inventory_sync.players[player.name]
|
||||
local script_data = get_script_data()
|
||||
local player_record = script_data.players[player.name]
|
||||
if not player_record or player_record.generation ~= generation then
|
||||
return
|
||||
end
|
||||
@@ -444,7 +415,7 @@ function inventory_sync.confirm_upload(player_name, generation)
|
||||
log("ERROR: Received upload confirmation for " .. player_name .. " without the sync flag")
|
||||
end
|
||||
log("Confirmed upload of " .. player_name)
|
||||
global.inventory_sync.active_uploads[player_name] = nil
|
||||
script_data.active_uploads[player_name] = nil
|
||||
player_record.dirty = false
|
||||
end
|
||||
|
||||
@@ -456,7 +427,8 @@ function inventory_sync.initiate_inventory_download(player, player_record, gener
|
||||
generation = generation,
|
||||
data = ""
|
||||
}
|
||||
global.inventory_sync.active_downloads[player.name] = record
|
||||
local script_data = get_script_data()
|
||||
script_data.active_downloads[player.name] = record
|
||||
|
||||
clusterio_api.send_json("inventory_sync_download", {
|
||||
player_name = player.name
|
||||
@@ -486,9 +458,10 @@ end
|
||||
function inventory_sync.check_active_downloads()
|
||||
-- Used for detecting the inventory download failing, like when the map is saved and
|
||||
-- re-loaded in the middle of a download. The player will be returned to a playable state.
|
||||
for player_name, record in pairs(global.inventory_sync.active_downloads) do
|
||||
local script_data = get_script_data()
|
||||
for player_name, record in pairs(script_data.active_downloads) do
|
||||
if record.last_active <= game.ticks_played - 600 then
|
||||
global.inventory_sync.active_downloads[player_name] = nil
|
||||
script_data.active_downloads[player_name] = nil
|
||||
local player = game.get_player(player_name)
|
||||
if player then
|
||||
log("ERROR: Inventory download failed for " .. player_name)
|
||||
@@ -507,12 +480,13 @@ function inventory_sync.check_active_downloads()
|
||||
end
|
||||
|
||||
function inventory_sync.check_active_uploads()
|
||||
for player_name, record in pairs(global.inventory_sync.active_uploads) do
|
||||
local script_data = get_script_data()
|
||||
for player_name, record in pairs(script_data.active_uploads) do
|
||||
if record.last_attempt + record.timeout < game.ticks_played then
|
||||
local player_record = global.inventory_sync.players[player_name]
|
||||
local player_record = script_data.players[player_name]
|
||||
local player = game.get_player(player_name)
|
||||
if not player_record or not player then
|
||||
global.inventory_sync.active_uploads[player_name] = nil
|
||||
script_data.active_uploads[player_name] = nil
|
||||
else
|
||||
log("Retrying upload for " .. player_name)
|
||||
record.last_attempt = game.ticks_played
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
local can_enter_vehicle = {
|
||||
[defines.controllers.character] = true,
|
||||
[defines.controllers.god] = true,
|
||||
[defines.controllers.editor] = true,
|
||||
}
|
||||
|
||||
--- @param player LuaPlayer
|
||||
--- @param record table
|
||||
return function(player, record)
|
||||
if record.vehicle and record.vehicle.valid and can_enter_vehicle[player.controller_type] then
|
||||
player.teleport(record.vehicle.position, record.vehicle.surface)
|
||||
player.driving = true
|
||||
|
||||
-- Teleport to safe location if unable to enter vehicle
|
||||
if not player.driving and player.controller_type == defines.controllers.character then
|
||||
local safe_position = record.vehicle.surface.find_non_colliding_position(
|
||||
player.character.name, player.position, 32, 1/8
|
||||
)
|
||||
if safe_position then
|
||||
player.teleport(safe_position, player.surface)
|
||||
end
|
||||
end
|
||||
elseif record.surface and record.position then
|
||||
player.teleport(record.position, record.surface)
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,12 @@
|
||||
local compat = require("modules/clusterio/compat")
|
||||
local clusterio_serialize = require("modules/clusterio/serialize")
|
||||
local character_inventories = require("modules/inventory_sync/define_player_inventories")
|
||||
local character_stat_keys = require("modules/inventory_sync/define_player_stat_keys")
|
||||
local serialize = {}
|
||||
|
||||
local v2_logistic_api = compat.version_ge("2.0.0")
|
||||
local v2_storage_api = compat.version_ge("2.0.0")
|
||||
|
||||
function serialize.serialize_inventories(source, inventories)
|
||||
local serialized = {}
|
||||
|
||||
@@ -72,6 +76,10 @@ end
|
||||
-- min
|
||||
-- max
|
||||
function serialize.serialize_personal_logistic_slots(player)
|
||||
if v2_logistic_api then
|
||||
return nil -- TODO implement v2_logistic_api
|
||||
end
|
||||
|
||||
local serialized = nil
|
||||
|
||||
-- Serialize personal logistic slots
|
||||
@@ -298,8 +306,8 @@ function serialize.serialize_player(player)
|
||||
end
|
||||
end
|
||||
|
||||
-- Serialize crafting queue
|
||||
if player.character then
|
||||
-- Serialize crafting queue, TODO implement v2_storage_api
|
||||
if player.character and not v2_storage_api then
|
||||
serialized.crafting_queue = serialize.serialize_crafting_queue(player)
|
||||
end
|
||||
|
||||
@@ -328,8 +336,8 @@ function serialize.deserialize_player(player, serialized)
|
||||
-- We have to set ticks to respawn to save the hidden state into the player but we
|
||||
-- can't unset tick_to_respawn by setting it back to nil as that triggers a respawn.
|
||||
player.ticks_to_respawn = 0
|
||||
player.set_controller({ type = defines.controller.god })
|
||||
player.set_controller({ type = defines.controller.ghost })
|
||||
player.set_controller({ type = defines.controllers.god })
|
||||
player.set_controller({ type = defines.controllers.ghost })
|
||||
end
|
||||
character.destroy()
|
||||
end
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
local clusterio_api = require("modules/clusterio/api")
|
||||
local compat = require("modules/clusterio/compat")
|
||||
local auth = {}
|
||||
|
||||
local v2_styles = compat.version_ge("2.0.0")
|
||||
|
||||
--- @param player LuaPlayer
|
||||
--- @param url string
|
||||
--- @param code string
|
||||
local function open_dialog(player, url, code)
|
||||
if player.gui.screen.player_auth_dialog then
|
||||
player.gui.screen.player_auth_dialog.destroy()
|
||||
@@ -28,7 +34,7 @@ local function open_dialog(player, url, code)
|
||||
type = "empty-widget",
|
||||
style = "draggable_space_header",
|
||||
}
|
||||
filler.style.horizontally_stretchable = "on"
|
||||
filler.style.horizontally_stretchable = true
|
||||
filler.style.right_margin = 4
|
||||
filler.style.height = 24
|
||||
filler.drag_target = frame
|
||||
@@ -36,7 +42,7 @@ local function open_dialog(player, url, code)
|
||||
titlebar.add {
|
||||
name = "player_auth_dialog_close_button",
|
||||
type = "sprite-button",
|
||||
sprite = "utility/close_white",
|
||||
sprite = v2_styles and "utility/close" or "utility/close_white",
|
||||
hovered_sprite = "utility/close_black",
|
||||
clicked_sprite = "utility/close_black",
|
||||
style = "frame_action_button",
|
||||
@@ -178,6 +184,8 @@ function auth.add_commands()
|
||||
end
|
||||
|
||||
auth.events = {}
|
||||
|
||||
--- @param event EventData.on_gui_click
|
||||
auth.events[defines.events.on_gui_click] = function(event)
|
||||
if not event.element or not event.element.valid then
|
||||
return
|
||||
@@ -197,6 +205,7 @@ auth.events[defines.events.on_gui_click] = function(event)
|
||||
end
|
||||
end
|
||||
|
||||
--- @param event EventData.on_gui_closed
|
||||
auth.events[defines.events.on_gui_closed] = function(event)
|
||||
if
|
||||
event.element
|
||||
|
||||
@@ -1,53 +1,79 @@
|
||||
local clusterio_api = require("modules/clusterio/api")
|
||||
local compat = require("modules/clusterio/compat")
|
||||
|
||||
local v2_tech_progress = compat.version_ge("2.0.0")
|
||||
|
||||
local sync = {}
|
||||
|
||||
|
||||
--- @param tech LuaTechnology
|
||||
local function get_technology_progress(tech)
|
||||
if tech == tech.force.current_research then
|
||||
return tech.force.research_progress
|
||||
elseif v2_tech_progress then
|
||||
return tech.saved_progress
|
||||
else
|
||||
--- @diagnostic disable-next-line
|
||||
return tech.force.get_saved_technology_progress(tech.name)
|
||||
end
|
||||
end
|
||||
|
||||
--- @param tech LuaTechnology
|
||||
--- @param progress number
|
||||
local function set_technology_progress(tech, progress)
|
||||
if tech == tech.force.current_research then
|
||||
tech.force.research_progress = progress
|
||||
elseif v2_tech_progress then
|
||||
tech.saved_progress = progress
|
||||
else
|
||||
--- @diagnostic disable-next-line
|
||||
tech.force.set_saved_technology_progress(tech.name, progress)
|
||||
end
|
||||
end
|
||||
|
||||
sync.events = {}
|
||||
sync.events[clusterio_api.events.on_server_startup] = function(event)
|
||||
if not global.research_sync then
|
||||
global.research_sync = {
|
||||
technologies = {},
|
||||
}
|
||||
--- @param no_early_return boolean?
|
||||
--- @return table
|
||||
local function get_script_data(no_early_return)
|
||||
local script_data = compat.script_data()
|
||||
local research_sync = script_data.research_sync
|
||||
if research_sync and not no_early_return then
|
||||
return research_sync
|
||||
end
|
||||
|
||||
-- Used when syncing completed technologies from the controller
|
||||
global.research_sync.ignore_research_finished = false
|
||||
research_sync = research_sync or {}
|
||||
research_sync.technologies = research_sync.technologies or {}
|
||||
|
||||
if research_sync.ignore_research_finished == nil then
|
||||
research_sync.ignore_research_finished = false
|
||||
end
|
||||
|
||||
local force = game.forces["player"]
|
||||
for _, tech in pairs(force.technologies) do
|
||||
local progress = get_technology_progress(tech)
|
||||
global.research_sync.technologies[tech.name] = {
|
||||
research_sync.technologies[tech.name] = {
|
||||
level = tech.level,
|
||||
researched = tech.researched,
|
||||
progress = progress,
|
||||
}
|
||||
end
|
||||
|
||||
script_data.research_sync = research_sync
|
||||
return research_sync
|
||||
end
|
||||
|
||||
sync.events = {}
|
||||
sync.events[clusterio_api.events.on_server_startup] = function(event)
|
||||
get_script_data(true)
|
||||
end
|
||||
|
||||
--- @param tech LuaTechnology
|
||||
local function get_contribution(tech)
|
||||
local progress = get_technology_progress(tech)
|
||||
if not progress then
|
||||
return 0, nil
|
||||
end
|
||||
|
||||
local prev_tech = global.research_sync.technologies[tech.name]
|
||||
local script_data = get_script_data()
|
||||
local prev_tech = script_data.technologies[tech.name]
|
||||
if prev_tech.progress and prev_tech.level == tech.level then
|
||||
return progress - prev_tech.progress, progress
|
||||
else
|
||||
@@ -55,6 +81,7 @@ local function get_contribution(tech)
|
||||
end
|
||||
end
|
||||
|
||||
--- @param tech LuaTechnology
|
||||
local function send_contribution(tech)
|
||||
local contribution, progress = get_contribution(tech)
|
||||
if contribution ~= 0 then
|
||||
@@ -63,10 +90,12 @@ local function send_contribution(tech)
|
||||
level = tech.level,
|
||||
contribution = contribution,
|
||||
})
|
||||
global.research_sync.technologies[tech.name].progress = progress
|
||||
local script_data = get_script_data()
|
||||
script_data.technologies[tech.name].progress = progress
|
||||
end
|
||||
end
|
||||
|
||||
--- @param event EventData.on_research_started
|
||||
sync.events[defines.events.on_research_started] = function(event)
|
||||
local tech = event.last_research
|
||||
if tech then
|
||||
@@ -74,13 +103,15 @@ sync.events[defines.events.on_research_started] = function(event)
|
||||
end
|
||||
end
|
||||
|
||||
--- @param event EventData.on_research_finished
|
||||
sync.events[defines.events.on_research_finished] = function(event)
|
||||
if global.research_sync.ignore_research_finished then
|
||||
local script_data = get_script_data()
|
||||
if research_sync.ignore_research_finished then
|
||||
return
|
||||
end
|
||||
|
||||
local tech = event.research
|
||||
global.research_sync.technologies[tech.name] = {
|
||||
script_data.technologies[tech.name] = {
|
||||
level = tech.level,
|
||||
researched = tech.researched,
|
||||
}
|
||||
@@ -121,10 +152,11 @@ function research_sync.dump_technologies()
|
||||
if #techs == 0 then
|
||||
rcon.print("[]")
|
||||
else
|
||||
rcon.print(game.table_to_json(techs))
|
||||
rcon.print(compat.table_to_json(techs))
|
||||
end
|
||||
end
|
||||
|
||||
--- @param data string
|
||||
function research_sync.sync_technologies(data)
|
||||
local force = game.forces["player"]
|
||||
|
||||
@@ -133,8 +165,9 @@ function research_sync.sync_technologies(data)
|
||||
local progressIndex = 3
|
||||
local researchedIndex = 4
|
||||
|
||||
global.research_sync.ignore_research_finished = true
|
||||
for _, tech_data in pairs(game.json_to_table(data)) do
|
||||
local script_data = get_script_data()
|
||||
script_data.ignore_research_finished = true
|
||||
for _, tech_data in pairs(compat.json_to_table(data) --[[@as table]]) do
|
||||
local tech = force.technologies[tech_data[nameIndex]]
|
||||
if tech and tech.level <= tech_data[levelIndex] then
|
||||
local new_level = math.min(tech_data[levelIndex], tech.prototype.max_level)
|
||||
@@ -162,18 +195,20 @@ function research_sync.sync_technologies(data)
|
||||
progress = get_technology_progress(tech)
|
||||
end
|
||||
|
||||
global.research_sync.technologies[tech.name] = {
|
||||
script_data.technologies[tech.name] = {
|
||||
level = tech.level,
|
||||
researched = tech.researched,
|
||||
progress = progress,
|
||||
}
|
||||
end
|
||||
end
|
||||
global.research_sync.ignore_research_finished = false
|
||||
script_data.ignore_research_finished = false
|
||||
end
|
||||
|
||||
--- @param data string
|
||||
function research_sync.update_progress(data)
|
||||
local techs = game.json_to_table(data)
|
||||
local script_data = get_script_data()
|
||||
local techs = compat.json_to_table(data) --[[@as table]]
|
||||
local force = game.forces["player"]
|
||||
|
||||
for _, controllerTech in ipairs(techs) do
|
||||
@@ -181,7 +216,7 @@ function research_sync.update_progress(data)
|
||||
if tech and tech.level == controllerTech.level then
|
||||
send_contribution(tech)
|
||||
set_technology_progress(tech, controllerTech.progress)
|
||||
global.research_sync.technologies[tech.name] = {
|
||||
script_data.technologies[tech.name] = {
|
||||
level = tech.level,
|
||||
progress = controllerTech.progress
|
||||
}
|
||||
@@ -189,6 +224,8 @@ function research_sync.update_progress(data)
|
||||
end
|
||||
end
|
||||
|
||||
--- @param name string
|
||||
--- @param level number
|
||||
function research_sync.research_technology(name, level)
|
||||
local force = game.forces["player"]
|
||||
local tech = force.technologies[name]
|
||||
@@ -200,7 +237,8 @@ function research_sync.research_technology(name, level)
|
||||
level = tech.prototype.max_level
|
||||
end
|
||||
|
||||
global.research_sync.ignore_research_finished = true
|
||||
local script_data = get_script_data()
|
||||
script_data.ignore_research_finished = true
|
||||
if tech == force.current_research and tech.level == level then
|
||||
force.research_progress = 1
|
||||
|
||||
@@ -215,9 +253,9 @@ function research_sync.research_technology(name, level)
|
||||
end
|
||||
game.play_sound { path = "utility/research_completed" }
|
||||
end
|
||||
global.research_sync.ignore_research_finished = false
|
||||
script_data.ignore_research_finished = false
|
||||
|
||||
global.research_sync.technologies[tech.name] = {
|
||||
script_data.technologies[tech.name] = {
|
||||
level = tech.level,
|
||||
researched = tech.researched,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
local compat = require("modules/clusterio/compat")
|
||||
|
||||
local v2_stats = compat.version_ge("2.0.0")
|
||||
|
||||
local statistics = {
|
||||
"item_production_statistics",
|
||||
"fluid_production_statistics",
|
||||
@@ -7,13 +11,16 @@ local statistics = {
|
||||
|
||||
statistics_exporter = {}
|
||||
function statistics_exporter.export()
|
||||
--- @diagnostic disable-next-line undefined-field
|
||||
local pollution = v2_stats and game.get_pollution_statistics(1) or game.pollution_statistics
|
||||
local stats = {
|
||||
game_tick = game.tick,
|
||||
player_count = #game.connected_players,
|
||||
game_flow_statistics = {
|
||||
pollution_statistics = {
|
||||
input = game.pollution_statistics.input_counts,
|
||||
output = game.pollution_statistics.output_counts,
|
||||
--- TODO support multi surface pollution statistics
|
||||
input = pollution.input_counts,
|
||||
output = pollution.output_counts,
|
||||
},
|
||||
},
|
||||
force_flow_statistics = {}
|
||||
@@ -21,14 +28,15 @@ function statistics_exporter.export()
|
||||
for _, force in pairs(game.forces) do
|
||||
local flow_statistics = {}
|
||||
for _, statName in pairs(statistics) do
|
||||
local stat = v2_stats and force["get_" .. statName](1) or force[statName]
|
||||
flow_statistics[statName] = {
|
||||
input = force[statName].input_counts,
|
||||
output = force[statName].output_counts,
|
||||
input = stat.input_counts,
|
||||
output = stat.output_counts,
|
||||
}
|
||||
end
|
||||
stats.force_flow_statistics[force.name] = flow_statistics
|
||||
end
|
||||
|
||||
|
||||
rcon.print(game.table_to_json(stats))
|
||||
rcon.print(compat.table_to_json(stats))
|
||||
end
|
||||
Reference in new issue
Block a user