local common = require("scripts.common") --- @generic T --- @param array T[] --- @param pred fun(elem: T): boolean local function remove_if(array, pred) local to_remove = {} for i = 1, #array do if pred(array[i]) then to_remove[#to_remove + 1] = i end end -- Remove from back to front to keep the indices valid for i = #to_remove, 1, -1 do table.remove(array, to_remove[i]) end end --- @param base LocalisedString --- @param child LocalisedString local function append_localised_string(base, child) --- @cast base -? local len = #base if len < 18 then base[len + 1] = child return end if len == 18 then base[19] = { "" } end append_localised_string(base[19], child) end --- @alias Deltas table --- @param deltas Deltas --- @param name string --- @param quality string --- @param count integer local function add_delta(deltas, name, quality, count) local key = name .. "/" .. quality local delta = deltas[key] if not delta then delta = { name = name, quality = quality, count = 0 } deltas[key] = delta end delta.count = delta.count + count end --- @param player LuaPlayer --- @param entity LuaEntity return function(player, entity) if not player.mod_settings["moc-enable-insertion"].value then return end if entity.type == "entity-ghost" then return end local proxy = entity.item_request_proxy if not proxy then return end --- @type Deltas local deltas = {} local entity_insert_plan = proxy.insert_plan remove_if(entity_insert_plan, function(insert_plan) -- TODO: Equipment grids local inventories = insert_plan.items.in_inventory if not inventories then return false end -- Despite the docs, these reads are not LuaObjects because insert_plan is a PropertyTree... local name = insert_plan.id.name --[[@as string]] local quality_name = insert_plan.id.quality or "normal" --[[@as string]] local quality = prototypes.quality[quality_name] remove_if(inventories, function(inventory_position) local inventory = entity.get_inventory(inventory_position.inventory) if not inventory then return false end if not inventory_position.count then inventory_position.count = 1 end while inventory_position.count > 0 do local item = common.get_item(player, name, quality)() if not item then return false end -- For some ungodly reason, stack is zero indexed! local destination_stack = inventory[inventory_position.stack + 1] local to_transfer = math.min(inventory_position.count, item.count) assert(destination_stack.transfer_stack(item, to_transfer), "Inventory transfer failed") inventory_position.count = inventory_position.count - to_transfer -- Negate because the deltas are relative to the player inventory. add_delta(deltas, name, quality_name, -to_transfer) end return inventory_position.count == 0 end) return not next(inventories) end) proxy.insert_plan = entity_insert_plan local entity_removal_plan = proxy.removal_plan remove_if(entity_removal_plan, function(removal_plan) -- TODO: Equipment grids local inventories = removal_plan.items.in_inventory if not inventories then return false end -- Despite the docs, these reads are not LuaObjects because removal_plan is a PropertyTree... local name = removal_plan.id.name --[[@as string]] local item_prototype = prototypes.item[name] local quality_name = removal_plan.id.quality or "normal" --[[@as string]] local quality = prototypes.quality[quality_name] remove_if(inventories, function(inventory_position) local inventory = entity.get_inventory(inventory_position.inventory) if not inventory then return false end local count_to_remove = inventory_position.count or 1 -- For some ungodly reason, stack is zero indexed! local source_stack = inventory[inventory_position.stack + 1] -- This probably can't ever happen. if source_stack.count < count_to_remove then return false end local destination_stack_iterator = common.get_item(player, name, quality) repeat local destination_stack = destination_stack_iterator() if not destination_stack then return false end local to_transfer = math.min(inventory_position.count, destination_stack.count) assert(destination_stack.transfer_stack(destination_stack, to_transfer), "Inventory transfer failed") inventory_position.count = inventory_position.count - to_transfer -- Negate because the deltas are relative to the player inventory. add_delta(deltas, name, quality_name, -to_transfer) until count_to_remove <= 0 or not destination_stack return inventory_position.count == 0 end) return not next(inventories) end) proxy.removal_plan = entity_removal_plan table.sort(deltas, function(a, b) return a.count > b.count end) --- @type LocalisedString local output = { "" } for _, delta in pairs(deltas) do if delta.count == 0 then goto continue end player.play_sound({ path = "item-drop/" .. delta.name }) local delta_string = tostring(delta.count) if delta.count > 0 then delta_string = "+" .. delta_string end append_localised_string(output, { "", delta_string, " ", "[item=" .. delta.name .. "] ", prototypes.item[delta.name].localised_name, "\n", }) ::continue:: end player.create_local_flying_text({ position = entity.position, text = output, }) return true end