Support for transferring crafting queues

This commit is contained in:
Daniel committed 2021-07-12 22:05:54 +02:00
1 parent 40301f59b7
commit 87218e4803
8 files changed
+339 -182

No files matched your search

@@ -0,0 +1,17 @@
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.
]]
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)
end
return add_commands
@@ -0,0 +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,
}
return inventories
@@ -0,0 +1,95 @@
local progress_dialog = require("modules/inventory_sync/progress_dialog")
local serialize = require("modules/clusterio/serialize")
local load_crafting_queue = require("modules/inventory_sync/load_crafting_queue")
local inventories = require("modules/inventory_sync/define_player_inventories")
function download_inventory(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
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
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
return download_inventory
+25 -182
View File
@@ -8,28 +8,38 @@ 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,
}
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")
inventory_sync = {}
inventory_sync.add_commands = add_commands
inventory_sync.events = {}
-- Cleanup
inventory_sync.events[defines.events.on_player_removed] = function(event)
local player = game.players[event.player_index]
-- Remove inventory download performance counter
global["inv_sync_download_start_tick "..player.name] = nil
-- Remove stored crafting queue
global["saved_crafting_queue_"..player.name] = nil
end
-- 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
-- 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.initiateInventoryDownload(playerIndex)
local player = game.players[playerIndex]
player.print("Initiating inventory download...")
@@ -50,176 +60,9 @@ function inventory_sync.initiateInventoryDownload(playerIndex)
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
-- This function is called by instance.js as /sc inventory_sync.downloadInventory("danielv", Escaped JSON string, package_number, total_packages_count) with data from the master
inventory_sync.downloadInventory = download_inventory
-- This function is called internally when a player leaves the game for their inventory to upload to the master
inventory_sync.uploadInventory = upload_inventory
return inventory_sync
@@ -0,0 +1,35 @@
-- Load crafting queue from a table
function load_crafting_queue(crafting_queue, player)
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 + 100
-- Load crafting queue
for _, queueItem in pairs(crafting_queue) do
local recipe = game.recipe_prototypes[queueItem.recipe]
game.print("Crafting "..queueItem.recipe)
-- Add items to inventory
for _, item in pairs(recipe.ingredients) do
local count = math.floor(item.amount * queueItem.count / math.max(recipe.main_product.amount, 1))
if count > 0 then
inventory.insert({
name = item.name,
count = count,
})
end
end
-- 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 - 100
end
return load_crafting_queue
@@ -0,0 +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.players[event.player_index]
local crafting_queue = global["saved_crafting_queue_"..player.name]
if crafting_queue == nil then
player.print("No saved crafting queue")
return
end
load_crafting_queue(crafting_queue, player)
global["saved_crafting_queue_"..player.name] = nil
end
end
return restore_crafts
@@ -0,0 +1,55 @@
function save_crafts(event)
if event.player_index then
local player = game.players[event.player_index]
local crafting_queue = {}
-- Avoid overwriting old save
if global["saved_crafting_queue_"..player.name] ~= nil then
player.print("You already have a saved crafting queue, restore it with /restore-crafts")
return
end
-- Serialize crafting queue
if player.crafting_queue == nil then
player.print("You can't save nothing, you silly goose")
return
end
for _, queueItem in pairs(player.crafting_queue) do
crafting_queue[queueItem.index] = {
count = queueItem.count,
recipe = queueItem.recipe,
}
end
-- Give player some more inventory space to avoid duplicating items
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus + 100
-- Cancel old crafts to get the items back
while player.crafting_queue_size > 0 do
local queueItem = player.crafting_queue[1]
local recipe = game.recipe_prototypes[queueItem.recipe]
-- Cancel craft
player.cancel_crafting {
index = 1,
count = 1,
}
-- Remove ingredients from inventory
for _, item in pairs(recipe.ingredients) do
local inventory = player.get_main_inventory()
inventory.remove({
name = item.name,
count = item.amount,
})
end
game.print("Canceled craft "..recipe.name)
end
-- Remove extra inventory slots
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus - 100
global["saved_crafting_queue_"..player.name] = crafting_queue
end
end
return save_crafts
@@ -0,0 +1,83 @@
local clusterio_api = require("modules/clusterio/api")
local serialize = require("modules/clusterio/serialize")
local inventories = require("modules/inventory_sync/define_player_inventories")
function upload_inventory(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,
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
-- Serialize crafting queue saved by /csc or /save-crafts
serialized_player.crafting_queue = global["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["saved_crafting_queue_"..player.name] = nil
end
return upload_inventory