mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 07:24:52 +09:00
Add 2.0 support to inventory_sync
This commit is contained in:
11 files changed
+188
-133
No files matched your search
@@ -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 inventory_sync = get_script_data(true) -- no early return to support loading from single player
|
||||
inventory_sync.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 inventory_sync = get_script_data()
|
||||
inventory_sync.players_waiting_for_acquire[player.name] = nil
|
||||
inventory_sync.players_in_cutscene_to_sync[player.name] = nil
|
||||
inventory_sync.active_downloads[player.name] = nil
|
||||
inventory_sync.finished_downloads[player.name] = nil
|
||||
inventory_sync.active_uploads[player.name] = nil
|
||||
inventory_sync.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 inventory_sync = get_script_data()
|
||||
local player_record = inventory_sync.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 inventory_sync = get_script_data(true)
|
||||
inventory_sync.players_waiting_for_acquire = {}
|
||||
inventory_sync.players_in_cutscene_to_sync = {}
|
||||
|
||||
for _, player in pairs(game.players) do
|
||||
if not global.inventory_sync.players[player.name] then
|
||||
if not inventory_sync.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 inventory_sync = get_script_data()
|
||||
inventory_sync.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 inventory_sync = get_script_data()
|
||||
for player_name, record in pairs(inventory_sync.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 inventory_sync = get_script_data()
|
||||
local response = assert(compat.json_to_table(data))
|
||||
if not inventory_sync.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
|
||||
inventory_sync.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
|
||||
inventory_sync.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 inventory_sync = get_script_data()
|
||||
for player_name, response in pairs(inventory_sync.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
|
||||
inventory_sync.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 inventory_sync = get_script_data()
|
||||
local finished_record = inventory_sync.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
|
||||
inventory_sync.finished_downloads[acquire_response.player_name] = nil
|
||||
end
|
||||
|
||||
local download_record = global.inventory_sync.active_downloads[acquire_response.player_name]
|
||||
local download_record = inventory_sync.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
|
||||
inventory_sync.active_downloads[acquire_response.player_name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local player_record = global.inventory_sync.players[acquire_response.player_name]
|
||||
local player_record = inventory_sync.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 inventory_sync = get_script_data()
|
||||
inventory_sync.finished_downloads[player.name] = nil
|
||||
|
||||
local player_record = global.inventory_sync.players[player.name]
|
||||
local player_record = inventory_sync.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 inventory_sync = 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 inventory_sync.players[player.name] then
|
||||
create_player(player, false)
|
||||
end
|
||||
|
||||
-- Send acquire request even if an active download is currently in plogress
|
||||
-- Send acquire request even if an active download is currently in progress
|
||||
inventory_sync.acquire(player, false)
|
||||
|
||||
-- Clear active upload if it existsi
|
||||
global.inventory_sync.active_uploads[player.name] = nil
|
||||
-- Clear active upload if it exists
|
||||
inventory_sync.active_uploads[player.name] = nil
|
||||
end
|
||||
|
||||
inventory_sync.on_nth_tick = {}
|
||||
@@ -391,16 +360,17 @@ 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
|
||||
if not compat.script_data().inventory_sync then
|
||||
log("ERROR: Global inventory sync not defined")
|
||||
return
|
||||
end
|
||||
|
||||
local player = game.get_player(event.player_index)
|
||||
local player_record = global.inventory_sync.players[player.name]
|
||||
local inventory_sync = get_script_data()
|
||||
local player = assert(game.get_player(event.player_index))
|
||||
local player_record = inventory_sync.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 inventory_sync.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] = {
|
||||
inventory_sync.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 inventory_sync = get_script_data()
|
||||
local player_record = inventory_sync.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
|
||||
inventory_sync.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 inventory_sync = get_script_data()
|
||||
inventory_sync.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 inventory_sync = get_script_data()
|
||||
for player_name, record in pairs(inventory_sync.active_downloads) do
|
||||
if record.last_active <= game.ticks_played - 600 then
|
||||
global.inventory_sync.active_downloads[player_name] = nil
|
||||
inventory_sync.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 inventory_sync = get_script_data()
|
||||
for player_name, record in pairs(inventory_sync.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 = inventory_sync.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
|
||||
inventory_sync.active_uploads[player_name] = nil
|
||||
else
|
||||
log("Retrying upload for " .. player_name)
|
||||
record.last_attempt = game.ticks_played
|
||||
|
||||
Reference in new issue
Block a user