Первый фикс

Пачки некоторых позиций увеличены
This commit is contained in:
2024-03-01 20:53:32 +03:00
commit 7c9c708c92
23653 changed files with 767936 additions and 0 deletions

View File

@@ -0,0 +1,313 @@
-- Inventory Cleanup Hotkey
local this = {}
local drag = scripts.drag
local util = scripts.util
local metatables = scripts.metatables
local config = require("config")
local helpers = scripts.helpers
local _ = helpers.on
function this.on_inventory_cleanup(event)
local player = _(game.players[event.player_index]); if player:isnot("valid player") or not player:setting("enableInventoryCleanupHotkey") then return end
local items = _(player:trashItems()) ; if items:is("empty") then return end
local area = _(player.position):perimeter(player:droprange())
local entities = _(this.getEntities(area, player)) ; if entities:is("empty") then return end
local dropToChests = player:setting("dropTrashToChests")
local dropToOutput = player:setting("dropTrashToOutput")
if player:setting("distributionMode") == "distribute" then
this.distributeItems(player, entities, items, dropToChests, dropToOutput)
else
this.balanceItems(player, entities, items, dropToChests, dropToOutput)
end
end
function this.distributeItems(player, entities, items, dropToChests, dropToOutput)
local offY, marked = 0, metatables.new("entityAsIndex")
items:each(function(item, totalItems)
local entitiesToProcess = this.filterEntities(entities, item, dropToChests, dropToOutput)
if #entitiesToProcess > 0 then
local itemCounts = metatables.new("entityAsIndex")
totalItems = player:removeItems(item, totalItems, true, false, true)
_(entitiesToProcess):each(function(entity)
local count = entity:itemcount(item)
itemCounts[entity] = {
original = count,
current = count,
}
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: Distribute item loop did not finish!"); break end -- safeguard
i = i + 1
util.distribute(entitiesToProcess, totalItems, function(entity, amount)
if amount > 0 then
local itemCount = itemCounts[entity]
local itemsInserted = this.insert(player, entity, item, amount)
itemCount.current = itemCount.current + itemsInserted
totalItems = totalItems - itemsInserted
local failedToInsert = amount - itemsInserted
if failedToInsert > 0 then
if itemCount.current ~= itemCount.original then
entity:spawnDistributionText(item, itemCount.current - itemCount.original, offY)
if not marked[entity] then
entity:mark(player)
marked[entity] = true
end
end
entitiesToProcess[entity] = nil
return
end
end
end)
end
_(entitiesToProcess):each(function(entity)
local itemCount = itemCounts[entity]
local amount = itemCount.current - itemCount.original
if amount ~= 0 then
entity:spawnDistributionText(item, amount, offY)
if not marked[entity] then
entity:mark(player)
marked[entity] = true
end
end
end)
if totalItems > 0 then
player:returnItems(item, totalItems, false, true)
end
offY = offY - 0.5
end
end)
end
function this.balanceItems(player, entities, items, dropToChests, dropToOutput)
local offY, marked = 0, metatables.new("entityAsIndex")
items:each(function(item, totalItems)
local entitiesToProcess = this.filterEntities(entities, item, dropToChests, dropToOutput)
if #entitiesToProcess > 0 then
local itemCounts = metatables.new("entityAsIndex")
totalItems = player:removeItems(item, totalItems, true, false, true)
-- collect items from filtered entities
_(entitiesToProcess):each(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
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 = this.insert(player, entity, item, amount)
itemCount.current = itemCount.current + itemsInserted
totalItems = totalItems - itemsInserted
local failedToInsert = amount - itemsInserted
if failedToInsert > 0 then
if itemCount.current ~= itemCount.original then
entity:spawnDistributionText(item, itemCount.current - itemCount.original, offY)
if not marked[entity] then
entity:mark(player)
marked[entity] = true
end
end
entitiesToProcess[entity] = nil
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
if amount ~= 0 then
entity:spawnDistributionText(item, amount, offY)
if not marked[entity] then
entity:mark(player)
marked[entity] = true
end
end
end)
if totalItems > 0 then
player:returnItems(item, totalItems, false, true)
end
offY = offY - 0.5
end
end)
end
function this.insert(player, entity, item, amount)
local useFuelLimit = player:setting("cleanupUseFuelLimit")
local useAmmoLimit = player:setting("cleanupUseAmmoLimit")
local dropToOutput = player:setting("dropTrashToOutput")
if entity.type == "furnace" and entity:recipe():isnot("valid") then
return entity:customInsert(player, item, amount, false, true, false, useFuelLimit, useAmmoLimit, false, {
fuel = true,
ammo = false,
input = false,
output = false,
modules = false,
roboport = false,
main = false,
})
elseif entity:is("crafting machine") then
return entity:customInsert(player, item, amount, false, true, false, useFuelLimit, useAmmoLimit, false, {
fuel = true,
ammo = false,
input = entity:recipe():hasIngredient(item),
output = dropToOutput and entity.type ~= "rocket-silo" and entity:recipe():hasProduct(item),
modules = false,
roboport = false,
main = false,
})
elseif entity.prototype.logistic_mode == "requester" then
return entity:customInsert(player, item, amount, false, true, false, useFuelLimit, useAmmoLimit, true, {
fuel = false,
ammo = false,
input = false,
output = false,
modules = false,
roboport = false,
main = true,
})
elseif entity.type == "spider-vehicle" and entity.get_logistic_point(defines.logistic_member_index.character_requester) then
return entity:customInsert(player, item, amount, false, true, false, useFuelLimit, useAmmoLimit, true, {
fuel = true,
ammo = true,
input = false,
output = false,
modules = false,
roboport = false,
main = true,
})
elseif entity.type == "car" or entity.type == "spider-vehicle" then
return entity:customInsert(player, item, amount, false, true, false, useFuelLimit, useAmmoLimit, false, {
fuel = true,
ammo = true,
input = false,
output = false,
modules = false,
roboport = false,
main = false,
})
end
-- Default priority insertion
return entity:customInsert(player, item, amount, false, true, false, useFuelLimit, useAmmoLimit, false, {
fuel = true,
ammo = true,
input = true,
output = false,
modules = false,
roboport = true,
main = true,
})
end
function this.filterEntities(entities, item, dropToChests, dropToOutput)
local result = metatables.new("entityAsIndex")
local prototype = game.item_prototypes[item]
_(entities):each(function(__, entity)
entity = _(entity)
if entity.can_insert(item) then
if entity.burner and entity.burner.fuel_categories[prototype.fuel_category] and entity:inventory("fuel").can_insert(item) then
result[entity] = entity
elseif entity:is("crafting machine") and entity:recipe():hasIngredient(item) then
result[entity] = entity
elseif (entity.prototype.logistic_mode == "requester" or (entity.type == "spider-vehicle" and entity.get_logistic_point(defines.logistic_member_index.character_requester))) and entity:remainingRequest(item) > 0 then
result[entity] = entity
elseif dropToChests and (entity.type == "container" or entity.type == "logistic-container") and entity.get_item_count(item) > 0 then
result[entity] = entity
elseif entity.type == "lab" and entity:inventory("lab_input").can_insert(item) then
result[entity] = entity
elseif (entity.type == "ammo-turret" or entity.type == "artillery-turret" or entity.type == "artillery-wagon") and entity:supportsAmmo(prototype) then
result[entity] = entity
elseif entity.type == "roboport" then
result[entity] = entity
elseif (entity.type == "car" or entity.type == "spider-vehicle") and prototype.type == "ammo" then
result[entity] = entity
end
end
if not result[entity] and dropToOutput and (entity.type == "furnace" or entity.type == "assembling-machine") and entity:recipe():hasProduct(item) then
result[entity] = entity
end
end)
return result
end
function this.getEntities(area, player)
local entities = {}
for __,entity in ipairs(player.surface.find_entities_filtered{ area = area, force = player.force }) do
if _(entity):is("valid") and
entity.operable and
not entity.to_be_deconstructed(player.force) and
not _(entity):isIgnored(player) then
entities[#entities + 1] = entity
end
end
return entities
end
return this

View File

@@ -0,0 +1,381 @@
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

View File

@@ -0,0 +1,227 @@
local gui = {}
local util = scripts.util
local templates = scripts["gui-templates"]
local mod_gui = require("mod-gui")
local helpers = scripts.helpers
local _ = helpers.on
local MAX_DUPLICATES = 1000
-- GUI events are saved in global.guiEvents["EVENT NAME"][PLAYER INDEX][GUI ELEMENT INDEX]
local eventHandlers = {} -- FIXME: support multiple events (curently all events saved under same ID in eventHandlers)
local events = {
on_gui_checked_state_changed = "onCheckedStateChanged",
on_gui_click = "onClicked",
on_gui_elem_changed = "onElementChanged",
on_gui_selection_state_changed = "onSelectionStateChanged",
on_gui_text_changed = "onTextChanged",
on_gui_value_changed = "onValueChanged",
on_gui_confirmed = "onConfirmed",
on_gui_selected_tab_changed = "onSelectedTabChanged",
on_gui_switch_state_changed = "onSwitchStateChanged",
on_gui_location_changed = "onLocationChanged",
}
local onChangedEvents = {
["checkbox"] = "onCheckedStateChanged",
["radiobutton"] = "onCheckedStateChanged",
["choose-elem-button"] = "onElementChanged",
["button"] = "onClicked",
["sprite-button"] = "onClicked",
["drop-down"] = "onSelectionStateChanged",
["list-box"] = "onSelectionStateChanged",
["textfield"] = "onTextChanged",
["text-box"] = "onTextChanged",
["slider"] = "onValueChanged",
["tabbed-pane"] = "onSelectedTabChanged",
["switch"] = "onSwitchStateChanged",
}
local specialParameters = {
children = true,
onCreated = true,
onChanged = true,
onCheckedStateChanged = true,
onClicked = true,
onElementChanged = true,
onSelectionStateChanged = true,
onTextChanged = true,
onValueChanged = true,
onConfirmed = true,
onSelectedTabChanged = true,
onSwitchStateChanged = true,
onLocationChanged = true,
ID = true,
root = true,
unique = true,
content = true,
}
local function registerHandlers(template, ID)
ID = ID or template.ID
local elemName = template.name
for _,event in pairs(events) do -- register event handler functions with generated ID
if template[event] then
eventHandlers[ID..";"..elemName] = template[event]
end
end
if template.onChanged and onChangedEvents[template.type] then -- register unified onChanged event
eventHandlers[ID..";"..elemName] = template.onChanged
end
if template.children then -- recursively register childs event handlers
for _,child in ipairs(template.children) do
registerHandlers(child, ID)
end
end
if template.type == "tab" and template.content then -- build tab contents
registerHandlers(template.content, ID)
end
end
function gui.registerTemplates(obj) -- called when lua script is loaded initially, assigns unqiue ID to every template
for name,template in pairs(obj.templates) do
template.ID = obj.class..";"..name -- unique ID, obj.class is assigned by framework.lua
registerHandlers(template)
end
end
local function getParameters(template, name)
local parameters = { name = name }
for param,value in pairs(template) do
if param ~= "name" and not specialParameters[param] then
if param == "style" and _(value):is("table") then
parameters.style = value.parent
else
parameters[param] = value
end
end
end
return parameters
end
local function getDefaultRoot(template, player)
if template.type == "button" or template.type == "sprite-button" then
return mod_gui.get_button_flow(player)
end
return mod_gui.get_frame_flow(player)
end
local function getRoot(template, player)
if template.root then
return template.root(player, getDefaultRoot(template, player))
end
return getDefaultRoot(template, player)
end
function gui.create(player, template, data, parent, ID) -- recursively builds a gui from a template
ID = ID or template.ID
parent = parent or getRoot(template, player)
local elemName = template.name
local uniqueElemName = elemName
if template.unique == false then
-- duplicates are named NAME[i] where i is the index starting at 1 (-> multiple can exist at once)
for i = 1, MAX_DUPLICATES do
if not parent[elemName.."#"..i] then
uniqueElemName = elemName.."#"..i
break
end
end
elseif template.ID then -- destroy any existing instances of this template if it's not unique
gui.destroy(player, template, parent)
end
local created = parent.add(getParameters(template, uniqueElemName)) -- create gui element
-- Apply custom styles
local style = _(template.style)
if style:is("table") then
_(created.style):set(style:unlesskey("parent"))
end
local index = created.player_index
for event,list in pairs(global.guiEvents) do -- register events
if template[event] then
list[index] = list[index] or {}
list[index][created.index] = ID..";"..elemName
end
end
if template.onChanged and onChangedEvents[template.type] then -- register unified onChanged event
local list = global.guiEvents[onChangedEvents[template.type]]
list[index] = list[index] or {}
list[index][created.index] = ID..";"..elemName
end
if template.children then -- build children
for _,child in ipairs(template.children) do
gui.create(player, child, data, created, ID)
end
end
if template.type == "tab" and parent.type == "tabbed-pane" and template.content then -- build tab contents
parent.add_tab(created, gui.create(player, template.content, data, parent, ID))
end
if template.onCreated then template.onCreated(created, data) end -- fire onCreated event
return created
end
function gui.get(player, template, parent) -- get the gui element matching the given template (unique templates only)
parent = parent or getRoot(template, player)
return parent[template.name]
end
function gui.getAll(player, template, parent) -- get table of all gui elements matching the given template (unique and non-unique)
parent = parent or getRoot(template, player)
local result = {}
if template.unique == false then -- destroy all
local regex = "^"..util.escape(template.name).."#[0-9]+$"
for _,child in pairs(parent.children) do
if child.name:find(regex) then
result[#result + 1] = child
end
end
else
result[1] = gui.get(player, template, parent)
end
return result
end
function gui.destroy(player, template, parent) -- destroy all gui elements matching the given template
for _,child in ipairs(gui.getAll(player, template, parent)) do
util.destroyIfValid(child)
end
end
local function handleGuiEvent(event, name)
if event.element.get_mod() == script.mod_name then
local handlers = global.guiEvents[name][event.player_index]
if handlers then
local handler = handlers[event.element.index]
if handler and eventHandlers[handler] then eventHandlers[handler](event.element, event) end
end
end
end
-- register game gui events
for gameEvent,translated in pairs(events) do
gui[gameEvent] = function(event)
handleGuiEvent(event, translated)
end
end
return gui

View File

@@ -0,0 +1,508 @@
local this = {}
local util = scripts.util
local metatables = scripts.metatables
local type, rawget, rawset, pairs, ipairs = type, rawget, rawset, pairs, ipairs
function this.on_scripts_initialized()
require("scripts.helpers.LuaControl")
require("scripts.helpers.LuaEntity")
require("scripts.helpers.LuaItemPrototype")
require("scripts.helpers.LuaPlayer")
require("scripts.helpers.LuaRecipe")
require("scripts.helpers.Position")
require("scripts.helpers.BoundingBox")
end
metatables.helpers = {
__index = function(t, k)
return this[k] or rawget(t, "__on")[k]
end,
__newindex = function(t, k, v)
rawget(t, "__on")[k] = v
end,
__len = function(t)
return #rawget(t, "__on")
end,
}
function this.on(obj) -- wraps object in table with helper methods
if type(obj) == "table" and not obj.__self and rawget(obj, "__on") then -- reapply metatable
return metatables.use(obj, "helpers")
else -- new metatable
return metatables.use({ __on = obj }, "helpers")
end
end
local _ = this.on
function this:toPlain()
return rawget(self, "__on")
end
local conditions = {
["nil"] = function(obj) return obj == nil end,
["any"] = function(obj) return obj ~= nil end,
["string"] = function(obj) return type(obj) == "string" end,
["number"] = function(obj) return type(obj) == "number" end,
["table"] = function(obj) return type(obj) == "table" end,
["object"] = function(obj) return type(obj) == "table" and obj.__self end,
["empty"] = util.isEmpty,
["filled"] = util.isFilled,
["valid"] = util.isValid,
["valid stack"] = util.isValidStack,
["valid player"] = util.isValidPlayer,
-- Custom type conditions
["crafting machine"] = util.isCraftingMachine,
["fuel"] = function(obj) return obj.object_name == "LuaItemStack" and obj.prototype.fuel_category ~= nil or obj.fuel_category ~= nil end,
["ammo"] = function(obj) return obj.type == "ammo" end,
}
local function applyNot(notModeActive, value)
if notModeActive then
return not value
else
return value
end
end
function this:is(...)
local isargs = {...}
local obj = rawget(self, "__on")
local notModeActive = false
local result = true
for __,condition in ipairs(isargs) do
if condition == "not" then
notModeActive = not notModeActive -- toggle notModeActive
else
if type(condition) == "table" then -- custom field check
local value = obj
for __,key in ipairs(condition) do
value = value[key]
end
local nestedIs = condition.is or condition.isnot -- nested Is check on custom field
if condition.is ~= nil then
value = type(nestedIs) == "table" and _(value):is(unpack(nestedIs)) or _(value):is(nestedIs)
elseif condition.isnot ~= nil then
value = type(nestedIs) == "table" and _(value):isnot(unpack(nestedIs)) or _(value):isnot(nestedIs)
end
result = result and applyNot(notModeActive, value)
elseif type(condition) == "function" then -- custom function
result = result and applyNot(notModeActive, condition(obj))
elseif conditions[condition] then -- normal condition check
result = result and applyNot(notModeActive, conditions[condition](obj))
else -- direct value check
result = result and applyNot(notModeActive, obj == condition)
end
notModeActive = false
-- early return if condition unmet
if not result then return result end
end
end
return result
end
function this:isnot(...)
local isargs = {...}
local obj = rawget(self, "__on")
local notModeActive = false
local result = true
for __,condition in ipairs(isargs) do
if condition == "not" then
notModeActive = not notModeActive -- toggle notModeActive
else
if type(condition) == "table" then -- custom field check
local value = obj
for __,key in ipairs(condition) do
value = value[key]
end
local nestedIs = condition.is or condition.isnot -- nested Is check on custom field
if condition.is ~= nil then
value = type(nestedIs) == "table" and _(value):is(unpack(nestedIs)) or _(value):is(nestedIs)
elseif condition.isnot ~= nil then
value = type(nestedIs) == "table" and _(value):isnot(unpack(nestedIs)) or _(value):isnot(nestedIs)
end
result = result and applyNot(notModeActive, value)
elseif type(condition) == "function" then -- custom function
result = result and applyNot(notModeActive, condition(obj))
elseif conditions[condition] then -- normal condition check
result = result and applyNot(notModeActive, conditions[condition](obj))
else -- direct value check
result = result and applyNot(notModeActive, obj == condition)
end
notModeActive = false
-- early return if condition unmet
if not result then return not result end
end
end
return not result
end
function this:has(condition, ...)
return self:is({is=condition, ...})
end
function this:hasnot(condition, ...)
return self:isnot({is=condition, ...})
end
function this:each(func)
local obj = rawget(self, "__on")
local iter = metatables.uses(obj, "entityAsIndex") and util.epairs or pairs
for k,v in iter(obj) do
func(k, v)
end
return self
end
function this:where(...)
local args = {...}
local func = args[#args]
local obj = rawget(self, "__on")
local iter = metatables.uses(obj, "entityAsIndex") and util.epairs or pairs
-- user passed anonymouse iterator function to where() directly (as last argument)
if #args > 1 and type(func) == "function" then
args[#args] = nil
for k,v in iter(obj) do
if _(v or k):is(unpack(args)) then
func(k, v)
end
end
return self
else -- or only filter out results and return table
local result = {}
for k,v in iter(obj) do
if _(v or k):is(...) then
result[k] = v
end
end
return _(result)
end
end
function this:unless(...) -- wherenot
local args = {...}
local func = args[#args]
local obj = rawget(self, "__on")
local iter = metatables.uses(obj, "entityAsIndex") and util.epairs or pairs
if #args > 1 and type(func) == "function" then -- if user passed anonymouse function to where() directly (as last argument)
args[#args] = nil
for k,v in iter(obj) do
if _(v or k):isnot(unpack(args)) then
func(k, v)
end
end
return self
else -- else only filter out results
local result = {}
for k,v in iter(obj) do
if _(v or k):isnot(...) then
result[k] = v
end
end
return _(result)
end
end
function this:wherekey(...)
local args = {...}
local func = args[#args]
local obj = rawget(self, "__on")
local iter = metatables.uses(obj, "entityAsIndex") and util.epairs or pairs
-- user passed anonymouse iterator function to where() directly (as last argument)
if #args > 1 and type(func) == "function" then
args[#args] = nil
for k,v in iter(obj) do
if _(k):is(unpack(args)) then
func(k, v)
end
end
return self
else -- or only filter out results and return table
local result = {}
for k,v in iter(obj) do
if _(k):is(...) then
result[k] = v
end
end
return _(result)
end
end
function this:unlesskey(...) -- wherenot
local args = {...}
local func = args[#args]
local obj = rawget(self, "__on")
local iter = metatables.uses(obj, "entityAsIndex") and util.epairs or pairs
if #args > 1 and type(func) == "function" then -- if user passed anonymouse function to where() directly (as last argument)
args[#args] = nil
for k,v in iter(obj) do
if _(k):isnot(unpack(args)) then
func(k, v)
end
end
return self
else -- else only filter out results
local result = {}
for k,v in iter(obj) do
if _(k):isnot(...) then
result[k] = v
end
end
return _(result)
end
end
function this:wherepair(...)
local args = {...}
local func = args[#args]
local obj = rawget(self, "__on")
local iter = metatables.uses(obj, "entityAsIndex") and util.epairs or pairs
-- user passed anonymouse iterator function to where() directly (as last argument)
if #args > 1 and type(func) == "function" then
args[#args] = nil
for k,v in iter(obj) do
if _({k,v}):is(unpack(args)) then
func(k, v)
end
end
return self
else -- or only filter out results and return table
local result = {}
for k,v in iter(obj) do
if _({k,v}):is(...) then
result[k] = v
end
end
return _(result)
end
end
function this:unlesspair(...) -- wherenot
local args = {...}
local func = args[#args]
local obj = rawget(self, "__on")
local iter = metatables.uses(obj, "entityAsIndex") and util.epairs or pairs
if #args > 1 and type(func) == "function" then -- if user passed anonymouse function to where() directly (as last argument)
args[#args] = nil
for k,v in iter(obj) do
if _({k,v}):isnot(unpack(args)) then
func(k, v)
end
end
return self
else -- else only filter out results
local result = {}
for k,v in iter(obj) do
if _({k,v}):isnot(...) then
result[k] = v
end
end
return _(result)
end
end
function this:wherehas(condition, ...)
local args = {...}
local count = #args
local func = args[count]
args.is = condition
if count > 1 and type(func) == "function" then
args[count] = nil
return self:where(args, func)
else
return self:where(args)
end
end
function this:unlesshas(condition, ...)
local args = {...}
local count = #args
local func = args[count]
args.is = condition
if count > 1 and type(func) == "function" then
args[count] = nil
return self:unless(args, func)
else
return self:unless(args)
end
end
function this:contains(value) -- table contains value
local obj = rawget(self, "__on")
local iter = metatables.uses(obj, "entityAsIndex") and util.epairs or pairs
for k,v in iter(obj) do
if v == value then
return true
end
end
return false
end
function this:set(values)
local obj = rawget(self, "__on")
values = rawget(values, "__on") or values
for k,v in pairs(values) do
obj[k] = v
end
return self
end
function this:map(func)
local obj = rawget(self, "__on")
local iter = metatables.uses(obj, "entityAsIndex") and util.epairs or pairs
local result = {}
for k,v in iter(obj) do
local k2,v2 = func(k,v)
if k2 == nil then
result[#result + 1] = v2
else
result[k2] = v2
end
end
return _(result)
end
function this:toArray()
return self:map(function(k,v)
return nil, v
end)
end
function this:keys()
return self:map(function(k)
return nil, k
end)
end
function this:sort(...)
local obj = rawget(self, "__on")
table.sort(obj, ...)
return self
end
function this:sum(key)
local obj = rawget(self, "__on")
local iter = metatables.uses(obj, "entityAsIndex") and util.epairs or pairs
local result = 0
if type(key) == "function" then
for k,v in iter(obj) do
result = result + key(k,v)
end
else
for k,v in iter(obj) do
result = result + v[key]
end
end
return result
end
function this:groupBy(key)
local obj = rawget(self, "__on")
local iter = metatables.uses(obj, "entityAsIndex") and util.epairs or pairs
local result = {}
if type(key) == "function" then
for k,v in iter(obj) do
local value = key(k,v)
if value ~= nil then
result[value] = result[value] or {}
if type(k) == "number" then
result[value][#result[value] + 1] = v
else
result[value][k] = v
end
end
end
else
for k,v in iter(obj) do
local value = v[key]
if value ~= nil then
result[value] = result[value] or {}
if type(k) == "number" then
result[value][#result[value] + 1] = v
else
result[value][k] = v
end
end
end
end
return _(result)
end
return this

View File

@@ -0,0 +1,30 @@
local box = scripts.helpers
local _ = scripts.helpers.on
-- Helper functions for BoundingBox --
function box:boxoffset(arg1, arg2) -- accepts Position or x,y
local offx, offy = arg1, arg2
if arg2 == nil then offx, offy = arg1.x, arg1.y end
local x1, y1, x2, y2 = self.left_top.x, self.left_top.y, self.right_bottom.x, self.right_bottom.y
return _{left_top = {x = x1 + offx, y = y1 + offy}, right_bottom = {x = x2 + offx, y = y2 + offy}}
end
function box:expand(tilesx, tilesy) -- by number of tiles (y defaults to x)
tilesy = tilesy or tilesx
local x1, y1, x2, y2 = self.left_top.x, self.left_top.y, self.right_bottom.x, self.right_bottom.y
return _{left_top = {x = x1 - tilesx, y = y1 - tilesy}, right_bottom = {x = x2 + tilesx, y = y2 + tilesy}}
end
function box:boxwidth()
return self.right_bottom.x - self.left_top.x
end
function box:boxheight()
return self.right_bottom.y - self.left_top.y
end
function box:area()
return (self.right_bottom.x - self.left_top.x) * (self.right_bottom.y - self.left_top.y)
end

View File

@@ -0,0 +1,239 @@
local config = require("config")
local control = scripts.helpers
local _ = scripts.helpers.on
-- Helper functions for LuaControl --
function control:itemcount(...)
if self.is_player() then
return self:playeritemcount(...)
else
return self.get_item_count(...)
end
end
function control:requests(...)
if self.is_player() then
return _(self.character):entityrequests(...)
else
return self:entityrequests(...)
end
end
function control:request(...)
if self.is_player() then
return _(self.character):entityrequest(...)
else
return self:entityrequest(...)
end
end
function control:remainingRequest(item)
return self:request(item) - self.get_item_count(item)
end
function control:inventory(name)
if name == nil or name == "main" then -- get main inventory
return self.get_main_inventory() or
(self.type == "container" and self.get_inventory(defines.inventory.chest)) or
(self.type == "logistic-container" and self.get_inventory(defines.inventory.chest)) or
(self.type == "car" and self.get_inventory(defines.inventory.car_trunk)) or
(self.type == "spider-vehicle" and self.get_inventory(defines.inventory.car_trunk)) or
(self.type == "cargo-wagon" and self.get_inventory(defines.inventory.cargo_wagon)) or
(self.type == "rocket-silo" and self.get_inventory(defines.inventory.rocket_silo_rocket))
elseif name == "input" then
return (self.type == "furnace" and self.get_inventory(defines.inventory.furnace_source)) or
(self.type == "assembling-machine" and self.get_inventory(defines.inventory.assembling_machine_input)) or
(self.type == "lab" and self.get_inventory(defines.inventory.lab_input)) or
(self.type == "rocket-silo" and self.get_inventory(defines.inventory.assembling_machine_input))
elseif name == "output" then
return self.get_output_inventory()
elseif name == "modules" then
return self.get_module_inventory()
elseif name == "ammo" then
return (self.type == "ammo-turret" and self.get_inventory(defines.inventory.turret_ammo)) or
(self.type == "car" and self.get_inventory(defines.inventory.car_ammo)) or
(self.type == "spider-vehicle" and self.get_inventory(defines.inventory.car_ammo)) or
(self.type == "artillery-wagon" and self.get_inventory(defines.inventory.artillery_wagon_ammo)) or
(self.type == "artillery-turret" and self.get_inventory(defines.inventory.artillery_turret_ammo)) or
(self.type == "character" and self.get_inventory(defines.inventory.character_ammo)) or
(self.type == "character" and self.get_inventory(defines.inventory.editor_ammo))
elseif name == "fuel" then
return self.get_fuel_inventory()
elseif name == "burnt_result" then
return self.get_burnt_result_inventory()
end
-- get specific inventory
return self.get_inventory(defines.inventory[name])
end
function control:contents(name)
if name == nil and self.is_player() then
return self:playercontents()
end
local inv = self:inventory(name)
if _(inv):isnot("valid") then return {} end
return inv.get_contents()
end
local function insert(self, name, item, amount)
if amount <= 0 then return 0 end
local inv = self:inventory(name)
if inv then
-- inv.sort_and_merge()
local inserted = inv.insert{ name = item, count = amount }
if inserted < amount then -- retry for things like furnace ingredients (can be overfilled)
inserted = inserted + inv.insert{ name = item, count = amount - inserted }
end
return inserted
end
return 0
end
-- priority insert with fuel and ammo limits
function control:customInsert(player, item, amount, takenFromCar, takenFromTrash, replaceItems, useFuelLimit, useAmmoLimit, useRequestLimit, allowed)
if amount <= 0 then return 0 end
local inserted = 0
local prototype = _(game.item_prototypes[item])
-- allow/disallow insertion into specific inventories by passing table with true/false values (default is allow)
allowed = _({
fuel = true, -- set default values
ammo = true,
input = true,
output = true,
modules = true,
roboport = true,
main = true,
}):set(allowed or {}):toPlain()
if allowed.fuel and prototype:is("fuel") then
local inv = self:inventory("fuel")
if inv then
local limit = useFuelLimit and math.min(amount, math.max(0, player:itemLimit(prototype, config.fuelLimitProfiles) - inv.get_item_count(item))) or amount
local insertedHere = insert(self, "fuel", item, limit)
limit = limit - insertedHere
-- no space left --> replace inferior items
if replaceItems and limit > 0 then
for __,inferiorFuel in pairs(global.fuelList[prototype.fuel_category]) do
if inferiorFuel.name == prototype.name or limit <= 0 then break end
local returnToPlayer = 0
while limit > 0 do
local stack = inv.find_item_stack(inferiorFuel.name)
local returnCount = stack and stack.count or 0
if stack and stack.set_stack{ name = item, count = limit } then
limit = limit - stack.count
insertedHere = insertedHere + stack.count
returnToPlayer = returnToPlayer + returnCount
else
break
end
end
if returnToPlayer > 0 then
player:returnItems(inferiorFuel.name, returnToPlayer, takenFromCar, takenFromTrash)
end
end
end
inserted = inserted + insertedHere
amount = amount - insertedHere
end
end
if amount <= 0 then return inserted end
if allowed.ammo and prototype:is("ammo") then
local inv = self:inventory("ammo")
if inv then
local limit = useAmmoLimit and math.min(amount, math.max(0, player:itemLimit(prototype, config.ammoLimitProfiles) - inv.get_item_count(item))) or amount
local insertedHere = insert(self, "ammo", item, limit)
limit = limit - insertedHere
-- no space left --> replace inferior items
if replaceItems and limit > 0 then
for __,inferiorAmmo in pairs(global.ammoList[prototype.get_ammo_type().category]) do
if inferiorAmmo.name == prototype.name or limit <= 0 then break end
local returnToPlayer = 0
while limit > 0 do
local stack = inv.find_item_stack(inferiorAmmo.name)
local returnCount = stack and stack.count or 0
if stack and stack.set_stack{ name = item, count = limit } then
limit = limit - stack.count
insertedHere = insertedHere + stack.count
returnToPlayer = returnToPlayer + returnCount
else
break
end
end
if returnToPlayer > 0 then
player:returnItems(inferiorAmmo.name, returnToPlayer, takenFromCar, takenFromTrash)
end
end
end
inserted = inserted + insertedHere
amount = amount - insertedHere
end
end
if amount <= 0 then return inserted end
if allowed.input then
local insertedHere = insert(self, "input", item, amount)
inserted = inserted + insertedHere
amount = amount - insertedHere
if insertedHere > 0 then allowed.main = false end
end
if amount <= 0 then return inserted end
if allowed.output and self:recipe():hasProduct(item) then
local insertedHere = insert(self, "output", item, amount)
inserted = inserted + insertedHere
amount = amount - insertedHere
end
if amount <= 0 then return inserted end
if allowed.modules then
local insertedHere = insert(self, "modules", item, amount)
inserted = inserted + insertedHere
amount = amount - insertedHere
if insertedHere > 0 then allowed.main = false end
end
if allowed.roboport and self.type == "roboport" then
if amount <= 0 then return inserted end
local insertedHere = insert(self, "roboport_robot", item, amount)
inserted = inserted + insertedHere
amount = amount - insertedHere
if amount <= 0 then return inserted end
insertedHere = insert(self, "roboport_material", item, amount)
inserted = inserted + insertedHere
amount = amount - insertedHere
end
if amount <= 0 then return inserted end
if allowed.main then
local limit = useRequestLimit and math.min(amount, math.max(0, self:remainingRequest(item))) or amount
local insertedHere = insert(self, "main", item, limit)
inserted = inserted + insertedHere
amount = amount - insertedHere
end
return inserted
end

View File

@@ -0,0 +1,91 @@
local util = scripts.util
local config = require("config")
local entity = scripts.helpers
local _ = scripts.helpers.on
-- Helper functions for LuaEntity --
function entity:entityrequests() -- fetch all requests as table
local requests = {}
if self.request_slot_count > 0 then
for i = 1, self.request_slot_count do
local request = self.get_request_slot(i)
if request then
local item, amount = request.name, request.count
if amount > 0 then
requests[item] = math.max(requests[item] or 0, amount)
end
end
end
end
return requests
end
function entity:entityrequest(item) -- fetch specific item request
local count = 0
if self.request_slot_count > 0 then
for i = 1, self.request_slot_count do
local request = self.get_request_slot(i)
if request and request.name == item and request.count > count then
count = math.max(count, request.count)
end
end
end
return count
end
function entity:logisticSlots() -- fetch all requests as table
local logisticSlots = {}
if self.request_slot_count > 0 then
for i = 1, self.request_slot_count do
local slot = self.get_personal_logistic_slot(i)
if slot and slot.name then
logisticSlots[slot.name] = slot
end
end
end
return logisticSlots
end
function entity:isIgnored(player)
return not global.allowedEntities[self.name] or
global.settings[player.index].ignoredEntities[self.name] or
global.remoteIgnoredEntities[self.name]
end
function entity:recipe()
return _(self.get_recipe() or (self.type == "furnace" and self.previous_recipe))
end
-- for turrets
function entity:supportsAmmo(item)
local ammoType = item.get_ammo_type("turret") or item.get_ammo_type()
if ammoType then
local attackParameters = self.prototype.attack_parameters
if attackParameters then
return _(attackParameters.ammo_categories):contains(ammoType.category)
elseif self.type == "artillery-turret" or self.type == "artillery-wagon" then
return ammoType.category == "artillery-shell"
end
end
return false
end
-- for furnace
function entity:canSmelt(item)
return #game.get_filtered_recipe_prototypes({
unpack(_(self.prototype.crafting_categories):map(function(category)
return nil, {filter = "category", category = category, mode = "or"}
end):toPlain()),
{filter = "has-ingredient-item", elem_filters = {{filter = "name", name = item}}, mode = "and"},
}) > 0
end

View File

@@ -0,0 +1,124 @@
local this = {}
local config = require("config")
local prototype = scripts.helpers
local _ = scripts.helpers.on
function prototype:calculateDamage()
local type = self.get_ammo_type()
return type and type.action and this.damageFromActions(type.action) or 0
end
function prototype:calculateRadius()
local type = self.get_ammo_type()
return type and type.action and this.radiusFromActions(type.action) or 0
end
function this.damageFromActions(actions)
return _(actions)
:sum(function(__,action)
return this.damageFromAction(action) * action.repeat_count
end)
end
function this.damageFromAction(action)
if action.action_delivery then
local multiplier = action.radius and action.radius * action.radius * math.pi or 1
return _(action.action_delivery)
:sum(function(__,delivery)
return this.deliveryDamage(delivery) * multiplier
end)
end
return 0
end
function this.deliveryDamage(delivery)
if delivery.type == 'instant' and delivery.target_effects then
return _(delivery.target_effects)
:sum(function(__,effect)
return (effect.action and this.damageFromActions(effect.action) or 0) or
(effect.type == 'damage' and effect.damage.amount or 0) or
(effect.type == 'create-entity' and this.entityAttackDamage(effect.entity_name) or 0)
end)
elseif delivery.projectile then
return this.entityAttackDamage(delivery.projectile)
elseif delivery.stream then
return this.entityAttackDamage(delivery.stream)
end
return 0
end
function this.entityAttackDamage(name)
local entity = game.entity_prototypes[name]
local damage = 0
if entity then
if entity.attack_result then
damage = damage + this.damageFromActions(entity.attack_result)
end
if entity.final_attack_result then
damage = damage + this.damageFromActions(entity.final_attack_result)
end
end
return damage
end
function this.radiusFromActions(actions)
return _(actions)
:sum(function(__,action)
return this.radiusFromAction(action)
end)
end
function this.radiusFromAction(action)
if action.action_delivery then
return _(action.action_delivery)
:sum(function(__,delivery)
return this.radiusOfDelivery(delivery)
end)
+ (action.radius or 0)
end
return 0
end
function this.radiusOfDelivery(delivery)
if delivery.type == 'instant' and delivery.target_effects then
return _(delivery.target_effects)
:sum(function(__,effect)
if effect.action then
return this.radiusFromActions(effect.action) + (effects.action.radius or 0)
end
return 0
end)
elseif delivery.projectile then
return this.radiusFromEntity(delivery.projectile)
elseif delivery.stream then
return this.radiusFromEntity(delivery.stream)
end
end
function this.radiusFromEntity(name)
local entity = game.entity_prototypes[name]
local radius = 0
if entity then
if entity.attack_result then
radius = radius + this.radiusFromActions(entity.attack_result)
end
if entity.final_attack_result then
radius = radius + this.radiusFromActions(entity.final_attack_result)
end
end
return radius
end

View File

@@ -0,0 +1,171 @@
local config = require("config")
local player = scripts.helpers
local _ = scripts.helpers.on
-- Helper functions for LuaPlayer --
function player:setting(name)
if name == "enableDragDistribute" then
if settings.global["disable-distribute"].value then return false end
elseif name == "enableInventoryCleanupHotkey" then
if settings.global["disable-inventory-cleanup"].value then return false end
elseif name == "cleanupDropRange" then
return math.min(global.settings[self.index].cleanupDropRange, settings.global["global-max-inventory-cleanup-range"].value)
end
local setting = global.settings[self.index][name]
if setting == nil and self.mod_settings[name] then return self.mod_settings[name].value end
return setting
end
function player:changeSetting(name, newValue)
local setting = global.settings[self.index]
if setting[name] == nil and self.mod_settings[name] then
self.mod_settings[name] = { value = newValue }
else
setting[name] = newValue
end
end
function player:droprange()
return math.min(self.reach_distance * config.rangeMultiplier, self:setting("cleanupDropRange"))
end
function player:trashItems()
local cleanupRequestOverflow = self:setting("cleanupRequestOverflow")
local defaultTrash = global.defaultTrash
local trash = self:contents("character_trash")
local logisticSlots = self:has("valid", "character") and _(self.character):logisticSlots() or {}
for item,count in pairs(self:contents()) do
local targetAmount = count
local slot = logisticSlots[item]
if not slot then -- default if no logistic slot with this item
targetAmount = defaultTrash[item]
elseif cleanupRequestOverflow and slot.min > 0 then -- request overflow
targetAmount = slot.min
elseif slot.max < 4294967295 then -- max value set to infinity = no autotrash
targetAmount = slot.max
end
if targetAmount ~= nil then
local surplus = count - targetAmount
if surplus > 0 then trash[item] = (trash[item] or 0) + surplus end
end
end
return trash
end
function player:playeritemcount(item, includeInv, includeCar)
local count = 0
local cursor_stack = self.cursor_stack
if cursor_stack and cursor_stack.valid_for_read and cursor_stack.name == item then
count = count + cursor_stack.count
elseif not includeInv and not includeCar then
return global.cache[self.index].cursorStackCount or 0
end
if includeInv then
local mainInv = self:inventory();
if _(mainInv):is("valid") then count = count + mainInv.get_item_count(item) end
end
if includeCar and self.driving and self:has("valid", "vehicle") then
local vehicleInv = _(self.vehicle):inventory("car_trunk")
if _(vehicleInv):is("valid") then count = count + vehicleInv.get_item_count(item) end
end
return count
end
function player:playercontents()
local contents = self:contents("main")
local cursor_stack = self.cursor_stack
if cursor_stack and cursor_stack.valid_for_read then
local item = cursor_stack.name
contents[item] = (contents[item] or 0) + cursor_stack.count
end
return contents
end
function player:removeItems(item, amount, takeFromInv, takeFromCar, takeFromTrash)
local removed = 0
if takeFromTrash then
local trash = self:inventory("character_trash")
if _(trash):is("valid") then
removed = trash.remove{ name = item, count = amount }
if amount <= removed then return removed end
end
end
if takeFromInv then
local main = self:inventory()
if _(main):is("valid") then
removed = removed + main.remove{ name = item, count = amount - removed }
if amount <= removed then return removed end
end
end
local cursor_stack = self.cursor_stack
if cursor_stack and cursor_stack.valid_for_read and cursor_stack.name == item then
local result = math.min(cursor_stack.count, amount - removed)
removed = removed + result
cursor_stack.count = cursor_stack.count - result
if amount <= removed then return removed end
elseif not takeFromInv and not takeFromCar and not takeFromTrash then
local main = self:inventory()
if _(main):is("valid") then
return removed + main.remove{ name = item, count = amount - removed }
end
end
if takeFromCar and self.driving and self:has("valid", "vehicle") then
local vehicleInv = _(self.vehicle):inventory("car_trunk")
if _(vehicleInv):is("valid") then removed = removed + vehicleInv.remove{ name = item, count = amount - removed } end
end
return removed
end
function player:returnItems(item, amount, takenFromCar, takenFromTrash)
local remaining = amount - self.insert{ name = item, count = amount }
if remaining > 0 and takenFromCar and self.driving and self:has("valid", "vehicle") then
local vehicleInv = _(self.vehicle):inventory("car_trunk")
if _(vehicleInv):is("valid") then remaining = remaining - vehicleInv.insert{ name = item, count = remaining } end
end
if remaining > 0 and takenFromTrash then
local trash = self:inventory("character_trash")
if _(trash):is("valid") then remaining = remaining - trash.insert{ name = item, count = remaining } end
end
if remaining > 0 then
self.surface.spill_item_stack(self.position, { name = item, count = remaining }, false)
end
end
function player:itemLimit(prototype, profile)
if profile then
local limit = self:setting(profile.valueSetting)
local type = self:setting(profile.typeSetting)
if type == "items" then
return math.ceil(limit)
elseif type == "stacks" then
return math.ceil(limit * (prototype.stack_size or 1))
elseif type == "mj" then
return math.ceil((limit * 1000000) / (prototype.fuel_value or 1))
end
end
return math.huge
end

View File

@@ -0,0 +1,58 @@
local recipe = scripts.helpers
local _ = scripts.helpers.on
-- Helper functions for LuaRecipe --
function recipe:ingredientcount(item) -- get count of a specific item in recipe ingredients
if self:is("valid") then
for __,ingredient in pairs(self.ingredients) do
if ingredient.name == item then return ingredient.amount end
end
end
return 0
end
function recipe:productcount(item) -- get count of a specific item in recipe products
if self:is("valid") then
for __,product in pairs(self.products) do
if product.name == item then
return product.amount or product.amount_min or product.amount_max or product.probability or 1
end
end
end
return 0
end
function recipe:hasIngredient(item)
return self:ingredientcount(item) > 0
end
function recipe:hasProduct(item)
return self:productcount(item) > 0
end
function recipe:ingredientmap() -- ingredient table: item name --> amount
local ingredients = {}
if self:is("valid") then
for __,ingredient in pairs(self.ingredients) do
if ingredient.type == "item" then ingredients[ingredient.name] = ingredient.amount end
end
end
return ingredients
end
function recipe:productmap() -- product table: item name --> amount
local products = {}
if self:is("valid") then
for __,product in pairs(self.products) do
if product.type == "item" then products[product.name] = product.amount end
end
end
return products
end

View File

@@ -0,0 +1,24 @@
local pos = scripts.helpers
local _ = scripts.helpers.on
-- Helper functions for Position --
function pos:offset(...)
if self.left_top ~= nil then
return self:boxoffset(...)
else
return self:posoffset(...)
end
end
function pos:posoffset(arg1, arg2) -- accepts Position or x,y
local offx, offy = arg1, arg2
if arg2 == nil then offx, offy = arg1.x, arg1.y end
return _{x = self.x + offx, y = self.y + offy}
end
function pos:perimeter(radiusx, radiusy) -- with given radius (y radius defaults to x radius)
radiusy = radiusy or radiusx
return _{left_top = {x = self.x - radiusx, y = self.y - radiusy}, right_bottom = {x = self.x + radiusx, y = self.y + radiusy}}
end

View File

@@ -0,0 +1,129 @@
local this = {}
local getmetatable, setmetatable, type, rawget, rawset = getmetatable, setmetatable, type, rawget, rawset
function this.uses(obj, name)
return getmetatable(obj) == this[name]
end
function this.use(obj, name)
if type(obj) == "table" then
rawset(obj, "__mt", name)
return setmetatable(obj, this[name])
end
end
function this.set(obj, name) -- not persistent
if type(obj) == "table" then
return setmetatable(obj, this[name])
end
end
function this.new(name)
return setmetatable({ __mt = name }, this[name])
end
function this.refresh(obj)
if type(obj) == "table" and not obj.__self then
for key,val in pairs(obj) do
this.refresh(val)
end
local name = rawget(obj, "__mt")
if type(name) == "string" then
local mt = this[name]
if mt then return setmetatable(obj, mt) end
end
end
end
this.entityAsIndex = { -- metatable for using an entity as a table index
__index = function (tbl, entity)
if type(entity) == "table" and entity.valid then
local id = entity.unit_number
if id then
local tblId = rawget(tbl, "id")
if tblId then return tblId[id] end
else
local tblPos = rawget(tbl, "pos")
if tblPos then
local surface, pos = entity.surface.name, entity.position
local x, y = pos.x, pos.y
local tbls = tblPos[surface]
if tbls then
local tblsx = tbls[x]
if tblsx then return tblsx[y] end
end
end
end
end
end,
__newindex = function (tbl, entity, value)
local id = entity.unit_number
local count = rawget(tbl, "count") or 0
if id then -- entities indexed by unit number
local tblId = rawget(tbl, "id")
if tblId then
local oldvalue = tblId[id]
if value ~= oldvalue then
if value == nil then
rawset(tbl, "count", count - 1)
else
rawset(tbl, "count", count + 1)
end
tblId[id] = value
end
elseif value ~= nil then
rawset(tbl, "id", { [entity.unit_number] = value })
rawset(tbl, "count", count + 1)
end
else -- other entities that don't support unit number indexed by their surface and position
local surface, pos = entity.surface.name, entity.position
local x, y = pos.x, pos.y
local tblPos = rawget(tbl, "pos")
if tblPos then
local tbls = tblPos[surface]
if tbls then
local tblsx = tbls[x]
if tblsx then
local oldvalue = tblsx[y]
if value ~= oldvalue then
if value == nil then
rawset(tbl, "count", count - 1)
else
rawset(tbl, "count", count + 1)
end
tblsx[y] = value
end
elseif value ~= nil then
tbls[x] = { [y] = value }
rawset(tbl, "count", count + 1)
end
elseif value ~= nil then
tblPos[surface] = { [x] = { [y] = value } }
rawset(tbl, "count", count + 1)
end
elseif value ~= nil then
rawset(tbl, "pos", { [surface] = { [x] = { [y] = value } } })
rawset(tbl, "count", count + 1)
end
end
end,
__len = function (tbl)
return rawget(tbl, "count") or 0
end,
}
return this

View File

@@ -0,0 +1,41 @@
-- Sets up the global table and parses settings
local this = {}
-- Returns the version of this remote API
-- Usage example: remote.call("even-distribution", "version")
function this.version()
return 1
end
-- Add an entity to be ignored by CTRL+Click Drag and SHIFT+C
-- Usage example: remote.call("even-distribution", "add_ignored_entity", "wooden-chest")
function this.add_ignored_entity(entity)
global.remoteIgnoredEntities = global.remoteIgnoredEntities or {}
global.remoteIgnoredEntities[entity] = true
end
-- Remove an entity from the ignored list
-- Usage example: remote.call("even-distribution", "remove_ignored_entity", "wooden-chest")
function this.remove_ignored_entity(entity)
global.remoteIgnoredEntities = global.remoteIgnoredEntities or {}
global.remoteIgnoredEntities[entity] = nil
end
-- Get the list of ignored entities
-- Usage example: remote.call("even-distribution", "get_ignored_entities")
-- --> { ["wooden-chest"] = true, ["steel-chest"] = true }
function this.get_ignored_entities(entity)
return global.remoteIgnoredEntities or {}
end
remote.add_interface("even-distribution",
{
version = this.version,
add_ignored_entity = this.add_ignored_entity,
remove_ignored_entity = this.remove_ignored_entity,
get_ignored_entities = this.get_ignored_entities
}
)
return this

View File

@@ -0,0 +1,59 @@
-- Controller for the Even Distribution settings GUI
local this = {}
local util = scripts.util
local gui = scripts["gui-tools"]
local templates = scripts["settings-gui.gui-templates"].templates
local helpers = scripts.helpers
local _ = helpers.on
function this.on_init()
for __,player in pairs(game.players) do
player = _(player)
if player:is("valid") then
this.destroyGUI(player)
this.buildButton(player)
end
end
end
this.on_configuration_changed = this.on_init
function this.on_player_created(event)
local player = _(game.players[event.player_index])
if player:is("valid") then
this.destroyGUI(player)
this.buildButton(player)
end
end
function this.on_runtime_mod_setting_changed(event)
if event.setting == "disable-distribute" or
event.setting == "disable-inventory-cleanup" then
local player = _(game.players[event.player_index])
if player:is("valid player") and gui.get(player, templates.settingsWindow) then
this.buildGUI(player)
end
end
end
function this.buildGUI(player)
gui.create(player, templates.settingsWindow, { })
end
function this.destroyGUI(player)
gui.destroy(player, templates.settingsWindow)
end
function this.buildButton(player)
gui.create(player, templates.settingsButton, { })
end
function this.destroyButton(player)
gui.destroy(player, templates.settingsButton)
end
return this

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,310 @@
-- Sets up the global table and parses settings
local setup = {}
local util = scripts.util
local metatables = scripts.metatables
local defaultTrash = require("default-trash")
local config = require("config")
local helpers = scripts.helpers
local _ = helpers.on
function setup.on_init()
global.cache = global.cache or {}
global.distrEvents = global.distrEvents or {}
global.settings = global.settings or {}
global.defaultTrash = setup.generateTrashItemList()
global.remoteIgnoredEntities = global.remoteIgnoredEntities or {}
global.allowedEntities = _(game.entity_prototypes)
:where(function(prototype)
return util.hasInventory(prototype) and
not config.ignoredEntities[prototype.type] and
not config.ignoredEntities[prototype.name]
end)
:map(function(name)
return name, true
end)
:toPlain()
-- GUI events are saved in global.guiEvents["EVENT NAME"][PLAYER INDEX][GUI ELEMENT INDEX]
global.guiEvents = global.guiEvents or
{
onCheckedStateChanged = {},
onClicked = {},
onElementChanged = {},
onSelectionStateChanged = {},
onTextChanged = {},
onValueChanged = {},
onConfirmed = {},
onSelectedTabChanged = {},
onSwitchStateChanged = {},
onLocationChanged = {},
}
-- Fuel upgrade list (ascending fuel value)
global.fuelList = _(game.item_prototypes)
:where("fuel")
:toArray()
:groupBy("fuel_category")
:sort(function(a,b)
return a.fuel_value < b.fuel_value or
a.fuel_acceleration_multiplier < b.fuel_acceleration_multiplier or
a.fuel_top_speed_multiplier < b.fuel_top_speed_multiplier or
a.fuel_emissions_multiplier < b.fuel_emissions_multiplier
end)
:toPlain()
-- Ammo upgrade list (ascending damage)
global.ammoList = _(game.item_prototypes)
:where("ammo")
:toArray()
:groupBy(function(__,prototype)
return prototype.get_ammo_type().category
end)
:sort(function(a,b)
return _(a):calculateDamage() < _(b):calculateDamage()
end)
:toPlain()
for _,force in pairs(game.forces) do
setup.enableLogisticsTab(force)
end
for player_index,player in pairs(game.players) do
setup.setupPlayer(player_index, player)
end
end
setup.on_configuration_changed = setup.on_init
function setup.on_player_created(event)
setup.setupPlayer(event.player_index)
end
function setup.setupPlayer(player_index, player)
player = _(player or game.players[player_index])
setup.createPlayerCache(player_index)
setup.migrateSettings(player)
end
function setup.createPlayerCache(index)
global.cache[index] = global.cache[index] or {}
global.cache[index].items = global.cache[index].items or {}
global.cache[index].markers = global.cache[index].markers or {}
metatables.use(global.cache[index].markers, "entityAsIndex")
global.cache[index].entities = global.cache[index].entities or {}
metatables.use(global.cache[index].entities, "entityAsIndex")
end
function setup.migrateSettings(player)
local settings = global.settings[player.index] or {}
global.settings[player.index] = settings
local character = _(player.character or player.cutscene_character)
-- default values
if settings.distributionMode == nil then settings.distributionMode = "distribute" end
if settings.fuelLimit == nil then settings.fuelLimit = 0.5 end
if settings.fuelLimitType == nil then settings.fuelLimitType = "stacks" end
if settings.ammoLimit == nil then settings.ammoLimit = 0.5 end
if settings.ammoLimitType == nil then settings.ammoLimitType = "stacks" end
if settings.enableDragDistribute == nil then settings.enableDragDistribute = true end
if settings.dragUseFuelLimit == nil then settings.dragUseFuelLimit = true end
if settings.dragUseAmmoLimit == nil then settings.dragUseAmmoLimit = true end
if settings.takeFromInventory == nil then settings.takeFromInventory = true end
if settings.takeFromCar == nil then settings.takeFromCar = true end
if settings.replaceItems == nil then settings.replaceItems = true end
if settings.distributionDelay == nil then settings.distributionDelay = 0.9 end
if settings.enableInventoryCleanupHotkey == nil then settings.enableInventoryCleanupHotkey = true end
if settings.cleanupRequestOverflow == nil then settings.cleanupRequestOverflow = true end
if settings.dropTrashToChests == nil then settings.dropTrashToChests = true end
if settings.dropTrashToOutput == nil then settings.dropTrashToOutput = true end
if settings.cleanupUseFuelLimit == nil then settings.cleanupUseFuelLimit = true end
if settings.cleanupUseAmmoLimit == nil then settings.cleanupUseAmmoLimit = true end
if settings.cleanupDropRange == nil then settings.cleanupDropRange = 30 end
if settings.ignoredEntities == nil then settings.ignoredEntities = {} end
-- migrate settings from old mod versions
if settings.version == nil then
settings.version = "1.0.0"
settings.enableDragDistribute = player.mod_settings["enable-ed"].value
settings.takeFromCar = player.mod_settings["take-from-car"].value
settings.cleanupRequestOverflow = player.mod_settings["cleanup-logistic-request-overflow"].value
settings.dropTrashToChests = player.mod_settings["drop-trash-to-chests"].value
settings.distributionDelay = player.mod_settings["distribution-delay"].value
settings.cleanupDropRange = player.mod_settings["max-inventory-cleanup-drop-range"].value
if character:is("valid") then
-- move custom trash to logistic slots
if settings.customTrash and character:is("valid") then
local slotCount = character.request_slot_count
local slots = character:logisticSlots()
if _(slots):is("empty") then slotCount = 0 end
_(settings.customTrash)
:wherepair(function(item) -- {item,count}
return slots[item[1]] == nil and global.defaultTrash[item[1]] ~= item[2]
end,
function(item,count)
character.set_personal_logistic_slot(slotCount + 1, {
name = item,
min = 0,
max = count,
})
slotCount = slotCount + 1
end)
settings.customTrash = nil
end
-- add default logistic slots
if player:setting("enableInventoryCleanupHotkey") and _(character:logisticSlots()):is("empty") then
setup.addDefaultLogisticSlots(character)
end
end
dlog("Player ("..player.name..") settings migrated from none to 1.0.0")
end
-- Add default logistic slot configuration
if settings.version == "1.0.0" then
settings.version = "1.0.2"
dlog("Player ("..player.name..") settings migrated from 1.0.0 to 1.0.2")
end
if settings.version == "1.0.2" then
settings.version = "1.0.3"
if settings.dropTrashToChests == nil then
settings.dropTrashToChests = settings.dropTrashTFueloChests
end
settings.dropTrashTFueloChests = nil
dlog("Player ("..player.name..") settings migrated from 1.0.2 to 1.0.3")
end
if settings.version == "1.0.3" then
settings.version = "1.0.8"
global.lastCharts = nil
global.lastCharacters = nil
dlog("Player ("..player.name..") settings migrated from 1.0.3 to 1.0.8")
end
--if settings.version == "0.3.x" then
-- ...
-- end
end
function setup.addDefaultLogisticSlots(character)
local slotCount = 0
local slots = {}
character.character_personal_logistic_requests_enabled = false
_(config.defaultLogisticSlots)
:wherepair(
function(item) -- {item,count}
return slots[item[1]] == nil and
global.defaultTrash[item[1]] ~= item[2] and
game.item_prototypes[item[1]]
end,
function(item,count)
character.set_personal_logistic_slot(slotCount + 1, {
name = item,
min = 0,
max = count * game.item_prototypes[item].stack_size,
})
slotCount = slotCount + 1
end)
end
function setup.on_force_created(event)
setup.enableLogisticsTab(event.force or event.destination)
end
setup.on_forces_merged = setup.on_force_created
setup.on_technology_effects_reset = setup.on_force_created
function setup.on_runtime_mod_setting_changed(event)
if event.setting == "disable-inventory-cleanup" then
for _,force in pairs(game.forces) do
setup.enableLogisticsTab(force)
end
-- add default logistic slots when enabling shift+c (if all slots are empty)
if settings.global["disable-inventory-cleanup"].value == false then
for __,player in pairs(game.players) do
local character = _(player.character or player.cutscene_character)
if character:is("valid") and
_(player):setting("enableInventoryCleanupHotkey") and
_(character:logisticSlots()):is("empty") then
setup.addDefaultLogisticSlots(character)
end
end
end
end
end
function setup.on_research_finished(event)
setup.enableLogisticsTab(event.research.force)
end
setup.on_research_reversed = setup.on_research_finished
setup.on_research_started = setup.on_research_finished
function setup.enableLogisticsTab(force)
if force.technologies["enable-logistics-tab"] and not setup.hasLogisticSlots(force) then
local enabled = not settings.global["disable-inventory-cleanup"].value
force.technologies["enable-logistics-tab"].researched = enabled
end
end
function setup.hasLogisticSlots(force)
for _,tech in pairs(force.technologies) do
if tech.researched and tech.name ~= "enable-logistics-tab" then
for _,effect in pairs(tech.effects) do
if effect.type == "character-logistic-requests" then
if effect.modifier then return true end
end
end
end
end
return false
end
function setup.generateTrashItemList()
local items = {}
for name,item in pairs(game.item_prototypes) do
if not (item.place_result or item.place_as_equipment_result or item.has_flag("hidden")) then -- or item.place_as_tile_result
local default = defaultTrash[name] or defaultTrash[item.subgroup.name] or defaultTrash[item.group.name]
if default and default ~= "ignore" then
if item.fuel_category and not defaultTrash[name] then -- fuels default to 2 stacks as desired amount
items[name] = 2 * item.stack_size
else
items[name] = default * item.stack_size
end
end
end
end
return items
end
function setup.on_load()
metatables.refresh(global)
end
return setup

View File

@@ -0,0 +1,169 @@
local util = {}
function util.doEvery(tick, func, args)
if (game.tick % tick) == 0 then func(args) end
end
-- remove leading and trailing whitespaces
function util.trim(str)
return str and (str:gsub("^%s*(.-)%s*$", "%1")) or ""
end
-- trim and also remove multiple whitespaces
function util.fullTrim(str)
return (util.trim(str):gsub("[ \t\r\n]*[\r\n][ \t\r\n]*", "\r\n"):gsub("[ \t]+", " "))
end
-- escape string for use with regex
local quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])'
function util.escape(str)
return str:gsub(quotepattern, "%%%1")
end
function util.isValid(object)
return object and object.valid
end
function util.destroyIfValid(object)
if util.isValid(object) then object.destroy() end
end
function util.isValidStack(stack)
return util.isValid(stack) and stack.valid_for_read
end
function util.isValidPlayer(player) -- valid, connected and alive player
return util.isValid(player) and
player.connected and (
player.controller_type == defines.controllers.character or
player.controller_type == defines.controllers.god or
player.controller_type == defines.controllers.editor
)
end
function util.isCraftingMachine(entity)
return entity.type == "furnace" or entity.type == "assembling-machine" or entity.type == "rocket-silo"
end
function util.enablesRobots(tech)
local recipes = game.recipe_prototypes
local items = game.item_prototypes
for __,effect in pairs(tech.effects) do
if effect.type == "unlock-recipe" then
local recipe = recipes[effect.recipe]
for __,product in pairs(recipe.products) do
if product.type == "item" then
local entity = items[product.name].place_result
if entity and entity.type == "roboport" then
return true
end
end
end
end
end
return false
end
function util.hasRobotsEnabled(force)
for __,tech in pairs(force.technologies) do
if tech.researched and util.enablesRobots(tech) then return true end
end
return false
end
function util.shallowCopy(original) -- Creates a shallow copy of a table
local copy = {}
for key,value in pairs(original) do copy[key] = value end
return copy
end
function util.hasInventory(prototype)
for __,index in pairs(defines.inventory) do
if prototype.get_inventory_size(index) then return true end
end
return false
end
function util.countTable(tbl)
local count = 0
for __ in pairs(tbl) do count = count + 1 end
return count
end
function util.isEmpty(tbl) -- empty table
return type(tbl) == "table" and next(tbl) == nil
end
function util.isFilled(tbl) -- filled table
return type(tbl) == "table" and next(tbl) ~= nil
end
function util.distribute(entities, totalItems, func)
local insertAmount = math.floor(totalItems / #entities)
local remainder = totalItems % #entities
for entity in util.epairs(entities) do
if util.isValid(entity) then
local amount = insertAmount
if remainder > 0 then
amount = amount + 1
remainder = remainder - 1
end
func(entity, amount)
end
end
end
function util.epairs(tbl) -- iterator for tables with entity based indices
local id, start
local surface, x, y
local tbls, tblsx, value
local tblId = rawget(tbl, "id")
local tblPos = rawget(tbl, "pos")
if tblId then
start = true
elseif not tblPos then
return function () end
end
return function () -- Iterator
if id or start then
start = false
id, value = next(tblId, id)
if value == nil then
if not tblPos then return end
surface, tbls = next(tblPos)
if not tbls then return end
x, tblsx = next(tbls)
else
return value
end
end
while value == nil do
y, value = next(tblsx, y)
if value == nil then
x, tblsx = next(tbls, x)
y = nil
if not tblsx then
surface, tbls = next(tblPos, surface)
if not tbls then return end
x, tblsx = next(tbls, x)
y = nil
end
end
end
return value
end
end
return util

View File

@@ -0,0 +1,149 @@
local this = {}
local util = scripts.util
local metatables = scripts.metatables
local config = require("config")
local helpers = scripts.helpers
local _ = helpers.on
local function getSprite(item)
return "item/"..item
end
local function getShortCount(count)
if count == nil then return 0 end
if count >= 1000000000 then return (math.floor(count / 1000000) / 10).."G" end
if count >= 1000000 then return (math.floor(count / 10000) / 10).."M" end
if count >= 1000 then return (math.floor(count / 100) / 10).."K" end
return count
end
local function getCountColor(count)
if count == 0 or count == nil then
return config.colors.insufficientItems
else
return config.colors.default
end
end
function helpers:mark(player, item, count) -- create highlight-box marker with item and count
if item ~= nil then
local box = _(self.selection_box)
local width = box:boxwidth()
local height = box:boxheight()
local scale = 0.63 * math.min(width, height)
return {
box = self.surface.create_entity{
name = "highlight-box",
position = self.position,
source = self:toPlain(),
render_player_index = player and player.index,
box_type = "electricity",
blink_interval = 0,
},
bg = rendering.draw_sprite{
sprite = "utility/entity_info_dark_background",
render_layer = "selection-box",
target = self:toPlain(),
target_offset = self.type == "spider-vehicle" and {0,-box:boxheight()*3/4} or nil,
players = player and {player:toPlain()},
surface = self.surface,
x_scale = scale,
y_scale = scale,
},
icon = rendering.draw_sprite{
sprite = getSprite(item),
render_layer = "selection-box",
target = self:toPlain(),
target_offset = self.type == "spider-vehicle" and {0,-box:boxheight()*3/4} or nil,
players = player and {player:toPlain()},
surface = self.surface,
x_scale = scale,
y_scale = scale,
},
text = rendering.draw_text({
text = getShortCount(count),
target = self:toPlain(),
target_offset = self.type == "spider-vehicle" and {0,-box:boxheight()*3/4} or nil,
--target_offset = { -0.9 * width * 0.5, -0.9 * height * 0.5 },
players = player and {player:toPlain()},
surface = self.surface,
alignment = "center",
color = getCountColor(count),
})
}
else
-- blink animation
self.surface.create_entity{
name = "highlight-box",
position = self.position,
source = self:toPlain(),
render_player_index = player and player.index,
box_type = "electricity",
blink_interval = 6,
time_to_live = 60 * 1,
}
end
end
function this.update(marker, item, count, color)
if marker then
rendering.set_sprite(marker.icon, getSprite(item))
rendering.set_text(marker.text, getShortCount(count))
rendering.set_color(marker.text, color or getCountColor(count))
end
end
function helpers:unmark() -- destroy distribution marker of entity
if self:is("object") then return self:is("valid") and self.destroy() end
local source, player
self:where("table", function(__, marker)
marker.destroy()
end)
:where("number", function(__, id)
if rendering.is_valid(id) then
if not source and rendering.get_target(id) then source = _(rendering.get_target(id).entity) end
if not player and rendering.get_players(id) then player = _(rendering.get_players(id)[1]) end
rendering.destroy(id)
end
end)
if source and player and source:is("valid") and player:is("valid player") then source:mark(player) end
end
function this.unmark(cache) -- destroy all distribution markers of a player (using cache)
_(cache.markers):each(function(markers)
_(markers):unmark()
end)
cache.markers = metatables.new("entityAsIndex")
end
function helpers:destroyTransferText() -- remove flying text from stack transfer
local surface = self.surface
local pos = self.position
util.destroyIfValid(surface.find_entities_filtered{
name = "flying-text",
area = {{pos.x, pos.y-1}, {pos.x, pos.y+0.5}},
limit = 1
}[1])
end
function helpers:spawnDistributionText(item, amount, offY, color) -- spawn distribution text on entity
local surface = self.surface
local pos = self.position
surface.create_entity{ -- spawn text
name = "distribution-text",
position = { pos.x - 0.5, pos.y + (offY or 0) },
text = {"", " ", -amount, " ", game.item_prototypes[item].localised_name},
color = color or config.colors.default
}
end
return this