diff --git a/CHANGELOG.md b/CHANGELOG.md index 436827f4..66ba7375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/create/templates/common/module/control.lua b/packages/create/templates/common/module/control.lua index 458f5cf3..06546e10 100644 --- a/packages/create/templates/common/module/control.lua +++ b/packages/create/templates/common/module/control.lua @@ -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 diff --git a/packages/create/templates/common/module/module_exports.lua b/packages/create/templates/common/module/module_exports.lua index d8dd0ae4..3642b397 100644 --- a/packages/create/templates/common/module/module_exports.lua +++ b/packages/create/templates/common/module/module_exports.lua @@ -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") diff --git a/packages/host/lua/clusterio_lib/api.lua b/packages/host/lua/clusterio_lib/api.lua index 90b121e3..6af5463b 100644 --- a/packages/host/lua/clusterio_lib/api.lua +++ b/packages/host/lua/clusterio_lib/api.lua @@ -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 diff --git a/packages/host/lua/clusterio_lib/info.json b/packages/host/lua/clusterio_lib/info.json index 5afc7b4f..cd4bbbed 100644 --- a/packages/host/lua/clusterio_lib/info.json +++ b/packages/host/lua/clusterio_lib/info.json @@ -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"] } } diff --git a/packages/host/lua/export/control.lua b/packages/host/lua/export/control.lua index b810f2f0..1833a4cc 100644 --- a/packages/host/lua/export/control.lua +++ b/packages/host/lua/export/control.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) diff --git a/packages/host/modules/clusterio/api.lua b/packages/host/modules/clusterio/api.lua index 08e7fbf1..11ea3aa4 100644 --- a/packages/host/modules/clusterio/api.lua +++ b/packages/host/modules/clusterio/api.lua @@ -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 diff --git a/packages/host/modules/clusterio/compat.lua b/packages/host/modules/clusterio/compat.lua new file mode 100644 index 00000000..cf45e9c1 --- /dev/null +++ b/packages/host/modules/clusterio/compat.lua @@ -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 diff --git a/packages/host/modules/clusterio/impl.lua b/packages/host/modules/clusterio/impl.lua index 93175950..f7923855 100644 --- a/packages/host/modules/clusterio/impl.lua +++ b/packages/host/modules/clusterio/impl.lua @@ -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, }) diff --git a/packages/host/modules/clusterio/serialize.lua b/packages/host/modules/clusterio/serialize.lua index b03557cf..e8b437d5 100644 --- a/packages/host/modules/clusterio/serialize.lua +++ b/packages/host/modules/clusterio/serialize.lua @@ -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 diff --git a/packages/host/src/patch.ts b/packages/host/src/patch.ts index 9b7f1fb9..9a675e60 100644 --- a/packages/host/src/patch.ts +++ b/packages/host/src/patch.ts @@ -346,9 +346,8 @@ function reorderDependencies(modules: SaveModule[]) { const knownScenarios: Record = { // 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"]), }; /** diff --git a/plugins/inventory_sync/module/define_player_stat_keys.lua b/plugins/inventory_sync/module/define_player_stat_keys.lua index a5749add..8e6a2c15 100644 --- a/plugins/inventory_sync/module/define_player_stat_keys.lua +++ b/plugins/inventory_sync/module/define_player_stat_keys.lua @@ -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 diff --git a/plugins/inventory_sync/module/download_inventory.lua b/plugins/inventory_sync/module/download_inventory.lua index 8d494031..17e2d7b8 100644 --- a/plugins/inventory_sync/module/download_inventory.lua +++ b/plugins/inventory_sync/module/download_inventory.lua @@ -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") diff --git a/plugins/inventory_sync/module/get_script_data.lua b/plugins/inventory_sync/module/get_script_data.lua new file mode 100644 index 00000000..e9dacf3a --- /dev/null +++ b/plugins/inventory_sync/module/get_script_data.lua @@ -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 diff --git a/plugins/inventory_sync/module/gui/dialog_failed_download.lua b/plugins/inventory_sync/module/gui/dialog_failed_download.lua index 4da5debe..de0ac5c7 100644 --- a/plugins/inventory_sync/module/gui/dialog_failed_download.lua +++ b/plugins/inventory_sync/module/gui/dialog_failed_download.lua @@ -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 diff --git a/plugins/inventory_sync/module/gui/progress_dialog.lua b/plugins/inventory_sync/module/gui/progress_dialog.lua index 7fa5865f..7ffd21ab 100644 --- a/plugins/inventory_sync/module/gui/progress_dialog.lua +++ b/plugins/inventory_sync/module/gui/progress_dialog.lua @@ -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) diff --git a/plugins/inventory_sync/module/inventory_sync.lua b/plugins/inventory_sync/module/inventory_sync.lua index d7e8483c..f800185f 100644 --- a/plugins/inventory_sync/module/inventory_sync.lua +++ b/plugins/inventory_sync/module/inventory_sync.lua @@ -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 diff --git a/plugins/inventory_sync/module/restore_position.lua b/plugins/inventory_sync/module/restore_position.lua new file mode 100644 index 00000000..5192bafa --- /dev/null +++ b/plugins/inventory_sync/module/restore_position.lua @@ -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 diff --git a/plugins/inventory_sync/module/serialize.lua b/plugins/inventory_sync/module/serialize.lua index ef176672..675060d1 100644 --- a/plugins/inventory_sync/module/serialize.lua +++ b/plugins/inventory_sync/module/serialize.lua @@ -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 diff --git a/plugins/player_auth/module/auth.lua b/plugins/player_auth/module/auth.lua index 822a6c2b..ad195f89 100644 --- a/plugins/player_auth/module/auth.lua +++ b/plugins/player_auth/module/auth.lua @@ -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 diff --git a/plugins/research_sync/module/sync.lua b/plugins/research_sync/module/sync.lua index 7abe268a..3ddb0b79 100644 --- a/plugins/research_sync/module/sync.lua +++ b/plugins/research_sync/module/sync.lua @@ -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, } diff --git a/plugins/statistics_exporter/module/export.lua b/plugins/statistics_exporter/module/export.lua index 52955488..8537d0f8 100644 --- a/plugins/statistics_exporter/module/export.lua +++ b/plugins/statistics_exporter/module/export.lua @@ -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