Offer the player a temporary inventory while waiting for master

This commit is contained in:
Daniel committed 2021-07-29 23:55:48 +02:00
1 parent 29917fb85a
commit e63f1246da
8 files changed
+172 -2

No files matched your search

+3
View File
@@ -17,6 +17,7 @@ function chunkify(chunkSize, string) {
class InstancePlugin extends libPlugin.BaseInstancePlugin {
async init() {
// Handle IPC from scenario script
this.instance.server.on("ipc-inventory_sync_upload", content => this.handleUpload(content)
.catch(err => this.logger.error(`Error handling ipc-inventory_sync_upload:\n${err.stack}`)));
this.instance.server.on("ipc-inventory_sync_download", content => this.handleDownload(content)
@@ -35,6 +36,8 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
async handleDownload(player) {
this.logger.verbose(`Downloading ${player.player_name}`);
// Retrieves inventory data. If the master is offline it waits until it comes back online.
let response = await this.info.messages.download.send(this.instance, {
player_name: player.player_name,
});
@@ -1,4 +1,4 @@
local progress_dialog = require("modules/inventory_sync/progress_dialog")
local progress_dialog = require("modules/inventory_sync/gui/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")
@@ -27,7 +27,18 @@ function download_inventory(player_name, data, number, total)
-- 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
-- Handle dirty inventory
if global.inventory_sync.players[player_name].dirty_inventory then
clean_dirty_inventory(player)
end
-- Recreate player character
ensure_character(player)
@@ -0,0 +1,60 @@
-- 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
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 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",
}
filler.style.horizontally_stretchable = "on"
filler.style.right_margin = 4
filler.style.height = 24
filler.drag_target = frame
local content = frame.add {
name = "inventory_sync_failed_download_content",
type = "frame",
direction = "vertical",
style = "inside_shallow_frame_with_padding",
}
content.style.width = 400
local p1 = content.add {
type = "label",
caption = "The inventory download couldn't complete. You can choose to keep playing with an empty inventory until the connection is reestablished and the inventory download completes. At that point your temporary inventory will be put in a chest.",
}
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.",
}
frame.force_auto_center()
player.opened = frame
end
return dialog_failed_download
@@ -0,0 +1,23 @@
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
-- 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
@@ -14,7 +14,10 @@ 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/welcome_new_player")
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")
inventory_sync = {}
inventory_sync.welcome_new_player = welcome_new_player
@@ -33,6 +36,7 @@ inventory_sync.events[clusterio_api.events.on_server_startup] = function(event)
saved_crafting_queue = {},
download_cache = {},
players_waiting_for_download = {},
players = {},
}
end
end
@@ -45,8 +49,20 @@ inventory_sync.events[defines.events.on_player_removed] = function(event)
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
}
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
@@ -57,7 +73,11 @@ 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.
@@ -97,12 +117,36 @@ function inventory_sync.initiate_inventory_download(player)
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
@@ -0,0 +1,29 @@
local inventories = require("modules/inventory_sync/define_player_inventories")
--[[
Take a player with a dirty inventory, create chests with all the items, clear the player,
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
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
end
return clean_dirty_inventory