mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 07:04:53 +09:00
Replace spaces with tabs
This commit is contained in:
1 parent
e63f1246da
commit
2ffb848fe6
15 files changed
+473
-473
No files matched your search
@@ -2,16 +2,16 @@ local save_crafts = require("modules/inventory_sync/save_crafts")
|
||||
local restore_crafts = require("modules/inventory_sync/restore_crafts")
|
||||
|
||||
function add_commands()
|
||||
--[[
|
||||
Command to serialize crafting queue for transport.
|
||||
When serialized, the crafting queue is cleared and stored in global. The items received are cleared as well.
|
||||
Upon leaving, the value in global is cleared and sent with the inventory to the master.
|
||||
Upon joining a game the crafting queue is added to the end of the existing crafting queue.
|
||||
]]
|
||||
--[[
|
||||
Command to serialize crafting queue for transport.
|
||||
When serialized, the crafting queue is cleared and stored in global. The items received are cleared as well.
|
||||
Upon leaving, the value in global is cleared and sent with the inventory to the master.
|
||||
Upon joining a game the crafting queue is added to the end of the existing crafting queue.
|
||||
]]
|
||||
commands.add_command("save-crafts", "Save crafting queue for transport to a different server", save_crafts)
|
||||
commands.add_command("csc", "Shorthand for /save-crafts", save_crafts)
|
||||
commands.add_command("restore-crafts", "Load crafting queue from global", restore_crafts)
|
||||
commands.add_command("crc", "Shorthand for /restore-crafts", restore_crafts)
|
||||
commands.add_command("csc", "Shorthand for /save-crafts", save_crafts)
|
||||
commands.add_command("restore-crafts", "Load crafting queue from global", restore_crafts)
|
||||
commands.add_command("crc", "Shorthand for /restore-crafts", restore_crafts)
|
||||
end
|
||||
|
||||
return add_commands
|
||||
@@ -1,10 +1,10 @@
|
||||
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,
|
||||
defines.inventory.character_main,
|
||||
defines.inventory.character_guns,
|
||||
defines.inventory.character_ammo,
|
||||
defines.inventory.character_armor,
|
||||
defines.inventory.character_vehicle,
|
||||
defines.inventory.character_trash,
|
||||
}
|
||||
|
||||
return inventories
|
||||
@@ -1,23 +1,23 @@
|
||||
return {
|
||||
"character_crafting_speed_modifier",
|
||||
"character_mining_speed_modifier",
|
||||
"character_additional_mining_categories",
|
||||
"character_running_speed_modifier",
|
||||
"character_build_distance_bonus",
|
||||
"character_item_drop_distance_bonus",
|
||||
"character_reach_distance_bonus",
|
||||
"character_resource_reach_distance_bonus",
|
||||
"character_item_pickup_distance_bonus",
|
||||
"character_loot_pickup_distance_bonus",
|
||||
"character_inventory_slots_bonus",
|
||||
"character_trash_slot_count_bonus",
|
||||
"character_maximum_following_robot_count_bonus",
|
||||
"character_health_bonus",
|
||||
"character_personal_logistic_requests_enabled",
|
||||
"build_distance",
|
||||
"drop_item_distance",
|
||||
"reach_distance",
|
||||
"item_pickup_distance",
|
||||
"loot_pickup_distance",
|
||||
"resource_reach_distance",
|
||||
"character_crafting_speed_modifier",
|
||||
"character_mining_speed_modifier",
|
||||
"character_additional_mining_categories",
|
||||
"character_running_speed_modifier",
|
||||
"character_build_distance_bonus",
|
||||
"character_item_drop_distance_bonus",
|
||||
"character_reach_distance_bonus",
|
||||
"character_resource_reach_distance_bonus",
|
||||
"character_item_pickup_distance_bonus",
|
||||
"character_loot_pickup_distance_bonus",
|
||||
"character_inventory_slots_bonus",
|
||||
"character_trash_slot_count_bonus",
|
||||
"character_maximum_following_robot_count_bonus",
|
||||
"character_health_bonus",
|
||||
"character_personal_logistic_requests_enabled",
|
||||
"build_distance",
|
||||
"drop_item_distance",
|
||||
"reach_distance",
|
||||
"item_pickup_distance",
|
||||
"loot_pickup_distance",
|
||||
"resource_reach_distance",
|
||||
}
|
||||
@@ -6,102 +6,102 @@ local ensure_character = require("modules/inventory_sync/ensure_character")
|
||||
local player_stat_keys = require("modules/inventory_sync/define_player_stat_keys")
|
||||
|
||||
function download_inventory(player_name, data, number, total)
|
||||
local player = game.get_player(player_name)
|
||||
if player == nil then
|
||||
log("Player not found! "..player_name)
|
||||
return
|
||||
end
|
||||
if number then
|
||||
if global.inventory_sync.download_cache[player_name] == nil then
|
||||
global.inventory_sync.download_cache[player_name] = ""
|
||||
end
|
||||
-- Append data to download cache
|
||||
global.inventory_sync.download_cache[player_name] = global.inventory_sync.download_cache[player_name] .. data
|
||||
end
|
||||
local player = game.get_player(player_name)
|
||||
if player == nil then
|
||||
log("Player not found! "..player_name)
|
||||
return
|
||||
end
|
||||
if number then
|
||||
if global.inventory_sync.download_cache[player_name] == nil then
|
||||
global.inventory_sync.download_cache[player_name] = ""
|
||||
end
|
||||
-- Append data to download cache
|
||||
global.inventory_sync.download_cache[player_name] = global.inventory_sync.download_cache[player_name] .. data
|
||||
end
|
||||
|
||||
if number ~= total then
|
||||
-- Show progress in console
|
||||
-- player.print("Downloaded "..(number + 1).."/"..total.." parts")
|
||||
-- Show progress in GUI
|
||||
progress_dialog(player, number+1, total)
|
||||
if number ~= total then
|
||||
-- Show progress in console
|
||||
-- player.print("Downloaded "..(number + 1).."/"..total.." parts")
|
||||
-- Show progress in GUI
|
||||
progress_dialog(player, number+1, total)
|
||||
|
||||
-- Request next segment
|
||||
rcon.print("Segment downloaded")
|
||||
-- Request next segment
|
||||
rcon.print("Segment downloaded")
|
||||
|
||||
-- Reset sync_start_tick to indicate master connection
|
||||
global.inventory_sync.players[player_name].sync_start_tick = game.tick
|
||||
else
|
||||
-- Stop tracking master connection status, sync is complete
|
||||
global.inventory_sync.players[player_name].sync_start_tick = 0
|
||||
-- Reset sync_start_tick to indicate master connection
|
||||
global.inventory_sync.players[player_name].sync_start_tick = game.tick
|
||||
else
|
||||
-- Stop tracking master connection status, sync is complete
|
||||
global.inventory_sync.players[player_name].sync_start_tick = 0
|
||||
|
||||
-- Handle dirty inventory
|
||||
if global.inventory_sync.players[player_name].dirty_inventory then
|
||||
clean_dirty_inventory(player)
|
||||
end
|
||||
-- Handle dirty inventory
|
||||
if global.inventory_sync.players[player_name].dirty_inventory then
|
||||
clean_dirty_inventory(player)
|
||||
end
|
||||
|
||||
-- Recreate player character
|
||||
ensure_character(player)
|
||||
-- Recreate player character
|
||||
ensure_character(player)
|
||||
|
||||
-- Remove freeplay skip cutscene label
|
||||
if player.gui.screen.skip_cutscene_label then
|
||||
player.gui.screen.skip_cutscene_label.destroy()
|
||||
end
|
||||
-- Remove freeplay skip cutscene label
|
||||
if player.gui.screen.skip_cutscene_label then
|
||||
player.gui.screen.skip_cutscene_label.destroy()
|
||||
end
|
||||
|
||||
local character = player.character or player.cutscene_character
|
||||
local character = player.character or player.cutscene_character
|
||||
|
||||
-- Load downloaded inventory
|
||||
local serialized_player = game.json_to_table(global.inventory_sync.download_cache[player_name])
|
||||
global.inventory_sync.download_cache[player_name] = nil -- remove data to lower mapsize
|
||||
-- Load downloaded inventory
|
||||
local serialized_player = game.json_to_table(global.inventory_sync.download_cache[player_name])
|
||||
global.inventory_sync.download_cache[player_name] = nil -- remove data to lower mapsize
|
||||
|
||||
-- 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 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+1)] -- 1 is empty to force array to be spare
|
||||
if slot ~= nil then
|
||||
player.set_personal_logistic_slot(i, slot)
|
||||
end
|
||||
end
|
||||
-- Load personal logistics slots
|
||||
for i = 1, 200 do
|
||||
local slot = serialized_player.personal_logistic_slots[tostring(i+1)] -- 1 is empty to force array to be spare
|
||||
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 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
|
||||
load_crafting_queue(serialized_player.crafting_queue, player)
|
||||
-- Load crafting queue
|
||||
load_crafting_queue(serialized_player.crafting_queue, player)
|
||||
|
||||
-- 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
|
||||
-- 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 stats
|
||||
for _,v in pairs(player_stat_keys) do
|
||||
character[v] = serialized_player[v]
|
||||
end
|
||||
-- Player stats
|
||||
for _,v in pairs(player_stat_keys) do
|
||||
character[v] = serialized_player[v]
|
||||
end
|
||||
|
||||
if serialized_player.flashlight then
|
||||
player.enable_flashlight()
|
||||
else
|
||||
player.disable_flashlight()
|
||||
end
|
||||
if serialized_player.flashlight then
|
||||
player.enable_flashlight()
|
||||
else
|
||||
player.disable_flashlight()
|
||||
end
|
||||
|
||||
local startTick = global.inventory_sync.download_start_tick[player.name]
|
||||
local log_line = "Imported inventory for "..player_name.." in "..game.tick - startTick.." ticks"
|
||||
player.print(log_line)
|
||||
log("[inventory_sync] "..log_line)
|
||||
end
|
||||
local startTick = global.inventory_sync.download_start_tick[player.name]
|
||||
local log_line = "Imported inventory for "..player_name.." in "..game.tick - startTick.." ticks"
|
||||
player.print(log_line)
|
||||
log("[inventory_sync] "..log_line)
|
||||
end
|
||||
end
|
||||
|
||||
return download_inventory
|
||||
@@ -1,10 +1,10 @@
|
||||
local function ensure_character(player)
|
||||
if player.character == nil then
|
||||
player.set_controller {
|
||||
type = defines.controllers.god,
|
||||
}
|
||||
player.create_character()
|
||||
end
|
||||
if player.character == nil then
|
||||
player.set_controller {
|
||||
type = defines.controllers.god,
|
||||
}
|
||||
player.create_character()
|
||||
end
|
||||
end
|
||||
|
||||
return ensure_character
|
||||
@@ -1,31 +1,31 @@
|
||||
-- Show inventory download progressbar
|
||||
function dialog_failed_download(player)
|
||||
if player.gui.screen.dialog_failed_download then
|
||||
player.gui.screen.dialog_failed_download.destroy()
|
||||
end
|
||||
if player.gui.screen.dialog_failed_download then
|
||||
player.gui.screen.dialog_failed_download.destroy()
|
||||
end
|
||||
|
||||
local frame = player.gui.screen.add {
|
||||
name = "dialog_failed_download",
|
||||
type = "frame",
|
||||
direction = "vertical",
|
||||
}
|
||||
local frame = player.gui.screen.add {
|
||||
name = "dialog_failed_download",
|
||||
type = "frame",
|
||||
direction = "vertical",
|
||||
}
|
||||
|
||||
local titlebar = frame.add {
|
||||
type = "flow",
|
||||
}
|
||||
titlebar.drag_target = frame
|
||||
local titlebar = frame.add {
|
||||
type = "flow",
|
||||
}
|
||||
titlebar.drag_target = frame
|
||||
|
||||
local title = titlebar.add {
|
||||
type = "label",
|
||||
style = "frame_title",
|
||||
caption = "Inventory sync download failed",
|
||||
}
|
||||
title.drag_target = frame
|
||||
local title = titlebar.add {
|
||||
type = "label",
|
||||
style = "frame_title",
|
||||
caption = "Inventory sync download failed",
|
||||
}
|
||||
title.drag_target = frame
|
||||
|
||||
local filler = titlebar.add {
|
||||
type = "empty-widget",
|
||||
style = "draggable_space_header",
|
||||
}
|
||||
local filler = titlebar.add {
|
||||
type = "empty-widget",
|
||||
style = "draggable_space_header",
|
||||
}
|
||||
filler.style.horizontally_stretchable = "on"
|
||||
filler.style.right_margin = 4
|
||||
filler.style.height = 24
|
||||
@@ -46,15 +46,15 @@ function dialog_failed_download(player)
|
||||
p1.style.single_line = false
|
||||
p1.style.bottom_margin = 8
|
||||
|
||||
local abort = content.add {
|
||||
name = "inventory_sync_failed_download_abort",
|
||||
type = "button",
|
||||
caption = "Continue with new inventory",
|
||||
tooltip = "Create a new empty temporary inventory. Items you pick up will be placed in a chest when the sync completes.",
|
||||
}
|
||||
local abort = content.add {
|
||||
name = "inventory_sync_failed_download_abort",
|
||||
type = "button",
|
||||
caption = "Continue with new inventory",
|
||||
tooltip = "Create a new empty temporary inventory. Items you pick up will be placed in a chest when the sync completes.",
|
||||
}
|
||||
|
||||
frame.force_auto_center()
|
||||
player.opened = frame
|
||||
frame.force_auto_center()
|
||||
player.opened = frame
|
||||
end
|
||||
|
||||
return dialog_failed_download
|
||||
@@ -2,22 +2,22 @@ local ensure_character = require("modules/inventory_sync/ensure_character")
|
||||
|
||||
-- on_gui_click
|
||||
local handle_gui_events = function(event)
|
||||
local player = game.get_player(event.player_index)
|
||||
local element = event.element
|
||||
if element.name == "inventory_sync_failed_download_abort" then
|
||||
-- Give the player a new inventory and mark it as dirty
|
||||
-- Show dirty status and an explanation in a GUI
|
||||
local player = game.get_player(event.player_index)
|
||||
local element = event.element
|
||||
if element.name == "inventory_sync_failed_download_abort" then
|
||||
-- Give the player a new inventory and mark it as dirty
|
||||
-- Show dirty status and an explanation in a GUI
|
||||
|
||||
-- Remove GUI element
|
||||
player.gui.screen.dialog_failed_download.destroy()
|
||||
-- Handle inventory dirtying
|
||||
if not global.inventory_sync.players[player.name].dirty_inventory then
|
||||
global.inventory_sync.players[player.name].dirty_inventory = true
|
||||
|
||||
-- Restore player character
|
||||
ensure_character(player)
|
||||
end
|
||||
end
|
||||
-- Remove GUI element
|
||||
player.gui.screen.dialog_failed_download.destroy()
|
||||
-- Handle inventory dirtying
|
||||
if not global.inventory_sync.players[player.name].dirty_inventory then
|
||||
global.inventory_sync.players[player.name].dirty_inventory = true
|
||||
|
||||
-- Restore player character
|
||||
ensure_character(player)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return handle_gui_events
|
||||
@@ -1,34 +1,34 @@
|
||||
-- Show inventory download progressbar
|
||||
function open_dialog(player, progress, total)
|
||||
if player.gui.screen.inventory_sync_progress then
|
||||
player.gui.screen.inventory_sync_progress.destroy()
|
||||
end
|
||||
if progress == total then
|
||||
return -- Hide dialog on completed download
|
||||
end
|
||||
if player.gui.screen.inventory_sync_progress then
|
||||
player.gui.screen.inventory_sync_progress.destroy()
|
||||
end
|
||||
if progress == total then
|
||||
return -- Hide dialog on completed download
|
||||
end
|
||||
|
||||
local frame = player.gui.screen.add {
|
||||
name = "inventory_sync_progress",
|
||||
type = "frame",
|
||||
direction = "vertical",
|
||||
}
|
||||
local frame = player.gui.screen.add {
|
||||
name = "inventory_sync_progress",
|
||||
type = "frame",
|
||||
direction = "vertical",
|
||||
}
|
||||
|
||||
local titlebar = frame.add {
|
||||
type = "flow",
|
||||
}
|
||||
titlebar.drag_target = frame
|
||||
local titlebar = frame.add {
|
||||
type = "flow",
|
||||
}
|
||||
titlebar.drag_target = frame
|
||||
|
||||
local title = titlebar.add {
|
||||
type = "label",
|
||||
style = "frame_title",
|
||||
caption = "Inventory sync",
|
||||
}
|
||||
title.drag_target = frame
|
||||
local title = titlebar.add {
|
||||
type = "label",
|
||||
style = "frame_title",
|
||||
caption = "Inventory sync",
|
||||
}
|
||||
title.drag_target = frame
|
||||
|
||||
local filler = titlebar.add {
|
||||
type = "empty-widget",
|
||||
style = "draggable_space_header",
|
||||
}
|
||||
local filler = titlebar.add {
|
||||
type = "empty-widget",
|
||||
style = "draggable_space_header",
|
||||
}
|
||||
filler.style.horizontally_stretchable = "on"
|
||||
filler.style.right_margin = 4
|
||||
filler.style.height = 24
|
||||
@@ -58,14 +58,14 @@ function open_dialog(player, progress, total)
|
||||
p1.style.single_line = false
|
||||
p1.style.bottom_margin = 8
|
||||
|
||||
local progressbar = content.add {
|
||||
type = "progressbar",
|
||||
value = progress / total,
|
||||
}
|
||||
progressbar.style.width = 376 -- inside_shallow_frame_with_padding has 12px padding on each side
|
||||
local progressbar = content.add {
|
||||
type = "progressbar",
|
||||
value = progress / total,
|
||||
}
|
||||
progressbar.style.width = 376 -- inside_shallow_frame_with_padding has 12px padding on each side
|
||||
|
||||
frame.force_auto_center()
|
||||
player.opened = frame
|
||||
frame.force_auto_center()
|
||||
player.opened = frame
|
||||
end
|
||||
|
||||
return open_dialog
|
||||
@@ -1,9 +1,9 @@
|
||||
local function welcome_new_player(player_name)
|
||||
local player = game.get_player(player_name)
|
||||
if player == nil then return end
|
||||
player.print("Welcome to the server!")
|
||||
player.print("Your inventory will be automatically synchronized between servers in this cluster.")
|
||||
player.print("To improve sync performance, please avoid storing blueprints in your inventory.")
|
||||
local player = game.get_player(player_name)
|
||||
if player == nil then return end
|
||||
player.print("Welcome to the server!")
|
||||
player.print("Your inventory will be automatically synchronized between servers in this cluster.")
|
||||
player.print("To improve sync performance, please avoid storing blueprints in your inventory.")
|
||||
end
|
||||
|
||||
return welcome_new_player
|
||||
@@ -29,124 +29,124 @@ 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 = {
|
||||
download_start_tick = {},
|
||||
saved_crafting_queue = {},
|
||||
download_cache = {},
|
||||
players_waiting_for_download = {},
|
||||
players = {},
|
||||
}
|
||||
end
|
||||
-- set up global table
|
||||
if not global.inventory_sync then
|
||||
global.inventory_sync = {
|
||||
download_start_tick = {},
|
||||
saved_crafting_queue = {},
|
||||
download_cache = {},
|
||||
players_waiting_for_download = {},
|
||||
players = {},
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Cleanup
|
||||
inventory_sync.events[defines.events.on_player_removed] = function(event)
|
||||
local player = game.get_player(event.player_index)
|
||||
local player = game.get_player(event.player_index)
|
||||
|
||||
-- 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
|
||||
-- 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.events[defines.events.on_player_created] = function(event)
|
||||
local player = game.get_player(event.player_index)
|
||||
global.inventory_sync.players[player.name] = {
|
||||
dirty_inventory = false, -- Player has a temporary non-synced inventory that should be persisted
|
||||
sync_start_tick = 0, -- To track download failure timeout
|
||||
}
|
||||
local player = game.get_player(event.player_index)
|
||||
global.inventory_sync.players[player.name] = {
|
||||
dirty_inventory = false, -- Player has a temporary non-synced inventory that should be persisted
|
||||
sync_start_tick = 0, -- To track download failure timeout
|
||||
}
|
||||
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()
|
||||
-- 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()
|
||||
-- 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()
|
||||
-- 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
|
||||
-- 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
|
||||
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
|
||||
-- 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
|
||||
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
|
||||
})
|
||||
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
|
||||
-- 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
|
||||
-- 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
|
||||
-- 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
|
||||
-- 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
|
||||
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
|
||||
@@ -1,27 +1,27 @@
|
||||
-- Load crafting queue from a table
|
||||
function load_crafting_queue(crafting_queue, player)
|
||||
local inventory = player.get_main_inventory()
|
||||
local inventory = player.get_main_inventory()
|
||||
|
||||
-- Give player some more inventory space to avoid duplicating items
|
||||
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus + 1000
|
||||
-- Give player some more inventory space to avoid duplicating items
|
||||
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus + 1000
|
||||
|
||||
-- Add items to inventory
|
||||
for _, item in pairs(crafting_queue.ingredients) do
|
||||
inventory.insert(item)
|
||||
end
|
||||
-- Add items to inventory
|
||||
for _, item in pairs(crafting_queue.ingredients) do
|
||||
inventory.insert(item)
|
||||
end
|
||||
|
||||
-- Load crafting queue
|
||||
for _, queueItem in pairs(crafting_queue.crafting_queue) do
|
||||
-- Start crafting (consume items)
|
||||
player.begin_crafting {
|
||||
count = queueItem.count,
|
||||
recipe = queueItem.recipe,
|
||||
-- silent = true, -- Fail silently if items are missing
|
||||
}
|
||||
end
|
||||
-- Load crafting queue
|
||||
for _, queueItem in pairs(crafting_queue.crafting_queue) do
|
||||
-- Start crafting (consume items)
|
||||
player.begin_crafting {
|
||||
count = queueItem.count,
|
||||
recipe = queueItem.recipe,
|
||||
-- silent = true, -- Fail silently if items are missing
|
||||
}
|
||||
end
|
||||
|
||||
-- Remove extra inventory slots
|
||||
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus - 1000
|
||||
-- Remove extra inventory slots
|
||||
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus - 1000
|
||||
end
|
||||
|
||||
return load_crafting_queue
|
||||
@@ -1,19 +1,19 @@
|
||||
local load_crafting_queue = require("modules/inventory_sync/load_crafting_queue")
|
||||
|
||||
function restore_crafts(event)
|
||||
if event.player_index then
|
||||
local player = game.get_player(event.player_index)
|
||||
local crafting_queue = global.inventory_sync.saved_crafting_queue[player.name]
|
||||
if event.player_index then
|
||||
local player = game.get_player(event.player_index)
|
||||
local crafting_queue = global.inventory_sync.saved_crafting_queue[player.name]
|
||||
|
||||
if crafting_queue == nil then
|
||||
player.print("No saved crafting queue")
|
||||
return
|
||||
end
|
||||
if crafting_queue == nil then
|
||||
player.print("No saved crafting queue")
|
||||
return
|
||||
end
|
||||
|
||||
load_crafting_queue(crafting_queue, player)
|
||||
load_crafting_queue(crafting_queue, player)
|
||||
|
||||
global.inventory_sync.saved_crafting_queue[player.name] = nil
|
||||
end
|
||||
global.inventory_sync.saved_crafting_queue[player.name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return restore_crafts
|
||||
@@ -1,96 +1,96 @@
|
||||
function save_crafts(event)
|
||||
if event.player_index then
|
||||
local player = game.get_player(event.player_index)
|
||||
local crafting_queue = {}
|
||||
if event.player_index then
|
||||
local player = game.get_player(event.player_index)
|
||||
local crafting_queue = {}
|
||||
|
||||
-- Avoid overwriting old save
|
||||
if global.inventory_sync.saved_crafting_queue[player.name] ~= nil then
|
||||
player.print("You already have a saved crafting queue, restore it with /restore-crafts")
|
||||
return
|
||||
end
|
||||
-- Avoid overwriting old save
|
||||
if global.inventory_sync.saved_crafting_queue[player.name] ~= nil then
|
||||
player.print("You already have a saved crafting queue, restore it with /restore-crafts")
|
||||
return
|
||||
end
|
||||
|
||||
-- Give player some more inventory space to avoid duplicating items
|
||||
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus + 1000
|
||||
-- Give player some more inventory space to avoid duplicating items
|
||||
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus + 1000
|
||||
|
||||
-- Save current items
|
||||
local inventory = player.get_main_inventory()
|
||||
local old_items = inventory.get_contents()
|
||||
-- Save current items
|
||||
local inventory = player.get_main_inventory()
|
||||
local old_items = inventory.get_contents()
|
||||
|
||||
-- Cancel old crafts to get the items back
|
||||
while player.crafting_queue_size > 0 do
|
||||
local old_queue = player.crafting_queue
|
||||
local queueItem = player.crafting_queue[1]
|
||||
|
||||
-- Cancel craft
|
||||
player.cancel_crafting {
|
||||
index = 1,
|
||||
count = 1,
|
||||
}
|
||||
-- Cancel old crafts to get the items back
|
||||
while player.crafting_queue_size > 0 do
|
||||
local old_queue = player.crafting_queue
|
||||
local queueItem = player.crafting_queue[1]
|
||||
|
||||
-- Cancel craft
|
||||
player.cancel_crafting {
|
||||
index = 1,
|
||||
count = 1,
|
||||
}
|
||||
|
||||
local rightmost_right_index = 0 -- 0 indexed since it is subtractive in a 1 indexed language
|
||||
local new_queue = player.crafting_queue
|
||||
while
|
||||
new_queue ~= nil and
|
||||
new_queue[#new_queue - rightmost_right_index] ~= nil and
|
||||
new_queue[#new_queue - rightmost_right_index].count >= old_queue[#old_queue - rightmost_right_index].count
|
||||
do
|
||||
rightmost_right_index = rightmost_right_index + 1
|
||||
end
|
||||
local oldItem = old_queue[#old_queue - rightmost_right_index]
|
||||
local newItem = nil
|
||||
if new_queue ~= nil then
|
||||
newItem = new_queue[#new_queue - rightmost_right_index]
|
||||
end
|
||||
local rightmost_right_index = 0 -- 0 indexed since it is subtractive in a 1 indexed language
|
||||
local new_queue = player.crafting_queue
|
||||
while
|
||||
new_queue ~= nil and
|
||||
new_queue[#new_queue - rightmost_right_index] ~= nil and
|
||||
new_queue[#new_queue - rightmost_right_index].count >= old_queue[#old_queue - rightmost_right_index].count
|
||||
do
|
||||
rightmost_right_index = rightmost_right_index + 1
|
||||
end
|
||||
local oldItem = old_queue[#old_queue - rightmost_right_index]
|
||||
local newItem = nil
|
||||
if new_queue ~= nil then
|
||||
newItem = new_queue[#new_queue - rightmost_right_index]
|
||||
end
|
||||
|
||||
-- Figure out how many items to add to queue
|
||||
local added = oldItem.count
|
||||
if newItem ~= nil then
|
||||
added = oldItem.count - newItem.count
|
||||
if oldItem.recipe ~= newItem.recipe then
|
||||
log("ERROR: Old item "..oldItem.recipe.." is not equal "..newItem.recipe)
|
||||
end
|
||||
end
|
||||
-- Figure out how many items to add to queue
|
||||
local added = oldItem.count
|
||||
if newItem ~= nil then
|
||||
added = oldItem.count - newItem.count
|
||||
if oldItem.recipe ~= newItem.recipe then
|
||||
log("ERROR: Old item "..oldItem.recipe.." is not equal "..newItem.recipe)
|
||||
end
|
||||
end
|
||||
|
||||
-- If the last item we added was of the same type, merge them in the queue
|
||||
if #crafting_queue > 0 and crafting_queue[#crafting_queue].recipe == oldItem.recipe then
|
||||
crafting_queue[#crafting_queue].count = crafting_queue[#crafting_queue].count + added
|
||||
else
|
||||
-- If the last item was of a different type, add a new item to the queue
|
||||
table.insert(crafting_queue, {
|
||||
recipe = oldItem.recipe,
|
||||
count = added,
|
||||
})
|
||||
end
|
||||
-- game.print("Saved craft "..oldItem.recipe)
|
||||
end
|
||||
|
||||
-- Find amount of items added and remove from inventory
|
||||
local new_items = inventory.get_contents()
|
||||
local difference = {}
|
||||
for k,v in pairs(new_items) do
|
||||
local old_count = old_items[k]
|
||||
local diff = v
|
||||
if old_count ~= nil then
|
||||
diff = diff - old_count
|
||||
end
|
||||
if diff > 0 then
|
||||
local ingredient = {
|
||||
name = k,
|
||||
count = diff,
|
||||
}
|
||||
inventory.remove(ingredient)
|
||||
table.insert(difference, ingredient)
|
||||
end
|
||||
end
|
||||
-- If the last item we added was of the same type, merge them in the queue
|
||||
if #crafting_queue > 0 and crafting_queue[#crafting_queue].recipe == oldItem.recipe then
|
||||
crafting_queue[#crafting_queue].count = crafting_queue[#crafting_queue].count + added
|
||||
else
|
||||
-- If the last item was of a different type, add a new item to the queue
|
||||
table.insert(crafting_queue, {
|
||||
recipe = oldItem.recipe,
|
||||
count = added,
|
||||
})
|
||||
end
|
||||
-- game.print("Saved craft "..oldItem.recipe)
|
||||
end
|
||||
|
||||
-- Find amount of items added and remove from inventory
|
||||
local new_items = inventory.get_contents()
|
||||
local difference = {}
|
||||
for k,v in pairs(new_items) do
|
||||
local old_count = old_items[k]
|
||||
local diff = v
|
||||
if old_count ~= nil then
|
||||
diff = diff - old_count
|
||||
end
|
||||
if diff > 0 then
|
||||
local ingredient = {
|
||||
name = k,
|
||||
count = diff,
|
||||
}
|
||||
inventory.remove(ingredient)
|
||||
table.insert(difference, ingredient)
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove extra inventory slots
|
||||
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus - 1000
|
||||
-- Remove extra inventory slots
|
||||
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus - 1000
|
||||
|
||||
global.inventory_sync.saved_crafting_queue[player.name] = {
|
||||
crafting_queue = crafting_queue,
|
||||
ingredients = difference,
|
||||
}
|
||||
end
|
||||
global.inventory_sync.saved_crafting_queue[player.name] = {
|
||||
crafting_queue = crafting_queue,
|
||||
ingredients = difference,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return save_crafts
|
||||
@@ -8,22 +8,22 @@ put them in ghost mode and mark the inventory as clean.
|
||||
]]
|
||||
|
||||
function clean_dirty_inventory(player)
|
||||
global.inventory_sync.players[player.name].dirty_inventory = false
|
||||
global.inventory_sync.players[player.name].dirty_inventory = false
|
||||
|
||||
local surface = player.surface
|
||||
local position = player.position
|
||||
local character = player.character
|
||||
player.disassociate_character(character)
|
||||
character.die()
|
||||
player.ticks_to_respawn = nil
|
||||
player.teleport(position, surface)
|
||||
local surface = player.surface
|
||||
local position = player.position
|
||||
local character = player.character
|
||||
player.disassociate_character(character)
|
||||
character.die()
|
||||
player.ticks_to_respawn = nil
|
||||
player.teleport(position, surface)
|
||||
|
||||
-- Remove player character
|
||||
local character = player.character
|
||||
player.set_controller {
|
||||
type = defines.controllers.ghost,
|
||||
}
|
||||
if character ~= nil then character.destroy() end
|
||||
-- Remove player character
|
||||
local character = player.character
|
||||
player.set_controller {
|
||||
type = defines.controllers.ghost,
|
||||
}
|
||||
if character ~= nil then character.destroy() end
|
||||
end
|
||||
|
||||
return clean_dirty_inventory
|
||||
@@ -5,77 +5,77 @@ local save_crafts = require("modules/inventory_sync/save_crafts")
|
||||
local player_stat_keys = require("modules/inventory_sync/define_player_stat_keys")
|
||||
|
||||
function upload_inventory(playerIndex)
|
||||
local player = game.get_player(playerIndex)
|
||||
local player = game.get_player(playerIndex)
|
||||
|
||||
if player.character == nil then
|
||||
-- Panic!
|
||||
log("ERROR: Player "..player.name.." quit without a character. Inventory not uploaded")
|
||||
return false
|
||||
end
|
||||
if player.character == nil then
|
||||
-- Panic!
|
||||
log("ERROR: Player "..player.name.." quit without a character. Inventory not uploaded")
|
||||
return false
|
||||
end
|
||||
|
||||
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,
|
||||
flashlight = player.is_flashlight_enabled(),
|
||||
}
|
||||
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,
|
||||
flashlight = player.is_flashlight_enabled(),
|
||||
}
|
||||
|
||||
-- Transfer player stats
|
||||
for _,v in pairs(player_stat_keys) do
|
||||
serialized_player[v] = player[v]
|
||||
end
|
||||
-- Transfer player stats
|
||||
for _,v in pairs(player_stat_keys) do
|
||||
serialized_player[v] = player[v]
|
||||
end
|
||||
|
||||
-- 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 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 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
|
||||
-- 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
|
||||
|
||||
-- Serialize crafting queue saved by /csc or /save-crafts OR automatically
|
||||
save_crafts({player_index = player.name})
|
||||
serialized_player.crafting_queue = global.inventory_sync.saved_crafting_queue[player.name]
|
||||
if serialized_player.crafting_queue == nil then
|
||||
serialized_player.crafting_queue = {}
|
||||
end
|
||||
-- Serialize crafting queue saved by /csc or /save-crafts OR automatically
|
||||
save_crafts({player_index = player.name})
|
||||
serialized_player.crafting_queue = global.inventory_sync.saved_crafting_queue[player.name]
|
||||
if serialized_player.crafting_queue == nil then
|
||||
serialized_player.crafting_queue = {}
|
||||
end
|
||||
|
||||
clusterio_api.send_json("inventory_sync_upload", serialized_player)
|
||||
|
||||
-- Clear saved crafting queue
|
||||
global.inventory_sync.saved_crafting_queue[player.name] = nil
|
||||
clusterio_api.send_json("inventory_sync_upload", serialized_player)
|
||||
|
||||
-- Clear saved crafting queue
|
||||
global.inventory_sync.saved_crafting_queue[player.name] = nil
|
||||
end
|
||||
|
||||
return upload_inventory
|
||||
Reference in new issue
Block a user