diff --git a/README.md b/README.md index 272c188b..22bb69ce 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ These are the plugins supported and maintained by the Clusterio developers: - [Statistics Exporter](/plugins/statistics_exporter/README.md): collect in-game statistics from all the servers and makes it available to the Prometheus endpoint on the master server. - [Subspace Storage](https://github.com/clusterio/subspace_storage): Provide shared storage that can transport items between servers via teleport chests. - [Player Auth](/plugins/player_auth/README.md): Provides authentication to the cluster via logging into a Factorio server. +- [Inventory Sync](/plugins/inventory_sync/README.md): Synchronizes your inventory between servers. There's also plugins developed and maintained by the community: diff --git a/plugins/inventory_sync/README.md b/plugins/inventory_sync/README.md index e01bec8a..f5d27ca7 100644 --- a/plugins/inventory_sync/README.md +++ b/plugins/inventory_sync/README.md @@ -2,6 +2,12 @@ Carry over player inventory between servers +As a player, it mostly just works. Most important to know is that factorio data transfer is slow, which means if you have "big" items (blueprints mostly) in your inventory it will take a long time to transfer when joining servers. For this reason it is recommended to keep blueprints in the blueprint library. + +A note on crashing servers: + +When a server crashes while you are online there won't be time to immediately upload the inventory. Instead, the inventory is uploaded as soon as the server restarts. This means if you have been playing on a different server from the one that crashed and gathered/consumed items but are offline at the time of the first server coming online again your inventory will reset to the autosave. + ## Installation Run the following commands in the folder Clusterio is installed to: @@ -9,11 +15,11 @@ Run the following commands in the folder Clusterio is installed to: npm install @clusterio/plugin-inventory_sync npx clusteriomaster plugin add @clusterio/plugin-inventory_sync -Substitute clusteriomaster with clusterioslave or clusterioctl if this a dedicate slave or ctl installation respectively. +Substitute clusteriomaster with clusterioslave or clusterioctl if this a dedicated slave or ctl installation respectively. ## Method of operation -This plugin does even based synchronization of inventories. The data flow is as follows: +This plugin does event based synchronization of inventories. The data flow is as follows: 1. Player joins server, gets put in spectator mode diff --git a/plugins/inventory_sync/module/inventory_sync.lua b/plugins/inventory_sync/module/inventory_sync.lua index 396c9afe..97683ce7 100644 --- a/plugins/inventory_sync/module/inventory_sync.lua +++ b/plugins/inventory_sync/module/inventory_sync.lua @@ -34,7 +34,7 @@ inventory_sync.events[defines.events.on_player_joined_game] = function(event) 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) +inventory_sync.events[defines.events.on_pre_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 diff --git a/plugins/inventory_sync/module/load_crafting_queue.lua b/plugins/inventory_sync/module/load_crafting_queue.lua index edec5cb6..f44d060b 100644 --- a/plugins/inventory_sync/module/load_crafting_queue.lua +++ b/plugins/inventory_sync/module/load_crafting_queue.lua @@ -3,33 +3,28 @@ 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 + 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 -- Load crafting queue - for _, queueItem in pairs(crafting_queue) do + for _, queueItem in pairs(crafting_queue.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 + -- game.print("Crafting "..queueItem.recipe) -- Start crafting (consume items) player.begin_crafting { count = queueItem.count, recipe = queueItem.recipe, - silent = true, -- Fail silently if items are missing + -- silent = true, -- Fail silently if items are missing } end -- Remove extra inventory slots - player.character_inventory_slots_bonus = player.character_inventory_slots_bonus - 100 + player.character_inventory_slots_bonus = player.character_inventory_slots_bonus - 1000 end return load_crafting_queue diff --git a/plugins/inventory_sync/module/save_crafts.lua b/plugins/inventory_sync/module/save_crafts.lua index b11beab1..4e2cc1f2 100644 --- a/plugins/inventory_sync/module/save_crafts.lua +++ b/plugins/inventory_sync/module/save_crafts.lua @@ -9,46 +9,87 @@ function save_crafts(event) 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 + 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() + -- 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] - 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, + 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 + + -- 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("Canceled craft "..recipe.name) + -- 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 - 100 + player.character_inventory_slots_bonus = player.character_inventory_slots_bonus - 1000 - global["saved_crafting_queue_"..player.name] = crafting_queue + global["saved_crafting_queue_"..player.name] = { + crafting_queue = crafting_queue, + ingredients = difference, + } end end diff --git a/plugins/inventory_sync/module/upload_inventory.lua b/plugins/inventory_sync/module/upload_inventory.lua index 0394fc6c..c3e6cc50 100644 --- a/plugins/inventory_sync/module/upload_inventory.lua +++ b/plugins/inventory_sync/module/upload_inventory.lua @@ -1,6 +1,7 @@ local clusterio_api = require("modules/clusterio/api") local serialize = require("modules/clusterio/serialize") local inventories = require("modules/inventory_sync/define_player_inventories") +local save_crafts = require("modules/inventory_sync/save_crafts") function upload_inventory(playerIndex) local player = game.players[playerIndex] @@ -68,7 +69,8 @@ function upload_inventory(playerIndex) end end - -- Serialize crafting queue saved by /csc or /save-crafts + -- Serialize crafting queue saved by /csc or /save-crafts OR automatically + save_crafts({player_index = player.name}) serialized_player.crafting_queue = global["saved_crafting_queue_"..player.name] if serialized_player.crafting_queue == nil then serialized_player.crafting_queue = {} diff --git a/plugins/inventory_sync/package.json b/plugins/inventory_sync/package.json index 3237132f..18a47d61 100644 --- a/plugins/inventory_sync/package.json +++ b/plugins/inventory_sync/package.json @@ -22,6 +22,9 @@ "devDependencies": { "@clusterio/lib": "^2.0.0-alpha.6", "@clusterio/web_ui": "^2.0.0-alpha.6", + "antd": "^4.16.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", "webpack": "^5.36.2", "webpack-cli": "^4.7.0", "webpack-merge": "^5.2.0"