381 lines
13 KiB
Lua
381 lines
13 KiB
Lua
local this = {}
|
|
local util = scripts.util
|
|
local visuals = scripts.visuals
|
|
local metatables = scripts.metatables
|
|
local config = require("config")
|
|
|
|
local helpers = scripts.helpers
|
|
local _ = helpers.on
|
|
|
|
function this.on_tick(event) -- handles distribution events
|
|
local distrEvents = global.distrEvents
|
|
|
|
if distrEvents[event.tick] then
|
|
for player_index, cache in pairs(distrEvents[event.tick]) do
|
|
local player = _(game.players[player_index])
|
|
|
|
if player:is("valid player") then
|
|
if player:setting("distributionMode") == "distribute" then
|
|
this.distributeItems(player, cache)
|
|
else
|
|
this.balanceItems(player, cache)
|
|
end
|
|
end
|
|
|
|
this.resetCache(cache)
|
|
end
|
|
|
|
distrEvents[event.tick] = nil
|
|
end
|
|
end
|
|
|
|
function this.distributeItems(player, cache)
|
|
local useFuelLimit = player:setting("dragUseFuelLimit")
|
|
local useAmmoLimit = player:setting("dragUseAmmoLimit")
|
|
local takeFromInv = player:setting("takeFromInventory")
|
|
local takeFromCar = player:setting("takeFromCar")
|
|
local replaceItems = player:setting("replaceItems")
|
|
local item = cache.item
|
|
local totalItems = player:itemcount(item, takeFromInv, takeFromCar)
|
|
|
|
if cache.half then totalItems = math.ceil(totalItems / 2) end
|
|
|
|
util.distribute(cache.entities, totalItems, function(entity, amount)
|
|
|
|
local itemsInserted = 0
|
|
local color
|
|
|
|
if amount > 0 then
|
|
local takenFromPlayer = player:removeItems(item, amount, takeFromInv, takeFromCar, false)
|
|
|
|
if takenFromPlayer < amount then color = config.colors.insufficientItems end
|
|
|
|
if takenFromPlayer > 0 then
|
|
itemsInserted = entity:customInsert(player, item, takenFromPlayer, takeFromCar, false, replaceItems, useFuelLimit, useAmmoLimit, false, {
|
|
-- if modules are recipe ingredients, dont put into module slots
|
|
-- modules = not entity:is("crafting machine") or not _(entity.get_recipe()):hasIngredient(item),
|
|
output = false,
|
|
})
|
|
|
|
local failedToInsert = takenFromPlayer - itemsInserted
|
|
if failedToInsert > 0 then
|
|
player:returnItems(item, failedToInsert, takeFromCar, false)
|
|
color = config.colors.targetFull
|
|
end
|
|
end
|
|
else
|
|
color = config.colors.insufficientItems
|
|
end
|
|
|
|
-- feedback
|
|
entity:spawnDistributionText(item, itemsInserted, 0, color)
|
|
-- player.play_sound{ path = "utility/inventory_move" }
|
|
|
|
end)
|
|
end
|
|
|
|
function this.balanceItems(player, cache)
|
|
local useFuelLimit = player:setting("dragUseFuelLimit")
|
|
local useAmmoLimit = player:setting("dragUseAmmoLimit")
|
|
local takeFromInv = player:setting("takeFromInventory")
|
|
local takeFromCar = player:setting("takeFromCar")
|
|
local replaceItems = player:setting("replaceItems")
|
|
local item = cache.item
|
|
local entitiesToProcess = metatables.new("entityAsIndex")
|
|
local itemCounts = metatables.new("entityAsIndex")
|
|
|
|
local totalItems = player:itemcount(item, takeFromInv, takeFromCar)
|
|
if cache.half then totalItems = math.ceil(totalItems / 2) end
|
|
if totalItems > 0 then
|
|
totalItems = player:removeItems(item, totalItems, takeFromInv, takeFromCar, false)
|
|
end
|
|
|
|
-- collect all items from all entities
|
|
_(cache.entities):where("valid", function(entity)
|
|
local count = _(entity):itemcount(item)
|
|
local removed = 0
|
|
if count > 0 then
|
|
removed = entity.remove_item{ name = item, count = count }
|
|
totalItems = totalItems + removed
|
|
end
|
|
|
|
-- save entities in new list
|
|
entitiesToProcess[entity] = entity
|
|
itemCounts[entity] = {
|
|
original = count,
|
|
remaining = count - removed, -- amount above balanced level (unable to take out)
|
|
current = count - removed,
|
|
}
|
|
end)
|
|
|
|
-- distribute collected items evenly
|
|
local i = 0
|
|
while totalItems > 0 and #entitiesToProcess > 0 do -- retry if some containers full
|
|
if i == 1000 then dlog("WARNING: Balance item loop did not finish!"); break end -- safeguard
|
|
i = i + 1
|
|
|
|
util.distribute(entitiesToProcess, totalItems, function(entity, amount)
|
|
|
|
local itemCount = itemCounts[entity]
|
|
|
|
amount = amount - itemCount.remaining
|
|
if amount > 0 then
|
|
local itemsInserted = entity:customInsert(player, item, amount, takeFromCar, false, replaceItems, useFuelLimit, useAmmoLimit, false, {
|
|
-- if modules are recipe ingredients, dont put into module slots
|
|
-- modules = not entity:is("crafting machine") or not _(entity.get_recipe()):hasIngredient(item),
|
|
output = false,
|
|
})
|
|
|
|
itemCount.current = itemCount.current + itemsInserted
|
|
totalItems = totalItems - itemsInserted
|
|
|
|
local failedToInsert = amount - itemsInserted
|
|
if failedToInsert > 0 then
|
|
entity:spawnDistributionText(item, itemCount.current - itemCount.original, 0, config.colors.targetFull)
|
|
entitiesToProcess[entity] = nil -- set nil while iterating bad?
|
|
return
|
|
end
|
|
|
|
amount = 0
|
|
end
|
|
|
|
itemCount.remaining = -amount -- update remaining item count (amount above balanced level)
|
|
-- add entity to new list?
|
|
end)
|
|
end
|
|
|
|
_(entitiesToProcess):each(function(entity)
|
|
local itemCount = itemCounts[entity]
|
|
local amount = itemCount.current - itemCount.original
|
|
_(entity):spawnDistributionText(item, amount, 0, (itemCount.current == 0) and config.colors.insufficientItems
|
|
or config.colors.default)
|
|
end)
|
|
|
|
if totalItems > 0 then
|
|
player:returnItems(item, totalItems, takeFromCar, false)
|
|
end
|
|
end
|
|
|
|
function this.on_fast_entity_transfer_hook(event)
|
|
local index = event.player_index
|
|
local player = _(game.players[index]); if player:isnot("valid player") then return end
|
|
local cache = _(global.cache[index])
|
|
cache.half = false
|
|
end
|
|
|
|
function this.on_fast_entity_split_hook(event)
|
|
local index = event.player_index
|
|
local player = _(game.players[index]); if player:isnot("valid player") then return end
|
|
local cache = _(global.cache[index])
|
|
cache.half = true
|
|
end
|
|
|
|
function this.on_selected_entity_changed(event)
|
|
local index = event.player_index
|
|
local player = _(game.players[index]); if player:isnot("valid player") or not player:setting("enableDragDistribute") then return end
|
|
local cursor_stack = _(player.cursor_stack); if cursor_stack:isnot("valid stack") then return end
|
|
local cache = _(global.cache[index])
|
|
local selected = _(player.selected) ; if selected:isnot("valid") or selected:isIgnored(player) then return end
|
|
|
|
-- if not selected.can_insert{ name = cursor_stack.name, count = 1 } then return end
|
|
|
|
cache.selectedEvent = {
|
|
tick = event.tick,
|
|
item = cursor_stack.name,
|
|
itemCount = selected:itemcount(cursor_stack.name),
|
|
cursorStackCount = cursor_stack.count,
|
|
}
|
|
end
|
|
|
|
function this.on_player_fast_transferred(event)
|
|
local index = event.player_index
|
|
local player = _(game.players[index]); if player:isnot("valid player") or not player:setting("enableDragDistribute") then return end
|
|
local cache = _(global.cache[index])
|
|
local selected = _(player.selected) ; if selected:isnot("valid") or selected:isIgnored(player) then return end
|
|
|
|
if cache.selectedEvent and cache.selectedEvent.tick == event.tick and event.entity == selected:toPlain() then
|
|
|
|
if event.from_player then
|
|
-- distribute...
|
|
|
|
if cache.selectedEvent.item then
|
|
cache:set{
|
|
item = cache.selectedEvent.item,
|
|
itemCount = selected:itemcount(cache.selectedEvent.item) - cache.selectedEvent.itemCount,
|
|
cursorStackCount = cache.selectedEvent.cursorStackCount,
|
|
}
|
|
|
|
this.onStackTransferred(selected, player, cache) -- handle stack transfer
|
|
end
|
|
else
|
|
-- take...
|
|
end
|
|
end
|
|
end
|
|
|
|
function this.onStackTransferred(entity, player, cache) -- handle vanilla drag stack transfer
|
|
local takeFromInv = player:setting("takeFromInventory")
|
|
local takeFromCar = player:setting("takeFromCar")
|
|
local distributionMode = player:setting("distributionMode")
|
|
local item = cache.item
|
|
local first = #cache.entities > 0 and util.epairs(cache.entities)() or nil
|
|
|
|
if not _(entity):isIgnored(player) and this.isEntityEligible(entity, item) and
|
|
(first == nil or ((first.type == "car" or first.type == "spider-vehicle") and (entity.type == "car" or entity.type == "spider-vehicle"))
|
|
or ((first.type ~= "car" and first.type ~= "spider-vehicle") and (entity.type ~= "car" and entity.type ~= "spider-vehicle"))) then
|
|
|
|
local distrEvents = global.distrEvents -- register new distribution event
|
|
if cache.applyTick and distrEvents[cache.applyTick] then distrEvents[cache.applyTick][player.index] = nil end
|
|
|
|
-- wait before applying distribution (seconds defined in mod config)
|
|
cache.applyTick = game.tick + math.max(math.ceil(60 * _(player):setting("distributionDelay")), 1)
|
|
|
|
distrEvents[cache.applyTick] = distrEvents[cache.applyTick] or {}
|
|
distrEvents[cache.applyTick][player.index] = cache
|
|
|
|
if not cache.entities[entity] then
|
|
cache.markers[entity] = entity:mark(player, item)
|
|
cache.entities[entity] = entity
|
|
end
|
|
|
|
if cache.itemCount == 0 then
|
|
player.play_sound{ path = "utility/inventory_move" }
|
|
end
|
|
end
|
|
|
|
-- give back transferred items
|
|
local collected = 0
|
|
local cursor_stack = player.cursor_stack
|
|
|
|
if cache.itemCount > 0 then
|
|
collected = entity.remove_item{ name = item, count = cache.itemCount }
|
|
end
|
|
|
|
if cursor_stack.valid_for_read and cursor_stack.name ~= item then
|
|
-- other items in cursor
|
|
player:inventory().insert{ name = item, count = collected }
|
|
|
|
else -- same items
|
|
-- collect cursor and transferred items temporarily
|
|
if cursor_stack.valid_for_read then
|
|
collected = collected + cursor_stack.count
|
|
end
|
|
|
|
-- fill cursor to previous amount
|
|
if collected < cache.cursorStackCount then
|
|
collected = collected + player:inventory().remove{ name = item, count = cache.cursorStackCount - collected }
|
|
end
|
|
|
|
if collected > 0 then
|
|
if collected < cache.cursorStackCount then
|
|
cursor_stack.set_stack{ name = item, count = collected }
|
|
else
|
|
cursor_stack.set_stack{ name = item, count = cache.cursorStackCount }
|
|
collected = collected - cache.cursorStackCount
|
|
if collected > 0 then
|
|
player:inventory().insert{ name = item, count = collected }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---- visuals ----
|
|
local totalItems = player:itemcount(item, takeFromInv, takeFromCar)
|
|
if cache.half then totalItems = math.ceil(totalItems / 2) end
|
|
|
|
if distributionMode == "balance" then
|
|
_(cache.entities):where("valid", function(entity)
|
|
totalItems = totalItems + _(entity):itemcount(item)
|
|
end)
|
|
end
|
|
|
|
util.distribute(cache.entities, totalItems, function(entity, amount)
|
|
-- if distributionMode == "balance" then
|
|
-- local count = _(entity):itemcount(item)
|
|
-- if count > amount then
|
|
-- visuals.update(cache.markers[entity], item, count, config.colors.targetFull)
|
|
-- return
|
|
-- end
|
|
-- end
|
|
|
|
visuals.update(cache.markers[entity], item, amount)
|
|
end)
|
|
|
|
entity:destroyTransferText()
|
|
end
|
|
|
|
function this.isEntityEligible(entity, item)
|
|
local prototype = game.item_prototypes[item]
|
|
entity = _(entity)
|
|
|
|
if entity.can_insert(item) then
|
|
return true
|
|
elseif entity.burner and entity.burner.fuel_categories[prototype.fuel_category] then
|
|
return true
|
|
elseif entity:is("crafting machine") and entity:recipe():hasIngredient(item) then
|
|
return true
|
|
elseif entity.type == "furnace" and not entity.get_recipe() and entity:canSmelt(item) then
|
|
return true
|
|
elseif entity.type == "rocket-silo" then
|
|
return true
|
|
elseif entity.type == "lab" and entity:inventory("lab_input").can_insert(item) then
|
|
return true
|
|
elseif (entity.type == "ammo-turret" or entity.type == "artillery-turret" or entity.type == "artillery-wagon") and entity:supportsAmmo(prototype) then
|
|
return true
|
|
elseif entity.type == "roboport" and (prototype.type == "repair-tool" or (prototype.place_result and prototype.place_result.max_payload_size ~= nil)) then
|
|
return true
|
|
elseif entity.prototype.module_inventory_size and entity.prototype.module_inventory_size > 0 and prototype.type == "module" then
|
|
return true
|
|
elseif entity:inventory("main") then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function this.resetCache(cache)
|
|
cache.item = nil
|
|
cache.half = false
|
|
cache.entities = metatables.new("entityAsIndex")
|
|
visuals.unmark(cache)
|
|
end
|
|
|
|
function this.on_pre_player_mined_item(event) -- remove mined/dead entities from cache
|
|
local entity = event.entity
|
|
|
|
for __,cache in pairs(global.cache) do
|
|
if cache.entities[entity] then
|
|
_(cache.markers[entity]):unmark() -- remove markers
|
|
cache.markers[entity] = nil
|
|
cache.entities[entity] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
this.on_robot_pre_mined = this.on_pre_player_mined_item
|
|
this.on_entity_died = this.on_pre_player_mined_item
|
|
|
|
function this.script_raised_destroy(event)
|
|
event = event or {}
|
|
event.entity = event.entity or event.destroyed_entity or event.destroyedEntity or event.target or nil
|
|
|
|
if _(event.entity):is("valid") then this.on_pre_player_mined_item(event) end
|
|
end
|
|
|
|
function this.on_player_died(event) -- resets distribution cache and events for that player
|
|
local cache = global.cache[event.player_index]
|
|
|
|
if cache then
|
|
this.resetCache(cache)
|
|
|
|
local distrEvents = global.distrEvents -- remove distribution event
|
|
if cache.applyTick and distrEvents[cache.applyTick] then
|
|
distrEvents[cache.applyTick][event.player_index] = nil
|
|
cache.applyTick = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
this.on_player_left_game = this.on_player_died
|
|
|
|
return this |