mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Create entries for players that are present in the save on server startup to preserving their inventory and fix attempting to reference the player object after it had been removed in on_player_removed.
175 lines
7.0 KiB
Lua
175 lines
7.0 KiB
Lua
--[[
|
|
|
|
When a player leaves the game, serialize their inventory and upload it to the master.
|
|
|
|
When a player joins the game, wait until they have a character, then send a request to
|
|
the master server for the players inventory.
|
|
|
|
When the master sends an inventory, parse it and give it to the player.
|
|
If the master responds that its a new player, show a welcome message.
|
|
|
|
]]
|
|
|
|
local clusterio_api = require("modules/clusterio/api")
|
|
local add_commands = require("modules/inventory_sync/commands")
|
|
local upload_inventory = require("modules/inventory_sync/upload_inventory")
|
|
local download_inventory = require("modules/inventory_sync/download_inventory")
|
|
local welcome_new_player = require("modules/inventory_sync/gui/welcome_new_player")
|
|
local handle_gui_events = require("modules/inventory_sync/gui/handle_gui_events")
|
|
local dialog_failed_download = require("modules/inventory_sync/gui/dialog_failed_download")
|
|
local clean_dirty_inventory = require("modules/inventory_sync/script/clean_dirty_inventory")
|
|
|
|
|
|
local function create_player(player, dirty)
|
|
global.inventory_sync.players[player.name] = {
|
|
dirty_inventory = dirty, -- Player has a temporary non-synced inventory that should be persisted
|
|
sync_start_tick = 0, -- To track download failure timeout
|
|
}
|
|
end
|
|
|
|
local function remove_player(player)
|
|
-- Remove inventory download performance counter
|
|
global.inventory_sync.download_start_tick[player.name] = nil
|
|
-- Remove stored crafting queue
|
|
global.inventory_sync.saved_crafting_queue[player.name] = nil
|
|
-- Remove other player data
|
|
global.inventory_sync.players[player.name] = nil
|
|
end
|
|
|
|
|
|
inventory_sync = {}
|
|
inventory_sync.welcome_new_player = welcome_new_player
|
|
-- This function is called by instance.js as /sc inventory_sync.download_inventory("danielv", Escaped JSON string, package_number, total_packages_count) with data from the master
|
|
inventory_sync.download_inventory = download_inventory
|
|
-- This function is called internally when a player leaves the game for their inventory to upload to the master
|
|
inventory_sync.upload_inventory = upload_inventory
|
|
|
|
inventory_sync.add_commands = add_commands
|
|
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.download_start_tick then
|
|
global.inventory_sync.download_start_tick = {}
|
|
end
|
|
if not global.inventory_sync.saved_crafting_queue then
|
|
global.inventory_sync.saved_crafting_queue = {}
|
|
end
|
|
if not global.inventory_sync.download_cache then
|
|
global.inventory_sync.download_cache = {}
|
|
end
|
|
if not global.inventory_sync.players_waiting_for_download then
|
|
global.inventory_sync.players_waiting_for_download = {}
|
|
end
|
|
if not global.inventory_sync.players then
|
|
global.inventory_sync.players = {}
|
|
end
|
|
|
|
for _, player in pairs(game.players) do
|
|
if not global.inventory_sync.players[player.name] then
|
|
create_player(player, true)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Cleanup
|
|
inventory_sync.events[defines.events.on_pre_player_removed] = function(event)
|
|
remove_player(game.get_player(event.player_index))
|
|
end
|
|
|
|
inventory_sync.events[defines.events.on_player_created] = function(event)
|
|
create_player(game.get_player(event.player_index), false)
|
|
end
|
|
|
|
inventory_sync.events[defines.events.on_gui_click] = handle_gui_events
|
|
|
|
-- Download inventory from master
|
|
inventory_sync.events[defines.events.on_player_joined_game] = function(event)
|
|
-- Add player to download queue. Inventory won't be downloaded before the player has a character
|
|
global.inventory_sync.players_waiting_for_download[game.get_player(event.player_index).name] = true
|
|
-- Don't wait for on_nth_tick if not required
|
|
inventory_sync.check_player_character_before_download()
|
|
end
|
|
|
|
inventory_sync.on_nth_tick = {}
|
|
inventory_sync.on_nth_tick[33] = function(event)
|
|
-- Periodically check if player has a character to download to. False in cutscenes.
|
|
inventory_sync.check_player_character_before_download()
|
|
|
|
-- Check if download has failed and we should create a dirty inventory
|
|
inventory_sync.check_inventory_download_failed()
|
|
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")
|
|
return
|
|
end
|
|
|
|
local player_index = event.player_index
|
|
local player = game.get_player(player_index)
|
|
-- Avoid uploading for unsynced players (due to not having a character, ex during freeplay cutscene)
|
|
if global.inventory_sync.players_waiting_for_download[player.name] == nil then
|
|
inventory_sync.upload_inventory(player_index)
|
|
global.inventory_sync.download_cache[player_index] = "" -- Clear cache to avoid leaking memory to people quitting during download
|
|
end
|
|
global.inventory_sync.players_waiting_for_download[player.name] = nil
|
|
end
|
|
|
|
function inventory_sync.check_player_character_before_download()
|
|
-- Check if players waiting for download have a valid character
|
|
for name in pairs(global.inventory_sync.players_waiting_for_download) do
|
|
local player = game.get_player(name)
|
|
if player.character ~= nil then
|
|
global.inventory_sync.players_waiting_for_download[name] = nil
|
|
inventory_sync.initiate_inventory_download(player)
|
|
end
|
|
end
|
|
end
|
|
|
|
function inventory_sync.initiate_inventory_download(player)
|
|
player.print("Initiating inventory download...")
|
|
global.inventory_sync.download_start_tick[player.name] = game.tick
|
|
global.inventory_sync.download_cache[player.name] = ""
|
|
clusterio_api.send_json("inventory_sync_download", {
|
|
player_name = player.name
|
|
})
|
|
|
|
-- Handle dirty inventories. A dirty inventory is a temporary inventory that has been used while waiting
|
|
-- for sync. It should be taken care of in some way, like putting it in a corpse or a chest.
|
|
if global.inventory_sync.players[player.name].dirty_inventory then
|
|
clean_dirty_inventory(player)
|
|
end
|
|
|
|
-- Set player into ghost mode for duration of download
|
|
local character = player.character
|
|
player.set_controller {
|
|
type = defines.controllers.ghost,
|
|
}
|
|
if character ~= nil then character.destroy() end
|
|
|
|
-- Start timeout for detecting master connection failure
|
|
global.inventory_sync.players[player.name].sync_start_tick = game.tick
|
|
end
|
|
function inventory_sync.check_inventory_download_failed()
|
|
-- Used for letting the master report the inventory download failing, like when the master is offline or an
|
|
-- error occurs somewhere in the server code. The player will be returned to a playable state.
|
|
for name, data in pairs(global.inventory_sync.players) do
|
|
if data.sync_start_tick ~= 0 and data.sync_start_tick <= game.tick - 600 then
|
|
-- Stop refreshing timer
|
|
data.sync_start_tick = 0
|
|
|
|
local player = game.get_player(name)
|
|
player.print("Inventory download failed due to master connection")
|
|
-- Show GUI with option to retry or abort download
|
|
dialog_failed_download(player)
|
|
end
|
|
end
|
|
end
|
|
|
|
return inventory_sync
|