Files
factorio-clusterio/plugins/inventory_sync/module/inventory_sync.lua
T

226 lines
9.8 KiB
Lua

--[[
When a player leaves the game, serialize their inventory and upload it to the master.
When a player joins the game, 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.
]]
local serialize = require("modules/clusterio/serialize")
local clusterio_api = require("modules/clusterio/api")
local progress_dialog = require("modules/inventory_sync/progress_dialog")
local inventories = {
defines.inventory.character_main,
defines.inventory.character_guns,
defines.inventory.character_ammo,
defines.inventory.character_armor,
defines.inventory.character_vehicle,
defines.inventory.character_trash,
}
inventory_sync = {}
inventory_sync.events = {}
-- Download inventory from master
inventory_sync.events[defines.events.on_player_joined_game] = function(event)
local playerIndex = event.player_index
inventory_sync.initiateInventoryDownload(playerIndex)
end
function inventory_sync.initiateInventoryDownload(playerIndex)
local player = game.players[playerIndex]
player.print("Initiating inventory download...")
global["inv_sync_download_start_tick "..player.name] = game.tick
if global.download_cache == nil then
global.download_cache = {}
end
global.download_cache[playerIndex] = ""
clusterio_api.send_json("inventory_sync_download", {
player_name = player.name
})
-- 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
end
-- This function is called by instance.js as /sc inventory_sync.downloadInventory("danielv", Escaped JSON string) with data from the master
function inventory_sync.downloadInventory(playerName, data, number, total)
local player = game.players[playerName]
if player == nil then return end
if number then
if global.download_cache[playerName] == nil then
global.download_cache[playerName] = ""
end
-- Append data to download cache
global.download_cache[playerName] = global.download_cache[playerName] .. data
end
if number ~= total then
-- Show progress in console
-- game.players[playerName].print("Downloaded "..(number + 1).."/"..total.." parts")
-- Show progress in GUI
progress_dialog(player, number+1, total)
-- Request next segment
rcon.print("Segment downloaded")
else
-- Recreate player character
player.set_controller {
type = defines.controllers.god,
}
player.create_character()
-- Load downloaded inventory
local serialized_player = game.json_to_table(global.download_cache[playerName])
global.download_cache[playerName] = nil -- remove data to lower mapsize
local player = game.players[playerName]
-- Load inventories
for _, inv in pairs(inventories) do
local inventory = player.get_inventory(inv)
if inventory ~= nil and serialized_player.inventories[tostring(inv)] ~= nil then
serialize.deserialize_inventory(inventory, serialized_player.inventories[tostring(inv)])
end
end
-- Load personal logistics slots
for i = 1, 200 do
local slot = serialized_player.personal_logistic_slots[tostring(i)]
if slot ~= nil then
player.set_personal_logistic_slot(i, slot)
end
end
-- Load hotbar, don't overwrite empty slots
for i = 1, 100 do
-- if serialized_player.hotbar[i] ~= nil then
player.set_quick_bar_slot(i, game.item_prototypes[serialized_player.hotbar[i]])
-- end
end
-- Load crafting queue
for _, queueItem in pairs(serialized_player.crafting_queue) do
player.begin_crafting {
count = queueItem.count,
recipe = queueItem.recipe,
silent = true, -- Fail silently if items are missing
}
end
-- Misc
player.cheat_mode = serialized_player.cheat_mode
player.force = serialized_player.force -- Force by name as string
player.tag = serialized_player.tag
player.color = serialized_player.color
player.chat_color = serialized_player.chat_color
player.character_crafting_speed_modifier = serialized_player.character_crafting_speed_modifier
player.character_mining_speed_modifier = serialized_player.character_mining_speed_modifier
player.character_additional_mining_categories = serialized_player.character_additional_mining_categories
player.character_running_speed_modifier = serialized_player.character_running_speed_modifier
player.character_build_distance_bonus = serialized_player.character_build_distance_bonus
player.character_item_drop_distance_bonus = serialized_player.character_item_drop_distance_bonus
player.character_reach_distance_bonus = serialized_player.character_reach_distance_bonus
player.character_resource_reach_distance_bonus = serialized_player.character_resource_reach_distance_bonus
player.character_item_pickup_distance_bonus = serialized_player.character_item_pickup_distance_bonus
player.character_loot_pickup_distance_bonus = serialized_player.character_loot_pickup_distance_bonus
player.character_inventory_slots_bonus = serialized_player.character_inventory_slots_bonus
player.character_trash_slot_count_bonus = serialized_player.character_trash_slot_count_bonus
player.character_maximum_following_robot_count_bonus = serialized_player.character_maximum_following_robot_count_bonus
player.character_health_bonus = serialized_player.character_health_bonus
player.character_personal_logistic_requests_enabled = serialized_player.character_personal_logistic_requests_enabled
if serialized_player.flashlight then
player.enable_flashlight()
else
player.disable_flashlight()
end
local startTick = global["inv_sync_download_start_tick "..game.players[playerName].name]
game.print("Imported inventory for "..playerName.." in "..game.tick - startTick.." ticks")
end
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_player_left_game] = function(event)
local playerIndex = event.player_index
inventory_sync.uploadInventory(playerIndex)
global.download_cache[playerIndex] = "" -- Clear cache to avoid leaking memory to people quitting during download
end
function inventory_sync.uploadInventory(playerIndex)
local player = game.players[playerIndex]
local serialized_player = {
name = player.name,
inventories = {},
hotbar = {},
color = player.color,
chat_color = player.chat_color,
tag = player.tag,
-- admin = player.admin, -- This is handled by other parts of clusterio
personal_logistic_slots = {},
force = player.force.name,
cheat_mode = player.cheat_mode,
crafting_queue = {},
character_crafting_speed_modifier = player.character_crafting_speed_modifier,
character_mining_speed_modifier = player.character_mining_speed_modifier,
character_additional_mining_categories = player.character_additional_mining_categories,
character_running_speed_modifier = player.character_running_speed_modifier,
character_build_distance_bonus = player.character_build_distance_bonus,
character_item_drop_distance_bonus = player.character_item_drop_distance_bonus,
character_reach_distance_bonus = player.character_reach_distance_bonus,
character_resource_reach_distance_bonus = player.character_resource_reach_distance_bonus,
character_item_pickup_distance_bonus = player.character_item_pickup_distance_bonus,
character_loot_pickup_distance_bonus = player.character_loot_pickup_distance_bonus,
character_inventory_slots_bonus = player.character_inventory_slots_bonus,
character_trash_slot_count_bonus = player.character_trash_slot_count_bonus,
character_maximum_following_robot_count_bonus = player.character_maximum_following_robot_count_bonus,
character_health_bonus = player.character_health_bonus,
character_personal_logistic_requests_enabled = player.character_personal_logistic_requests_enabled,
flashlight = player.is_flashlight_enabled(),
}
-- Serialize hotbar
for i = 1, 100 do
serialized_player.hotbar[i] = {}
local slot = player.get_quick_bar_slot(i)
if slot ~= nil and slot.name ~= nil then
serialized_player.hotbar[i] = slot.name
end
end
-- Serialize personal logistics slots
for i = 1, 200 do -- Nobody will have more than 200 logistics slots, right?
local slot = player.get_personal_logistic_slot(i)
if slot.name ~= nil then
-- We leave [1] empty to force the JSON function to parse it as an object.
-- When its parsed as an array we get inconsistent key datatypes depending
-- on whether or not it is sparse.
serialized_player.personal_logistic_slots[i+1] = {
name = slot.name,
min = slot.min,
max = slot.max,
}
end
end
-- Serialize inventories
for _, inv in pairs(inventories) do
local inventory = player.get_inventory(inv)
if inventory ~= nil then
local serialized_inventory = serialize.serialize_inventory(inventory)
serialized_player.inventories[inv] = serialized_inventory
end
end
clusterio_api.send_json("inventory_sync_upload", serialized_player)
end
return inventory_sync