diff --git a/plugins/inventory_sync/module/inventory_sync.lua b/plugins/inventory_sync/module/inventory_sync.lua index 9805601c..ca452d4c 100644 --- a/plugins/inventory_sync/module/inventory_sync.lua +++ b/plugins/inventory_sync/module/inventory_sync.lua @@ -16,6 +16,8 @@ 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") +local v2_remote_controller = compat.version_ge("2.0.0") + -- Returns true if the player is currently in a cutscene local function is_in_cutscene(player) return player.controller_type == defines.controllers.cutscene @@ -116,9 +118,9 @@ function inventory_sync.deserialize_player(player, finished_record) -- Transfer items from stashed inventory if stashed_corpse then - local main = player.get_main_inventory() local stash = stashed_corpse.get_inventory(defines.inventory.character_corpse) - if main then + if player.character then + local main = player.character.get_main_inventory() for i = 1, #stash do -- Try transferring a stack local source = stash[i] @@ -312,7 +314,7 @@ function inventory_sync.sync_player(acquire_response) end function inventory_sync.finish_download(player, finished_record) - local status, result = pcall(inventory_sync.deserialize_player, player, finished_record) + local status, result = xpcall(inventory_sync.deserialize_player, debug.traceback, player, finished_record) if not status then log("ERROR: Deserializing player " .. player.name .. " failed: " .. result) player.print("ERROR: Deserializing player data failed: " .. result) @@ -382,7 +384,7 @@ inventory_sync.events[defines.events.on_pre_player_left_game] = function(event) end player_record.generation = player_record.generation + 1 - local status, result = pcall(inventory_sync.serialize_player, player, player_record) + local status, result = xpcall(inventory_sync.serialize_player, debug.traceback, player, player_record) if not status then log("ERROR: Serializing player " .. player.name .. " failed: " .. result) player.print("ERROR: Serializing player data failed: " .. result) @@ -440,8 +442,13 @@ function inventory_sync.initiate_inventory_download(player, player_record, gener -- player data is downloading if player_record.sync then -- Store original position to teleport back to - record.surface = player.surface - record.position = player.position + if v2_remote_controller then + record.surface = player.physical_surface + record.position = player.physical_position + else + record.surface = player.surface + record.position = player.position + end if player.driving then record.vehicle = player.vehicle end diff --git a/plugins/inventory_sync/module/restore_position.lua b/plugins/inventory_sync/module/restore_position.lua index 5192bafa..f2513133 100644 --- a/plugins/inventory_sync/module/restore_position.lua +++ b/plugins/inventory_sync/module/restore_position.lua @@ -1,9 +1,18 @@ +local compat = require("modules/clusterio/compat") + local can_enter_vehicle = { [defines.controllers.character] = true, [defines.controllers.god] = true, [defines.controllers.editor] = true, } +if defines.controllers.remote then + -- Does not exist in 1.1, so we need this nil check + can_enter_vehicle[defines.controllers.remote] = true +end + +local v2_surface_platform = compat.version_ge("2.0.0") + --- @param player LuaPlayer --- @param record table return function(player, record) @@ -20,6 +29,8 @@ return function(player, record) player.teleport(safe_position, player.surface) end end + elseif v2_surface_platform and record.surface and record.surface.platform and can_enter_vehicle[player.controller_type] then + player.enter_space_platform(record.surface.platform) elseif record.surface and record.position then player.teleport(record.position, record.surface) end diff --git a/plugins/inventory_sync/module/serialize.lua b/plugins/inventory_sync/module/serialize.lua index 28f4fa06..567289d0 100644 --- a/plugins/inventory_sync/module/serialize.lua +++ b/plugins/inventory_sync/module/serialize.lua @@ -6,6 +6,7 @@ local serialize = {} local v2_logistic_api = compat.version_ge("2.0.0") local v2_storage_api = compat.version_ge("2.0.0") +local v2_remote_controller = compat.version_ge("2.0.0") local recipe_notifications_api = compat.version_ge("2.0.67") function serialize.serialize_inventories(source, inventories) @@ -203,7 +204,7 @@ function serialize.serialize_crafting_queue(player) player.character_inventory_slots_bonus = player.character_inventory_slots_bonus + 1000 -- Save current items - local inventory = player.get_main_inventory() + local inventory = player.character.get_main_inventory() local old_items = inventory.get_contents() local crafting_queue_progress = player.crafting_queue_progress @@ -317,7 +318,7 @@ function serialize.serialize_crafting_queue(player) end function serialize.deserialize_crafting_queue(player, serialized) - local inventory = player.get_main_inventory() + local inventory = player.character.get_main_inventory() -- Give player some more inventory space to avoid duplicating items player.character_inventory_slots_bonus = player.character_inventory_slots_bonus + 1000 @@ -460,6 +461,11 @@ function serialize.serialize_player(player, failed_deserialization) ticks_to_respawn = player.ticks_to_respawn, } + -- In 2.0 we want to sync the physical controller to ignore remote view + if v2_remote_controller then + serialized.controller = controller_to_name[player.physical_controller_type] + end + -- For the waiting to respawn state the inventory logistic requests and filters are hidden on the player if player.controller_type == defines.controllers.ghost and player.ticks_to_respawn then player.ticks_to_respawn = nil -- Respawn now @@ -482,7 +488,9 @@ function serialize.serialize_player(player, failed_deserialization) end -- Serialize non-character inventories - if player.controller_type == defines.controllers.god then + if player.controller_type == defines.controllers.god + or v2_remote_controller and player.physical_controller_type == defines.controllers.god + then serialized.inventories = serialize.serialize_inventories(player, { main = defines.inventory.god_main }) end @@ -510,6 +518,26 @@ function serialize.serialize_player(player, failed_deserialization) return serialized end +--- Ensure a player has a character, works from any controller type +--- @param player LuaPlayer +--- @return LuaEntity +local function ensure_character(player) + -- Do nothing if the player has a valid character + local character = player.character + if character and character.valid then + return character + end + + -- Switch to god controller if create_character would fail + if player.controller_type ~= defines.controllers.god then + player.set_controller{ type = defines.controllers.god } + end + + -- Create and return the character + assert(player.create_character(), "Failed to create character") + return player.character +end + --- @class FailedDeserializationPlayerData --- @field recipe_notifications string[]? @@ -519,46 +547,49 @@ end function serialize.deserialize_player(player, serialized) local failed_deserialization = {} - if player.controller_type ~= defines.controllers[serialized.controller] or serialized.controller == "ghost" then - -- If targeting the character or ghost controller then create a character - if serialized.controller == "character" or serialized.controller == "ghost" then - if player.controller_type == defines.controllers.ghost or player.controller_type == defines.controllers.spectator then - player.set_controller({ type = defines.controllers.god }) - end - if player.controller_type == defines.controllers.god then - player.create_character() + local target_controller = defines.controllers[serialized.controller] + if player.controller_type ~= target_controller or serialized.controller == "ghost" then + if serialized.controller == "character" then + -- Create a character but do not destroy an existing one + ensure_character(player) + if player.controller_type ~= target_controller then + player.set_controller{ type = target_controller } end - -- The ghost state stores hidden logistic and filters which are only accessible in the character controller - if serialized.controller == "ghost" then + elseif serialized.controller == "ghost" then + -- Ghost state stores hidden logistic and filters which are only accessible in the character controller + local character = ensure_character(player) + if serialized.personal_logistic_slots then serialize.deserialize_personal_logistic_slots(player, serialized.personal_logistic_slots) + end + if serialized.inventories then serialize.deserialize_inventories(player, serialized.inventories, character_inventories) - local character = player.character - if serialized.ticks_to_respawn then - player.ticks_to_respawn = serialized.ticks_to_respawn - else - -- 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.controllers.god }) - player.set_controller({ type = defines.controllers.ghost }) - end - if character and character.valid then - character.destroy() - end + end + if serialized.ticks_to_respawn then + player.ticks_to_respawn = serialized.ticks_to_respawn + else + -- 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.controllers.god } + player.set_controller{ type = defines.controllers.ghost } + end + if character and character.valid then + character.destroy() + end + + elseif serialized.controller ~= "remote" then + -- All other controllers should not have a character + local character = player.character + if character and character.valid then + character.destroy() + end + if player.controller_type ~= target_controller then + player.set_controller{ type = target_controller } end else - -- Targeting the god or spectator controller, if coming from the - -- character controller then destroy the character - if player.controller_type == defines.controllers.character then - player.character.destroy() - end - - -- Switching to god or spectator is a matter of setting the controller - if player.controller_type ~= defines.controllers[serialized.controller] then - player.set_controller({ type = defines.controllers[serialized.controller] }) - end + error("Remote should not be 'serialized.controller'") end end