Keep inventory


Keeps your inventory after death.

Tweaks
a month ago
1.1 - 2.1
9.75K

b [FIXED?] mod/game crashed when a player died

a month ago

i fixed and merge all file into one

if remote.interfaces["keep-inventory"] then return end

---@type table<string, table>
local mod_data
---@type table<integer, LuaEntity>
local corpses_queue
---@type table<integer, table<string, integer>>
local corpse_inventory
-- LuaSurface, MapPosition
---@type table<integer, table>
local corpse_pos_data

-- Event handlers can still run when another part of the mod replaces one of
-- this module's lifecycle handlers. Rebind and initialize the persistent
-- tables at the point of use so the local aliases are always available.
local function ensure_data()
storage.keep_inventory = storage.keep_inventory or {}
local data = storage.keep_inventory
data.corpses_queue = data.corpses_queue or {}
data.corpse_inventory = data.corpse_inventory or {}
data.corpse_pos_data = data.corpse_pos_data or {}

mod_data = data
corpses_queue = data.corpses_queue
corpse_inventory = data.corpse_inventory
corpse_pos_data = data.corpse_pos_data
return data

end

remote.add_interface("keep-inventory", {
getSource = function()
local mod_name = script.mod_name
rcon.print(mod_name) -- Returns "level" if it's a scenario, otherwise "keep-inventory" as a mod.
return mod_name
end
})

---For Space Exploration mod
local function on_pre_player_died(event)
ensure_data()
local player_index = event.player_index
corpse_pos_data[player_index] = nil
local player = game.get_player(player_index)
if not (player and player.valid) then return end
if player.cheat_mode then return end
corpse_pos_data[player_index] = {player.surface, player.position} -- (it's wrong, but it works)
end

---For Space Exploration mod
local function on_SE_player_respawned(event)
ensure_data()
local player_index = event.player_index
local player = game.get_player(player_index)
if not (player and player.valid) then return end
if player.cheat_mode then
corpse_pos_data[player_index] = nil
return
end
if corpse_pos_data[player_index] == nil then return end

local pos_data = corpse_pos_data[player_index]
local surface = pos_data[1]
if not (surface and surface.valid) then
    corpse_pos_data[player_index] = nil
    return
end
local position = pos_data[2]
local corpse = surface.find_entity("character-corpse", position)
if not (corpse and corpse.valid) then
    corpse_pos_data[player_index] = nil
    return
end
local prev_inventory = corpse.get_inventory(defines.inventory.character_corpse)
if not (prev_inventory and prev_inventory.valid) or prev_inventory.is_empty() then
    corpse_pos_data[player_index] = nil
    return
end

if prev_inventory.valid then
    local player_position = player.position
    local current_surface = player.surface
    local spill_item_stack_param = {
        position = player_position, stack = nil,
        enable_looted = true, allow_belts = false
    }
    local armor_inventory = player.get_inventory(defines.inventory.character_armor)
    if armor_inventory and armor_inventory.valid then
        for i = 1, #prev_inventory do
            local stack = prev_inventory[i]
            if not stack.valid_for_read then
                goto continue
            end
            if not stack.is_armor then
                goto continue
            end
            if armor_inventory.can_insert(stack) then
                armor_inventory.insert(stack)
                prev_inventory.remove(stack)
                break
            end
            if player.can_insert(stack) then
                player.insert(stack)
            else
                spill_item_stack_param.stack = stack
                current_surface.spill_item_stack(spill_item_stack_param)
            end
            :: continue ::
        end
    end
    for i = 1, #prev_inventory do
        local stack = prev_inventory[i]
        if not stack.valid_for_read then
            goto continue
        end
        if player.can_insert(stack) then
            player.insert(stack)
        else
            spill_item_stack_param.stack = stack
            current_surface.spill_item_stack(spill_item_stack_param)
        end
        :: continue ::
    end
    corpse_inventory[player_index] = nil
end

corpses_queue[player_index] = nil
if corpse and corpse.valid then
    corpse.destroy({raise_destroy=true})
end
corpse_pos_data[player_index] = nil

end

local function on_player_died(event)
ensure_data()
local player_index = event.player_index
corpse_inventory[player_index] = nil
local player = game.get_player(player_index)
if not (player and player.valid) then return end
if player.cheat_mode then return end
local surface = player.surface
local position = player.position
-- TOOD: improve
local corpse = surface.find_entity("character-corpse", position)
if not (corpse and corpse.valid) then return end
corpse.operable = false
corpse.minable_flag = false
corpses_queue[player_index] = corpse

local inventory = corpse.get_inventory(defines.inventory.character_corpse)
if not (inventory and inventory.valid) or inventory.is_empty() then return end
corpse_inventory[player_index] = inventory

end

local function on_pre_player_removed(event)
ensure_data()
local player_index = event.player_index
corpses_queue[player_index] = nil
corpse_inventory[player_index] = nil
end

local function on_player_respawned(event)
ensure_data()
local player_index = event.player_index
local player = game.get_player(player_index)
if not (player and player.valid) then return end
if player.cheat_mode then
corpses_queue[player_index] = nil
return
end

local prev_inventory = corpse_inventory[player_index]
if prev_inventory and prev_inventory.valid then
    local player_position = player.position
    local current_surface = player.surface
    local spill_item_stack_param = {
        position = player_position, stack = nil,
        enable_looted = true, allow_belts = false
    }
    local armor_inventory = player.get_inventory(defines.inventory.character_armor)
    if armor_inventory and armor_inventory.valid then
        for i = 1, #prev_inventory do
            local stack = prev_inventory[i]
            if not stack.valid_for_read then
                goto continue
            end
            if not stack.is_armor then
                goto continue
            end
            if armor_inventory.can_insert(stack) then
                armor_inventory.insert(stack)
                prev_inventory.remove(stack)
                break
            end
            if player.can_insert(stack) then
                player.insert(stack)
            else
                spill_item_stack_param.stack = stack
                current_surface.spill_item_stack(spill_item_stack_param)
            end
            :: continue ::
        end
    end
    for i = 1, #prev_inventory do
        local stack = prev_inventory[i]
        if not stack.valid_for_read then
            goto continue
        end
        if player.can_insert(stack) then
            player.insert(stack)
        else
            spill_item_stack_param.stack = stack
            current_surface.spill_item_stack(spill_item_stack_param)
        end
        :: continue ::
    end
    corpse_inventory[player_index] = nil
end

local corpse = corpses_queue[player_index]
corpses_queue[player_index] = nil
if corpse and corpse.valid then
    corpse.destroy({raise_destroy=true})
end

end

local function link_events()
if remote.interfaces["space-exploration"] and remote.interfaces["space-exploration"]["get_on_player_respawned_event"] then
script.on_event(defines.events.on_pre_player_died, on_pre_player_died)
local id = remote.call("space-exploration", "get_on_player_respawned_event")
script.on_event(id, on_SE_player_respawned)
end
end

local function link_data()
mod_data = storage.keep_inventory
if mod_data then
corpses_queue = mod_data.corpses_queue
corpse_inventory = mod_data.corpse_inventory
corpse_pos_data = mod_data.corpse_pos_data
else
corpses_queue = nil
corpse_inventory = nil
corpse_pos_data = nil
end

link_events()

end

local function update_global_data()
local data = ensure_data()
data.players_bodies = {}

link_events()

for player_index, corpse in pairs(corpses_queue) do
    if game.get_player(player_index) == nil or corpse.valid == false then
        corpses_queue[player_index] = nil
    end
end

for player_index, inv in pairs(corpse_inventory) do
    if game.get_player(player_index) == nil or inv.valid == false then
        corpse_inventory[player_index] = nil
    end
end

for player_index in pairs(corpse_pos_data) do
    if game.get_player(player_index) == nil then
        corpse_pos_data[player_index] = nil
    end
end

end

script.on_init(update_global_data)
script.on_configuration_changed(update_global_data)
script.on_load(link_data)
script.on_event(defines.events.on_player_respawned, on_player_respawned)
script.on_event(defines.events.on_player_died, on_player_died)
script.on_event(defines.events.on_pre_player_removed, on_pre_player_removed)

-- This is a part of "gvv", "Lua API global Variable Viewer" mod. https://mods.factorio.com/mod/gvv
-- It makes possible gvv mod to read sandboxed variables in the map or other mod if following code is inserted at the end of empty line of "control.lua" of each.
if script.active_mods["gvv"] then require("gvv.gvv")() end

[deleted message]
a month ago

Error handling isn't consistent, huh???

a month ago

Anyway, I've updated the mod.

a month ago

thank you

New response