mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 15:24:52 +09:00
Allow inventory sync components to be turned off per instance
Adds an inventory_sync.sync_<component> instance config field for the controller, force, appearance, inventories, logistics, quick bar, settings and recipe notifications. All default to on, which keeps the current behaviour. A component which is off on an instance is left alone there and its fields are not stored from that instance. The controller merges each upload with the stored data using the uploading instance's config, the instance strips the fields from downloads before sending them over RCON, and the scenario is told the enabled set on start and when it changes so it can skip serializing them and skip the steps which replace the character. The controller is only synced together with the inventories, as switching controller can destroy the character holding them. Without the controller synced the player gets back the controller they had on the instance, only character inventories move between instances, and a player without a character keeps their inventory. Closes #968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e810020db5
commit
0e559cf000
11 files changed
+571
-81
No files matched your search
@@ -720,16 +720,16 @@ end
|
||||
--- @class SerializedPlayerData
|
||||
--- @field generation number
|
||||
--- @field name string
|
||||
--- @field controller string
|
||||
--- @field color Color
|
||||
--- @field chat_color Color
|
||||
--- @field tag string
|
||||
--- @field force string
|
||||
--- @field cheat_mode boolean
|
||||
--- @field flashlight boolean
|
||||
--- @field controller string?
|
||||
--- @field color Color?
|
||||
--- @field chat_color Color?
|
||||
--- @field tag string?
|
||||
--- @field force string?
|
||||
--- @field cheat_mode boolean?
|
||||
--- @field flashlight boolean?
|
||||
--- @field shortcuts table<string, boolean>?
|
||||
--- @field game_view_settings table<string, boolean>?
|
||||
--- @field ticks_to_respawn number
|
||||
--- @field ticks_to_respawn number?
|
||||
--- @field character table<string, any>?
|
||||
--- @field inventories table<string, table>?
|
||||
--- @field hotbar table<string, string>?
|
||||
@@ -784,37 +784,71 @@ function serialize.deserialize_game_view_settings(player, serialized)
|
||||
end
|
||||
end
|
||||
|
||||
--- Components which are synced, nil when all of them are
|
||||
--- @alias SyncComponents table<string, boolean>?
|
||||
|
||||
--- @param components SyncComponents
|
||||
--- @param name string
|
||||
--- @return boolean
|
||||
local function syncs(components, name)
|
||||
return components == nil or components[name] == true
|
||||
end
|
||||
serialize.syncs = syncs
|
||||
|
||||
--- @param player LuaPlayer
|
||||
--- @param failed_deserialization FailedDeserializationPlayerData
|
||||
--- @param components SyncComponents
|
||||
--- @return SerializedPlayerData
|
||||
function serialize.serialize_player(player, failed_deserialization)
|
||||
function serialize.serialize_player(player, failed_deserialization, components)
|
||||
local sync_controller = syncs(components, "controller")
|
||||
local sync_inventories = syncs(components, "inventories")
|
||||
local sync_logistics = syncs(components, "logistics")
|
||||
|
||||
--- @type SerializedPlayerData
|
||||
local serialized = {
|
||||
generation = 0, -- Gets replaced later
|
||||
controller = controller_to_name[player.controller_type],
|
||||
name = player.name,
|
||||
color = player.color,
|
||||
chat_color = player.chat_color,
|
||||
tag = player.tag,
|
||||
force = player.force.name,
|
||||
cheat_mode = player.cheat_mode,
|
||||
flashlight = player.is_flashlight_enabled(),
|
||||
shortcuts = serialize.serialize_shortcuts(player),
|
||||
game_view_settings = serialize.serialize_game_view_settings(player),
|
||||
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]
|
||||
if sync_controller then
|
||||
serialized.controller = controller_to_name[player.controller_type]
|
||||
serialized.cheat_mode = player.cheat_mode
|
||||
serialized.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
|
||||
end
|
||||
|
||||
if syncs(components, "appearance") then
|
||||
serialized.color = player.color
|
||||
serialized.chat_color = player.chat_color
|
||||
serialized.tag = player.tag
|
||||
end
|
||||
|
||||
if syncs(components, "force") then
|
||||
serialized.force = player.force.name
|
||||
end
|
||||
|
||||
if syncs(components, "settings") then
|
||||
serialized.flashlight = player.is_flashlight_enabled()
|
||||
serialized.shortcuts = serialize.serialize_shortcuts(player)
|
||||
serialized.game_view_settings = serialize.serialize_game_view_settings(player)
|
||||
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
|
||||
if sync_controller and (sync_inventories or sync_logistics)
|
||||
and player.controller_type == defines.controllers.ghost and player.ticks_to_respawn
|
||||
then
|
||||
player.ticks_to_respawn = nil -- Respawn now
|
||||
|
||||
serialized.personal_logistic_slots = serialize.serialize_personal_logistic_slots(player)
|
||||
serialized.inventories = serialize.serialize_inventories(player, character_inventories)
|
||||
if sync_logistics then
|
||||
serialized.personal_logistic_slots = serialize.serialize_personal_logistic_slots(player)
|
||||
end
|
||||
if sync_inventories then
|
||||
serialized.inventories = serialize.serialize_inventories(player, character_inventories)
|
||||
end
|
||||
|
||||
-- Go back to waiting for respawn
|
||||
local character = player.character
|
||||
@@ -826,33 +860,55 @@ function serialize.serialize_player(player, failed_deserialization)
|
||||
|
||||
-- Serialize character
|
||||
if player.character then
|
||||
serialized.character = serialize.serialize_character(player.character)
|
||||
serialized.personal_logistic_slots = serialize.serialize_personal_logistic_slots(player)
|
||||
if sync_inventories then
|
||||
serialized.character = serialize.serialize_character(player.character)
|
||||
end
|
||||
if sync_logistics then
|
||||
serialized.personal_logistic_slots = serialize.serialize_personal_logistic_slots(player)
|
||||
end
|
||||
end
|
||||
|
||||
-- Serialize non-character inventories
|
||||
if player.controller_type == defines.controllers.god
|
||||
or v2_remote_controller and player.physical_controller_type == defines.controllers.god
|
||||
then
|
||||
if sync_controller and sync_inventories and (
|
||||
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
|
||||
|
||||
-- Serialize quick bar
|
||||
serialized.quick_bar = serialize.serialize_quick_bar(player)
|
||||
if syncs(components, "quick_bar") then
|
||||
serialized.quick_bar = serialize.serialize_quick_bar(player)
|
||||
end
|
||||
|
||||
-- Serialize crafting queue
|
||||
if player.character then
|
||||
if sync_inventories and player.character then
|
||||
serialized.crafting_queue = serialize.serialize_crafting_queue(player)
|
||||
end
|
||||
|
||||
-- Serialize recipe notifications
|
||||
if recipe_notifications_api then
|
||||
if recipe_notifications_api and syncs(components, "recipe_notifications") then
|
||||
serialized.recipe_notifications = serialize.serialize_crafting_notifications(player, failed_deserialization.recipe_notifications)
|
||||
end
|
||||
|
||||
return serialized
|
||||
end
|
||||
|
||||
--- The controller a player has on this instance, restored in place of the synced one when it is not synced
|
||||
--- @class LocalControllerState
|
||||
--- @field controller string?
|
||||
--- @field ticks_to_respawn number?
|
||||
|
||||
--- @param player LuaPlayer
|
||||
--- @return LocalControllerState
|
||||
function serialize.serialize_local_controller(player)
|
||||
local controller_type = v2_remote_controller and player.physical_controller_type or player.controller_type
|
||||
return {
|
||||
controller = controller_to_name[controller_type],
|
||||
ticks_to_respawn = player.ticks_to_respawn,
|
||||
}
|
||||
end
|
||||
|
||||
--- Find a surface characters can exist on, used when the player is on a space platform surface
|
||||
--- @param platform LuaSpacePlatform
|
||||
--- @return LuaSurface
|
||||
@@ -915,32 +971,46 @@ end
|
||||
|
||||
--- @param player LuaPlayer
|
||||
--- @param serialized SerializedPlayerData
|
||||
--- @param components SyncComponents
|
||||
--- @param local_controller LocalControllerState? Controller to restore when the controller is not synced
|
||||
--- @return FailedDeserializationPlayerData?
|
||||
function serialize.deserialize_player(player, serialized)
|
||||
function serialize.deserialize_player(player, serialized, components, local_controller)
|
||||
local failed_deserialization = {}
|
||||
local sync_controller = syncs(components, "controller")
|
||||
local sync_inventories = syncs(components, "inventories")
|
||||
local sync_logistics = syncs(components, "logistics")
|
||||
|
||||
local target_controller = defines.controllers[serialized.controller]
|
||||
if player.controller_type ~= target_controller or serialized.controller == "ghost" then
|
||||
if serialized.controller == "character" then
|
||||
-- Stored data from instances which do not sync a component lacks its fields
|
||||
local restore_controller = sync_controller and serialized.controller ~= nil
|
||||
|
||||
--- @type LocalControllerState?
|
||||
local state = local_controller
|
||||
if restore_controller then
|
||||
state = { controller = serialized.controller, ticks_to_respawn = serialized.ticks_to_respawn }
|
||||
end
|
||||
|
||||
local target_controller = state and defines.controllers[state.controller]
|
||||
if state and (player.controller_type ~= target_controller or state.controller == "ghost") then
|
||||
if state.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
|
||||
|
||||
elseif serialized.controller == "ghost" then
|
||||
elseif state.controller == "ghost" then
|
||||
-- Ghost state stores hidden logistic and filters which are only accessible in the character controller
|
||||
-- The force first, so the character and the logistic groups are on the restored player's force
|
||||
player.force = serialized.force
|
||||
local character = ensure_character(player)
|
||||
if serialized.personal_logistic_slots then
|
||||
if restore_controller and sync_logistics and serialized.personal_logistic_slots then
|
||||
serialize.deserialize_personal_logistic_slots(player, serialized.personal_logistic_slots)
|
||||
end
|
||||
if serialized.inventories then
|
||||
if restore_controller and sync_inventories and serialized.inventories then
|
||||
serialize.deserialize_inventories(player, serialized.inventories, character_inventories)
|
||||
end
|
||||
if serialized.ticks_to_respawn then
|
||||
player.ticks_to_respawn = serialized.ticks_to_respawn
|
||||
if state.ticks_to_respawn then
|
||||
player.ticks_to_respawn = state.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.
|
||||
@@ -952,7 +1022,7 @@ function serialize.deserialize_player(player, serialized)
|
||||
character.destroy()
|
||||
end
|
||||
|
||||
elseif serialized.controller ~= "remote" then
|
||||
elseif state.controller ~= "remote" then
|
||||
-- All other controllers should not have a character
|
||||
local character = player.character
|
||||
if character and character.valid then
|
||||
@@ -967,44 +1037,60 @@ function serialize.deserialize_player(player, serialized)
|
||||
end
|
||||
end
|
||||
|
||||
player.color = serialized.color
|
||||
player.chat_color = serialized.chat_color
|
||||
player.tag = serialized.tag
|
||||
player.force = serialized.force
|
||||
player.cheat_mode = serialized.cheat_mode
|
||||
if serialized.flashlight then
|
||||
player.enable_flashlight()
|
||||
else
|
||||
player.disable_flashlight()
|
||||
if syncs(components, "appearance") and serialized.color then
|
||||
player.color = serialized.color
|
||||
player.chat_color = serialized.chat_color
|
||||
player.tag = serialized.tag
|
||||
end
|
||||
if serialized.shortcuts then
|
||||
serialize.deserialize_shortcuts(player, serialized.shortcuts)
|
||||
if syncs(components, "force") and serialized.force then
|
||||
player.force = serialized.force
|
||||
end
|
||||
if serialized.game_view_settings then
|
||||
serialize.deserialize_game_view_settings(player, serialized.game_view_settings)
|
||||
if restore_controller and serialized.cheat_mode ~= nil then
|
||||
player.cheat_mode = serialized.cheat_mode
|
||||
end
|
||||
if syncs(components, "settings") then
|
||||
if serialized.flashlight == true then
|
||||
player.enable_flashlight()
|
||||
elseif serialized.flashlight == false then
|
||||
player.disable_flashlight()
|
||||
end
|
||||
if serialized.shortcuts then
|
||||
serialize.deserialize_shortcuts(player, serialized.shortcuts)
|
||||
end
|
||||
if serialized.game_view_settings then
|
||||
serialize.deserialize_game_view_settings(player, serialized.game_view_settings)
|
||||
end
|
||||
end
|
||||
|
||||
-- Deserialize character
|
||||
-- Deserialize character, the stored player may not have had one when the controller is not synced
|
||||
if player.character then
|
||||
serialize.deserialize_character(player.character, serialized.character)
|
||||
serialize.deserialize_personal_logistic_slots(player, serialized.personal_logistic_slots)
|
||||
if sync_inventories and serialized.character then
|
||||
serialize.deserialize_character(player.character, serialized.character)
|
||||
end
|
||||
if sync_logistics then
|
||||
serialize.deserialize_personal_logistic_slots(player, serialized.personal_logistic_slots)
|
||||
end
|
||||
end
|
||||
|
||||
-- Deserialize non-character inventories
|
||||
if player.controller_type == defines.controllers.god then
|
||||
if restore_controller and sync_inventories and serialized.inventories
|
||||
and player.controller_type == defines.controllers.god
|
||||
then
|
||||
serialize.deserialize_inventories(player, serialized.inventories, { main = defines.inventory.god_main })
|
||||
end
|
||||
|
||||
-- Deserialize quick bar (named hotbar in old data)
|
||||
serialize.deserialize_quick_bar(player, serialized.quick_bar or serialized.hotbar)
|
||||
if syncs(components, "quick_bar") then
|
||||
serialize.deserialize_quick_bar(player, serialized.quick_bar or serialized.hotbar)
|
||||
end
|
||||
|
||||
-- Deserialize crafting queue
|
||||
if player.character and serialized.crafting_queue then
|
||||
if sync_inventories and player.character and serialized.crafting_queue then
|
||||
serialize.deserialize_crafting_queue(player, serialized.crafting_queue)
|
||||
end
|
||||
|
||||
-- Deserialize recipe notifications
|
||||
if recipe_notifications_api and serialized.recipe_notifications then
|
||||
if recipe_notifications_api and syncs(components, "recipe_notifications") and serialized.recipe_notifications then
|
||||
failed_deserialization.recipe_notifications =
|
||||
serialize.deserialize_crafting_notifications(player, serialized.recipe_notifications)
|
||||
end
|
||||
|
||||
Reference in new issue
Block a user