Vlayer Update (#254)

* redo

* waterfill role

* update

* spacing

* Apply solar_power_multiplier in one location

* Better surface handling

* Fix circuit interface with too many modded items

* Better energy interfaces

* update vlayer

* auto down grade config

* Fix production multiplier to work with fake surface

* Add doc comments to vlayer

* Update vlayer.lua

---------

Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
This commit is contained in:
2023-12-29 11:37:15 +09:00
committed by GitHub
parent 7046a3e051
commit 3cd55cbd63
3 changed files with 1073 additions and 535 deletions

View File

@@ -1,167 +1,167 @@
-- Vlayer Config
--- Settings for vlayer including the allowed items, the update frequency, and some cheats
-- @config Vlayer
return {
enabled = true,
update_tick = 10,
-- 10 MJ
energy_base_limit = 10000000,
land = {
enabled = true,
tile = 'landfill',
result = 4,
requirement = {
['solar-panel'] = 9,
['accumulator'] = 4
update_tick_storage = 60, --- @setting update_tick_storage The number of ticks between each update of the storage interfaces
update_tick_energy = 10, --- @setting update_tick_energy The number of ticks between each update of the energy and circuit interfaces
update_tick_gui = 60, --- @setting update_tick_gui The number of ticks between each update of the gui
unlimited_capacity = false, --- @setting unlimited_capacity When true the vlayer has an unlimited energy capacity, accumulators are not required
unlimited_surface_area = false, --- @setting unlimited_surface_area When true the vlayer has an unlimited surface area, landfill is not required
modded_auto_downgrade = false, --- @setting modded_auto_downgrade When true modded items will be converted into their base game equivalent, original items can not be recovered
mimic_surface = 'nauvis', --- @setting mimic_surface Surface name/index the vlayer will copy its settings from, use nil to use the settings below
surface = { --- @setting surface When mimic_surface is nil these settings will be used instead, see LuaSurface for details
always_day = false,
solar_power_multiplier = 1,
min_brightness = 0.15,
ticks_per_day = 25000,
daytime = 0,
dusk = 0.25,
evening = 0.45,
morning = 0.55,
dawn = 0.75
},
interface_limit = { --- @setting interface_limit Sets the limit for the number of vlayer interfaces that can be created
energy = 1, -- >1 allows for disconnected power networks to receive power
circuit = 10, -- No caveats
storage_input = 10, -- No caveats
storage_output = 1 -- >0 allows for item teleportation (allowed_items only)
},
allowed_items = { --- @setting allowed_items List of all items allowed in vlayer storage and their properties
--[[
Allowed properties:
starting_value = 0: The amount of the item placed into the vlayer on game start, ignores area requirements
required_area = 0: When greater than 0 the items properties are not applied unless their is sufficient surplus surface area
production = 0: The energy production of the item in MW, used for solar panels
discharge = 0: The energy discharge of the item in MW, used for accumulators
capacity = 0: The energy capacity of the item in MJ, used for accumulators
surface_area = 0: The surface area provided by the item, used for landfill
]]
['solar-panel'] = {
starting_value = 0,
required_area = 9,
production = 0.06 -- MW
},
['accumulator'] = {
starting_value = 2,
required_area = 4,
discharge = 0.3, -- MW
capacity = 5 -- MJ
},
['landfill'] = {
starting_value = 0,
required_area = 0,
surface_area = 4 -- Tiles
}
-- TODO: Can convert wood directly to energy to reduce trash
--[[
['wood'] = {
starting_value = 0,
required_area = 0,
surface_area = 0,
fuel_value = 2
},
['iron-ore'] = {
starting_value = 0,
required_area = 0,
surface_area = 0
},
['copper-ore'] = {
starting_value = 0,
required_area = 0,
surface_area = 0
},
['coal'] = {
starting_value = 0,
required_area = 0,
surface_area = 0
},
['stone'] = {
starting_value = 0,
required_area = 0,
surface_area = 0
},
['uranium-ore'] = {
starting_value = 0,
required_area = 0,
surface_area = 0
},
]]
},
always_day = false,
battery_limit = true,
-- setting to a value greater than 1 will allow for wireless energy transfer
interface_limit = {
storage_input = 1,
energy = 1,
circuit = 1
},
print_out = {
['electric-energy-interface'] = 'energy interface',
['constant-combinator'] = 'circuit output',
['logistic-chest-storage'] = 'storage input'
},
gui = {
style = 'heading_1_label',
type = 'label',
content = {
{
title = 'Storage',
type = nil,
name = nil
},
{
title = '',
type = nil,
name = nil
},
{
title = '[img=entity/solar-panel] Solar Panel',
type = nil,
name = nil
},
{
title = 0,
type = 'item',
name = 'solar-panel'
},
{
title = '[img=entity/accumulator] Accumulator',
type = nil,
name = nil
},
{
title = 0,
type = 'item',
name = 'accumulator'
},
{
title = '[virtual-signal=signal-L] Landfill',
type = nil,
name = nil
},
{
title = 0,
type = 'signal',
name = 7
},
{
title = '[virtual-signal=signal-A] Solar Available',
type = nil,
name = nil
},
{
title = 0,
type = 'signal',
name = 8
},
{
title = '[virtual-signal=signal-B] Acc Available',
type = nil,
name = nil
},
{
title = 0,
type = 'signal',
name = 9
},
{
title = 'Power Production',
type = nil,
name = nil
},
{
title = 'MW',
type = nil,
name = nil
},
{
title = '[virtual-signal=signal-P] Peak',
type = nil,
name = nil
},
{
title = '0',
type = 'signal',
name = 1
},
{
title = '[virtual-signal=signal-S] Sustained',
type = nil,
name = nil
},
{
title = '0',
type = 'signal',
name = 2
},
{
title = 'Battery',
type = nil,
name = nil
},
{
title = 'MJ',
type = nil,
name = nil
},
{
title = '[virtual-signal=signal-M] Max',
type = nil,
name = nil
},
{
title = '0',
type = 'signal',
name = 3
},
{
title = '[virtual-signal=signal-C] Current',
type = nil,
name = nil
},
{
title = '0',
type = 'signal',
name = 4
},
{
title = 'Convert',
type = nil,
name = nil
},
{
title = '',
type = nil,
name = nil
}
}
modded_items = { --- @setting modded_items List of all modded items allowed in vlayer storage and their base game equivalent
['solar-panel-2'] = {
starting_value = 0,
base_game_equivalent = 'solar-panel',
multiplier = 4
},
['solar-panel-3'] = {
starting_value = 0,
base_game_equivalent = 'solar-panel',
multiplier = 16
},
['solar-panel-4'] = {
starting_value = 0,
base_game_equivalent = 'solar-panel',
multiplier = 64
},
['solar-panel-5'] = {
starting_value = 0,
base_game_equivalent = 'solar-panel',
multiplier = 256
},
['solar-panel-6'] = {
starting_value = 0,
base_game_equivalent = 'solar-panel',
multiplier = 1024
},
['solar-panel-7'] = {
starting_value = 0,
base_game_equivalent = 'solar-panel',
multiplier = 4096
},
['solar-panel-8'] = {
starting_value = 0,
base_game_equivalent = 'solar-panel',
multiplier = 16384
},
['accumulator-2'] = {
starting_value = 0,
base_game_equivalent = 'accumulator',
multiplier = 4
},
['accumulator-3'] = {
starting_value = 0,
base_game_equivalent = 'accumulator',
multiplier = 16
},
['accumulator-4'] = {
starting_value = 0,
base_game_equivalent = 'accumulator',
multiplier = 64
},
['accumulator-5'] = {
starting_value = 0,
base_game_equivalent = 'accumulator',
multiplier = 256
},
['accumulator-6'] = {
starting_value = 0,
base_game_equivalent = 'accumulator',
multiplier = 1024
},
['accumulator-7'] = {
starting_value = 0,
base_game_equivalent = 'accumulator',
multiplier = 4096
},
['accumulator-8'] = {
starting_value = 0,
base_game_equivalent = 'accumulator',
multiplier = 16384
},
}
}
}

View File

@@ -1,223 +1,633 @@
--- Adds a virtual layer to store power to save space.
-- @addon Virtual Layer
--[[-- Control Module - vlayer
- Adds a virtual layer to store power to save space.
@control vlayer
@alias vlayer
]]
local Global = require 'utils.global' --- @dep utils.global
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.vlayer' --- @dep config.vlayer
local move_items_stack = _C.move_items_stack
local mega = 1000000
local vlayer = {}
Global.register(vlayer, function(tbl)
vlayer = tbl
local vlayer_data = {
entity_interfaces = {
energy = {},
circuit = {},
storage_input = {},
storage_output = {}
},
properties = {
total_surface_area = 0,
used_surface_area = 0,
production = 0,
discharge = 0,
capacity = 0,
},
storage = {
items = {},
energy = 0,
unallocated = {}
},
surface = table.deep_copy(config.surface)
}
Global.register(vlayer_data, function(tbl)
vlayer_data = tbl
end)
vlayer.storage = {}
vlayer.storage.item = {}
vlayer.storage.input = {}
vlayer.storage.item_m = {}
vlayer.power = {}
vlayer.power.entity = {}
vlayer.power.energy = 0
vlayer.power.circuit = {}
vlayer.circuit = {}
vlayer.circuit.output = {}
for i=1, 11 do
vlayer.circuit.output[i] = {}
vlayer.circuit.output[i].count = 0
for _, properties in pairs(config.allowed_items) do
properties.modded = false
end
vlayer.circuit.output[1].signal = {type='virtual', name='signal-P'}
vlayer.circuit.output[2].signal = {type='virtual', name='signal-S'}
vlayer.circuit.output[3].signal = {type='virtual', name='signal-M'}
vlayer.circuit.output[4].signal = {type='virtual', name='signal-C'}
vlayer.circuit.output[5].signal = {type='virtual', name='signal-D'}
vlayer.circuit.output[6].signal = {type='virtual', name='signal-T'}
vlayer.circuit.output[7].signal = {type='virtual', name='signal-L'}
vlayer.circuit.output[8].signal = {type='virtual', name='signal-A'}
vlayer.circuit.output[9].signal = {type='virtual', name='signal-B'}
vlayer.circuit.output[10].signal = {type='item', name='solar-panel'}
vlayer.circuit.output[11].signal = {type='item', name='accumulator'}
-- For all modded items, create a config for them
for item_name, properties in pairs(config.modded_items) do
local base_properties = config.allowed_items[properties.base_game_equivalent]
local m = properties.multiplier
vlayer.storage.item['solar-panel'] = 0
vlayer.storage.item['accumulator'] = 0
if config.land.enabled then
vlayer.storage.item[config.land.tile] = 0
vlayer.storage.item_a = {}
vlayer.storage.item_a['solar-panel'] = 0
vlayer.storage.item_a['accumulator'] = 0
config.allowed_items[item_name] = {
starting_value = properties.starting_value or 0,
required_area = base_properties.required_area or 0,
surface_area = (base_properties.surface_area or 0) * m,
production = (base_properties.production or 0) * m,
capacity = (base_properties.capacity or 0) * m,
modded = true
}
end
local vlayer_storage_item = {}
--- Get all items in storage, do not modify
-- @treturn table a dictionary of all items stored in the vlayer
function vlayer.get_items()
return vlayer_data.storage.items
end
for i=2, 8 do
vlayer_storage_item['solar-panel-' .. i] = {name='solar-panel', multiplier=4 ^ (i - 1)}
vlayer_storage_item['accumulator-' .. i] = {name='accumulator', multiplier=4 ^ (i - 1)}
--- Get interface counts
-- @treturn table a dictionary of the vlayer interface counts
function vlayer.get_interface_counts()
local interfaces = vlayer_data.entity_interfaces
return {
energy = #interfaces.energy,
circuit = #interfaces.circuit,
storage_input = #interfaces.storage_input,
storage_output = #interfaces.storage_output,
}
end
--[[
25,000 / 416 s
昼 208秒 ソーラー効率100%
夕方 83秒 1秒ごとにソーラー発電量が約1.2%ずつ下がり、やがて0%になる
41秒 ソーラー発電量が0%になる
朝方 83秒 1秒ごとにソーラー発電量が約1.2%ずつ上がり、やがて100%になる
0.75 Day 12,500 208s
0.25 Sunset 5,000 83s
0.45 Night 2,500 41s
0.55 Sunrise 5,000 83s
昼 208秒 ソーラー効率100%
夕方 83秒 1秒ごとにソーラー発電量が約1.2%ずつ下がり、やがて0%になる
41秒 ソーラー発電量が0%になる
朝方 83秒 1秒ごとにソーラー発電量が約1.2%ずつ上がり、やがて100%になる
(surface.dawn) 0.75 18,750 Day 12,500 208s
0.00 0 Noon
(surface.dusk) 0.25 6,250 Sunset 5,000 83s
(surface.evening) 0.45 11,250 Night 2,500 41s
(surface.morning) 0.55 13,750 Sunrise 5,000 83s
]]
Event.on_nth_tick(config.update_tick, function()
-- storage handle
for k, v in pairs(vlayer.storage.input) do
if ((v == nil) or (not v.valid)) then
vlayer.input[k] = nil
--- Get the power multiplier based on the surface time
local function get_production_multiplier()
local mul = vlayer_data.surface.solar_power_multiplier
local surface = vlayer_data.surface
if surface.always_day then
-- Surface is always day, so full production is used
return mul
end
if surface.darkness then
-- We are using a real surface, our config does not contain 'darkness'
local brightness = 1 - surface.darkness
if brightness >= surface.min_brightness then
return mul * (brightness - surface.min_brightness) / (1 - surface.min_brightness)
else
local chest = v.get_inventory(defines.inventory.chest)
local chest_content = chest.get_contents()
if config.land.enabled then
for item_name, count in pairs(chest_content) do
if (vlayer.storage.item[item_name] ~= nil) then
if item_name == config.land.tile then
vlayer.storage.item[item_name] = vlayer.storage.item[item_name] + count
else
vlayer.storage.item_a[item_name] = vlayer.storage.item_a[item_name] + count
end
chest.remove({name=item_name, count=count})
elseif (vlayer_storage_item[item_name] ~= nil) then
vlayer.storage.item_a[vlayer_storage_item[item_name].name] = vlayer.storage.item_a[vlayer_storage_item[item_name].name] + (count * vlayer_storage_item[item_name].multiplier)
chest.remove({name=item_name, count=count})
end
end
local land_req = (vlayer.storage.item['solar-panel'] * config.land.requirement['solar-panel']) + (vlayer.storage.item['accumulator'] * config.land.requirement['accumulator'])
local land_surplus = (vlayer.storage.item[config.land.tile] * config.land.result) - land_req
if (vlayer.storage.item_a['solar-panel'] > 0 and vlayer.storage.item_a['accumulator'] > 0) then
local land_allocation = math.floor(land_surplus / (config.land.requirement['solar-panel'] + config.land.requirement['accumulator']))
vlayer.storage.item['solar-panel'] = vlayer.storage.item['solar-panel'] + math.min(vlayer.storage.item_a['solar-panel'], land_allocation)
vlayer.storage.item_a['solar-panel'] = vlayer.storage.item_a['solar-panel'] - math.min(vlayer.storage.item_a['solar-panel'], land_allocation)
vlayer.storage.item['accumulator'] = vlayer.storage.item['accumulator'] + math.min(vlayer.storage.item_a['accumulator'], land_allocation)
vlayer.storage.item_a['accumulator'] = vlayer.storage.item_a['accumulator'] - math.min(vlayer.storage.item_a['accumulator'], land_allocation)
elseif (vlayer.storage.item_a['solar-panel'] > 0 and vlayer.storage.item_a['accumulator'] == 0) then
local land_allocation = math.floor(land_surplus / config.land.requirement['solar-panel'])
vlayer.storage.item['solar-panel'] = vlayer.storage.item['solar-panel'] + math.min(vlayer.storage.item_a['solar-panel'], land_allocation)
vlayer.storage.item_a['solar-panel'] = vlayer.storage.item_a['solar-panel'] - math.min(vlayer.storage.item_a['solar-panel'], land_allocation)
else
local land_allocation = math.floor(land_surplus / config.land.requirement['accumulator'])
vlayer.storage.item['accumulator'] = vlayer.storage.item['accumulator'] + math.min(vlayer.storage.item_a['accumulator'], land_allocation)
vlayer.storage.item_a['accumulator'] = vlayer.storage.item_a['accumulator'] - math.min(vlayer.storage.item_a['accumulator'], land_allocation)
end
vlayer.circuit.output[1].count = math.floor(vlayer.storage.item['solar-panel'] * 0.06 * game.surfaces['nauvis'].solar_power_multiplier)
vlayer.circuit.output[2].count = math.floor(vlayer.storage.item['solar-panel'] * 873 * game.surfaces['nauvis'].solar_power_multiplier / 20800)
vlayer.circuit.output[3].count = vlayer.storage.item['accumulator'] * 5
vlayer.circuit.output[7].count = (vlayer.storage.item[config.land.tile] * config.land.result) - (vlayer.storage.item['solar-panel'] * config.land.requirement['solar-panel']) - (vlayer.storage.item['accumulator'] * config.land.requirement['accumulator'])
vlayer.circuit.output[8].count = vlayer.storage.item_a['solar-panel']
vlayer.circuit.output[9].count = vlayer.storage.item_a['accumulator']
vlayer.circuit.output[10].count = vlayer.storage.item['solar-panel']
vlayer.circuit.output[11].count = vlayer.storage.item['accumulator']
else
for item_name, count in pairs(chest_content) do
if (vlayer.storage.item[item_name] ~= nil) then
vlayer.storage.item[item_name] = vlayer.storage.item[item_name] + count
chest.remove({name=item_name, count=count})
elseif (vlayer_storage_item[item_name] ~= nil) then
vlayer.storage.item[vlayer_storage_item[item_name].name] = vlayer.storage.item[vlayer_storage_item[item_name].name] + (count * vlayer_storage_item[item_name].multiplier)
chest.remove({name=item_name, count=count})
end
end
vlayer.circuit.output[1].count = math.floor(vlayer.storage.item['solar-panel'] * 0.06 * game.surfaces['nauvis'].solar_power_multiplier)
vlayer.circuit.output[2].count = math.floor(vlayer.storage.item['solar-panel'] * 873 * game.surfaces['nauvis'].solar_power_multiplier / 20800)
vlayer.circuit.output[3].count = (vlayer.storage.item['accumulator'] * 5) + (config.energy_base_limit / 1000000)
vlayer.circuit.output[10].count = vlayer.storage.item['solar-panel']
vlayer.circuit.output[11].count = vlayer.storage.item['accumulator']
end
return 0
end
end
-- power handle
local vlayer_power_capacity_total = math.floor(((vlayer.storage.item['accumulator'] * 5000000) + (config.energy_base_limit * #vlayer.power.entity)) / 2)
local vlayer_power_capacity = math.floor(vlayer_power_capacity_total / #vlayer.power.entity)
-- Caused by using a set config rather than a surface
local tick = game.tick % surface.ticks_per_day
local daytime = tick / surface.ticks_per_day
surface.daytime = daytime
if config.always_day or game.surfaces['nauvis'].always_day then
vlayer.power.energy = vlayer.power.energy + math.floor(vlayer.storage.item['solar-panel'] * 60000 * game.surfaces['nauvis'].solar_power_multiplier / config.update_tick)
if daytime <= surface.dusk then -- Noon to Sunset
return mul
elseif daytime <= surface.evening then -- Sunset to Night
return mul * (1 - ((daytime - surface.dusk) / (surface.evening - surface.dusk)))
elseif daytime <= surface.morning then -- Night to Sunrise
return 0
elseif daytime <= surface.dawn then -- Sunrise to Morning
return mul * ((surface.daytime - surface.morning) / (surface.dawn - surface.morning))
else -- Morning to Noon
return mul
end
end
--- Get the sustained power multiplier, this needs improving
local function get_sustained_multiplier()
local mul = vlayer_data.surface.solar_power_multiplier
local surface = vlayer_data.surface
if surface.always_day then
-- Surface is always day, so full production is used
return mul
end
-- For nauvis vanilla: 208s + (1/2 x (83s + 83s))
local day_duration = 1 - surface.dawn + surface.dusk
local sunset_duration = surface.evening - surface.dusk
local sunrise_duration = surface.dawn - surface.morning
return mul * (day_duration + (0.5 * (sunset_duration + sunrise_duration)))
end
--- Internal, Allocate items in the vlayer, this will increase the property values of the vlayer such as production and capacity
-- Does not increment item storage, so should not be called before insert_item unless during init
-- Does not validate area requirements, so checks must be performed before calling this function
-- Accepts negative count for deallocating items
-- @tparam string item_name The name of the item to allocate
-- @tparam number count The count of the item to allocate
function vlayer.allocate_item(item_name, count)
local item_properties = config.allowed_items[item_name]
assert(item_properties, 'Item not allowed in vlayer: ' .. tostring(item_name))
if item_properties.production then
vlayer_data.properties.production = vlayer_data.properties.production + item_properties.production * count
end
if item_properties.capacity then
vlayer_data.properties.capacity = vlayer_data.properties.capacity + item_properties.capacity * count
end
if item_properties.discharge then
vlayer_data.properties.discharge = vlayer_data.properties.discharge + item_properties.discharge * count
end
if item_properties.surface_area then
vlayer_data.properties.total_surface_area = vlayer_data.properties.total_surface_area + item_properties.surface_area * count
end
if item_properties.required_area and item_properties.required_area > 0 then
vlayer_data.properties.used_surface_area = vlayer_data.properties.used_surface_area + item_properties.required_area * count
end
end
-- For all allowed items, setup their starting values, default 0
for item_name, properties in pairs(config.allowed_items) do
vlayer_data.storage.items[item_name] = properties.starting_value or 0
if properties.required_area and properties.required_area > 0 then
vlayer_data.storage.unallocated[item_name] = 0
end
vlayer.allocate_item(item_name, properties.starting_value)
end
--- Insert an item into the vlayer, this will increment its count in storage and allocate it if possible
-- @tparam string item_name The name of the item to insert
-- @tparam number count The count of the item to insert
function vlayer.insert_item(item_name, count)
local item_properties = config.allowed_items[item_name]
assert(item_properties, 'Item not allowed in vlayer: ' .. tostring(item_name))
vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] + count
if not config.unlimited_surface_area and item_properties.required_area and item_properties.required_area > 0 then
-- Calculate how many can be allocated
local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
local allocate_count = math.min(count, math.floor(surplus_area / item_properties.required_area))
if allocate_count > 0 then
vlayer.allocate_item(item_name, allocate_count)
end
vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] + count - allocate_count
else
local tick = game.tick % 25000
vlayer.allocate_item(item_name, count)
end
end
if tick <= 5000 or tick > 17500 then
vlayer.power.energy = vlayer.power.energy + math.floor(vlayer.storage.item['solar-panel'] * 60000 * game.surfaces['nauvis'].solar_power_multiplier / config.update_tick)
--- Remove an item from the vlayer, this will decrement its count in storage and prioritise unallocated items over deallocation
-- Can not always fulfil the remove request for items which provide surface area, therefore returns the amount actually removed
-- @tparam string item_name The name of the item to remove
-- @tparam number count The count of the item to remove
-- @treturn number The count of the item actually removed
function vlayer.remove_item(item_name, count)
local item_properties = config.allowed_items[item_name]
assert(item_properties, 'Item not allowed in vlayer: ' .. tostring(item_name))
local remove_unallocated = 0
elseif tick <= 10000 then
vlayer.power.energy = vlayer.power.energy + math.floor(vlayer.storage.item['solar-panel'] * 60000 * game.surfaces['nauvis'].solar_power_multiplier / config.update_tick * (1 - ((tick - 5000) / 5000)))
if not config.unlimited_surface_area and item_properties.required_area and item_properties.required_area > 0 then
-- Remove from the unallocated storage first
remove_unallocated = math.min(count, vlayer_data.storage.unallocated[item_name])
elseif (tick > 12500) and (tick <= 17500) then
vlayer.power.energy = vlayer.power.energy + math.floor(vlayer.storage.item['solar-panel'] * 60000 * game.surfaces['nauvis'].solar_power_multiplier / config.update_tick * ((tick - 5000) / 5000))
if remove_unallocated > 0 then
vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] - count
vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] - count
end
-- Check if any more items need to be removed
count = count - remove_unallocated
if count == 0 then
return remove_unallocated
end
end
if config.battery_limit then
if vlayer.power.energy > vlayer_power_capacity_total then
vlayer.power.energy = vlayer_power_capacity_total
-- Calculate the amount to remove based on items in storage
local remove_count = math.min(count, vlayer_data.storage.items[item_name])
if item_properties.surface_area and item_properties.surface_area > 0 then
-- If the item provides surface area then it has additional limitations
local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
remove_count = math.min(remove_count, math.floor(surplus_area / item_properties.surface_area))
if remove_count <= 0 then
return remove_unallocated
end
end
for k, v in pairs(vlayer.power.entity) do
if (v == nil) or (not v.valid)then
vlayer.power.entity[k] = nil
-- Remove the item from allocated storage
vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] - remove_count
vlayer.allocate_item(item_name, -remove_count)
return remove_unallocated + remove_count
end
--- Create a new storage input interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface
function vlayer.create_input_interface(surface, position, last_user)
local interface = surface.create_entity{name='logistic-chest-storage', position=position, force='neutral'}
table.insert(vlayer_data.entity_interfaces.storage_input, interface)
if last_user then
interface.last_user = last_user
end
interface.destructible = false
interface.minable = false
interface.operable = true
return interface
end
--- Handle all input interfaces, will take their contents and insert it into the vlayer storage
local function handle_input_interfaces()
for index, interface in pairs(vlayer_data.entity_interfaces.storage_input) do
if not interface.valid then
vlayer_data.entity_interfaces.storage_input[index] = nil
else
v.electric_buffer_size = vlayer_power_capacity
v.power_production = math.floor(vlayer_power_capacity / 60)
v.power_usage = math.floor(vlayer_power_capacity / 60)
local inventory = interface.get_inventory(defines.inventory.chest)
if vlayer.power.energy < vlayer_power_capacity then
v.energy = math.floor((v.energy + vlayer.power.energy) / 2)
vlayer.power.energy = v.energy
for name, count in pairs(inventory.get_contents()) do
if config.allowed_items[name] then
if config.allowed_items[name].modded then
if config.modded_auto_downgrade then
vlayer.insert_item(config.modded_items[name].base_game_equivalent, count * config.modded_items[name].multiplier)
elseif v.energy < vlayer_power_capacity then
local energy_change = vlayer_power_capacity - v.energy
else
vlayer.insert_item(name, count)
end
else
vlayer.insert_item(name, count)
end
if energy_change < vlayer.power.energy then
v.energy = v.energy + energy_change
vlayer.power.energy = vlayer.power.energy - energy_change
else
v.energy = v.energy + vlayer.power.energy
vlayer.power.energy = 0
inventory.remove({name=name, count=count})
end
end
end
end
end
-- circuit handle
vlayer.circuit.output[4].count = math.floor(vlayer.power.energy / 1000000)
vlayer.circuit.output[5].count = math.floor(game.tick / 25000)
vlayer.circuit.output[6].count = game.tick % 25000
--- Create a new storage output interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface
function vlayer.create_output_interface(surface, position, last_user)
local interface = surface.create_entity{name='logistic-chest-requester', position=position, force='neutral'}
table.insert(vlayer_data.entity_interfaces.storage_output, interface)
for k, v in pairs(vlayer.power.circuit) do
if (v == nil) or (not v.valid) then
vlayer.power.circuit[k] = nil
if last_user then
interface.last_user = last_user
end
interface.destructible = false
interface.minable = false
interface.operable = true
return interface
end
--- Handle all output interfaces, will take their requests and remove it from the vlayer storage
local function handle_output_interfaces()
for index, interface in pairs(vlayer_data.entity_interfaces.storage_output) do
if not interface.valid then
vlayer_data.entity_interfaces.storage_output[index] = nil
else
local circuit_o = v.get_or_create_control_behavior()
local inventory = interface.get_inventory(defines.inventory.chest)
for i=1, #vlayer.circuit.output do
circuit_o.set_signal(i, {signal=vlayer.circuit.output[i].signal, count=vlayer.circuit.output[i].count})
for i = 1, interface.request_slot_count do
local request = interface.get_request_slot(i)
if request and config.allowed_items[request.name] then
local current_amount = inventory.get_item_count(request.name)
local request_amount = math.min(request.count - current_amount, vlayer_data.storage.items[request.name])
if request_amount > 0 and inventory.can_insert({name=request.name, count=request_amount}) then
local removed_item_count = vlayer.remove_item(request.name, request_amount)
if removed_item_count > 0 then
inventory.insert({name=request.name, count=removed_item_count})
end
end
end
end
end
end
end
--- Handle the unallocated items because more surface area may have been added
local function handle_unallocated()
-- unallocated cant happen when its unlimited
if config.unlimited_surface_area then
return
end
-- Get the total unallocated area so items can be allocated in equal amounts
local unallocated_area = 0
for item_name, count in pairs(vlayer_data.storage.unallocated) do
local item_properties = config.allowed_items[item_name]
unallocated_area = unallocated_area + item_properties.required_area * count
end
if unallocated_area == 0 then
return
end
-- Allocate items in an equal distribution
local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
for item_name, count in pairs(vlayer_data.storage.unallocated) do
local allocation_count = math.min(count, math.floor(count * surplus_area / unallocated_area))
if allocation_count > 0 then
vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] - allocation_count
vlayer.allocate_item(item_name, allocation_count)
end
end
end
--- Get the statistics for the vlayer
function vlayer.get_statistics()
return {
total_surface_area = vlayer_data.properties.total_surface_area,
used_surface_area = vlayer_data.properties.used_surface_area,
energy_production = vlayer_data.properties.production * mega * get_production_multiplier(),
energy_sustained = vlayer_data.properties.production * mega * get_sustained_multiplier(),
energy_capacity = vlayer_data.properties.capacity * mega,
energy_storage = vlayer_data.storage.energy,
day = math.floor(game.tick / vlayer_data.surface.ticks_per_day),
time =math.floor(vlayer_data.surface.daytime * vlayer_data.surface.ticks_per_day)
}
end
--- Circuit signals used for the statistics
local circuit_signals = {
total_surface_area = 'signal-A',
used_surface_area = 'signal-B',
energy_production = 'signal-P',
energy_sustained = 'signal-S',
energy_capacity = 'signal-C',
energy_storage = 'signal-E',
day = 'signal-D',
time = 'signal-T',
}
--- Create a new circuit interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface
function vlayer.create_circuit_interface(surface, position, last_user)
local interface = surface.create_entity{name='constant-combinator', position=position, force='neutral'}
table.insert(vlayer_data.entity_interfaces.circuit, interface)
if last_user then
interface.last_user = last_user
end
interface.destructible = false
interface.minable = false
interface.operable = true
return interface
end
--- Handle all circuit interfaces, updating their signals to match the vlayer statistics
local function handle_circuit_interfaces()
local stats = vlayer.get_statistics()
for index, interface in pairs(vlayer_data.entity_interfaces.circuit) do
if not interface.valid then
vlayer_data.entity_interfaces.circuit[index] = nil
else
local circuit_oc = interface.get_or_create_control_behavior()
local max_signals = circuit_oc.signals_count
local signal_index = 1
-- Set the virtual signals based on the vlayer stats
for stat_name, signal_name in pairs(circuit_signals) do
if stat_name:find('energy') then
circuit_oc.set_signal(signal_index, {signal={type='virtual', name=signal_name}, count=math.floor(stats[stat_name] / mega)})
else
circuit_oc.set_signal(signal_index, {signal={type='virtual', name=signal_name}, count=math.floor(stats[stat_name])})
end
signal_index = signal_index + 1
end
-- Set the item signals based on stored items
for item_name, count in pairs(vlayer_data.storage.items) do
if game.item_prototypes[item_name] and count > 0 then
circuit_oc.set_signal(signal_index, {signal={type='item', name=item_name}, count=count})
signal_index = signal_index + 1
if signal_index > max_signals then
return -- No more signals can be added
end
end
end
-- Clear remaining signals to prevent outdated values being present (caused by count > 0 check)
for clear_index = signal_index, max_signals do
if not circuit_oc.get_signal(clear_index).signal then
break -- There are no more signals to clear
end
circuit_oc.set_signal(clear_index, nil)
end
end
end
end
--- Create a new energy interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface, or nil if it could not be created
function vlayer.create_energy_interface(surface, position, last_user)
if not surface.can_place_entity{name='electric-energy-interface', position=position} then
return nil
end
local interface = surface.create_entity{name='electric-energy-interface', position=position, force='neutral'}
table.insert(vlayer_data.entity_interfaces.energy, interface)
if last_user then
interface.last_user = last_user
end
interface.destructible = false
interface.minable = false
interface.operable = false
interface.electric_buffer_size = 0
interface.power_production = 0
interface.power_usage = 0
interface.energy = 0
return interface
end
--- Handle all energy interfaces as well as the energy storage
local function handle_energy_interfaces()
-- Add the newly produced power
local production = vlayer_data.properties.production * mega * (config.update_tick_energy / 60)
vlayer_data.storage.energy = vlayer_data.storage.energy + math.floor(production * get_production_multiplier())
-- Calculate how much power is present in the network, that is storage + all interfaces
if #vlayer_data.entity_interfaces.energy > 0 then
local available_energy = vlayer_data.storage.energy
for index, interface in pairs(vlayer_data.entity_interfaces.energy) do
if not interface.valid then
vlayer_data.entity_interfaces.energy[index] = nil
else
available_energy = available_energy + interface.energy
end
end
-- Distribute the energy between all interfaces
local discharge_rate = 2 * (production + vlayer_data.properties.discharge * mega) / #vlayer_data.entity_interfaces.energy
local fill_to = math.min(discharge_rate, math.floor(available_energy / #vlayer_data.entity_interfaces.energy))
for index, interface in pairs(vlayer_data.entity_interfaces.energy) do
interface.electric_buffer_size = math.max(discharge_rate, interface.energy) -- prevent energy loss
local delta = fill_to - interface.energy -- positive means storage to interface
vlayer_data.storage.energy = vlayer_data.storage.energy - delta
interface.energy = interface.energy + delta
end
end
-- Cap the stored energy to the allowed capacity
if not config.unlimited_capacity and vlayer_data.storage.energy > vlayer_data.properties.capacity * mega then
vlayer_data.storage.energy = vlayer_data.properties.capacity * mega
end
end
--- Remove the closest entity interface to the given position
-- @tparam LuaSurface surface The surface to search for an interface on
-- @tparam MapPosition position The position to start the search from
-- @tparam number radius The radius to search for an interface within
-- @treturn string The type of interface that was removed, or nil if no interface was found
-- @treturn MapPosition The position the interface was at, or nil if no interface was found
function vlayer.remove_closest_interface(surface, position, radius)
local entities = surface.find_entities_filtered{
name = {'logistic-chest-storage', 'logistic-chest-requester', 'constant-combinator', 'electric-energy-interface'},
force = 'neutral',
position = position,
radius = radius,
limit = 1
}
-- Get the details which will be returned
if #entities == 0 then
return nil, nil
end
local interface = entities[1]
local name = interface.name
local pos = interface.position
-- Return the type of interface removed and do some clean up
if name == 'logistic-chest-storage' then
move_items_stack(interface.get_inventory(defines.inventory.chest).get_contents())
table.remove_element(vlayer_data.entity_interfaces.storage_input, interface)
interface.destroy()
return 'storage input', pos
elseif name == 'logistic-chest-requester' then
move_items_stack(interface.get_inventory(defines.inventory.chest).get_contents())
table.remove_element(vlayer_data.entity_interfaces.storage_output, interface)
interface.destroy()
return 'storage output', pos
elseif name == 'constant-combinator' then
table.remove_element(vlayer_data.entity_interfaces.circuit, interface)
interface.destroy()
return 'circuit', pos
elseif name == 'electric-energy-interface' then
vlayer_data.storage.energy = vlayer_data.storage.energy + interface.energy
table.remove_element(vlayer_data.entity_interfaces.energy, interface)
interface.destroy()
return 'energy', pos
end
end
local function on_surface_event()
if config.mimic_surface then
local surface = game.get_surface(config.mimic_surface)
if surface then
vlayer_data.surface = surface
return
end
end
if not vlayer_data.surface.index then
-- Our fake surface data never has an index, we test for this to avoid unneeded copies from the config
vlayer_data.surface = table.deep_copy(config.surface)
end
end
--- Handle all storage IO and attempt allocation of unallocated items
Event.on_nth_tick(config.update_tick_storage, function(_)
handle_input_interfaces()
handle_output_interfaces()
handle_unallocated()
end)
--- Handle all energy and circuit updates
Event.on_nth_tick(config.update_tick_energy, function(_)
handle_circuit_interfaces()
handle_energy_interfaces()
end)
Event.add(defines.events.on_surface_created, on_surface_event)
Event.add(defines.events.on_surface_renamed, on_surface_event)
Event.add(defines.events.on_surface_imported, on_surface_event)
Event.on_init(on_surface_event) -- Default surface always exists, does not trigger on_surface_created
return vlayer

View File

@@ -1,276 +1,404 @@
--- Adds a virtual layer to store power to save space.
-- @addon Virtual Layer
--[[-- Gui Module - Virtual Layer
- Adds a virtual layer to store power to save space.
@gui Virtual Layer
@alias vlayer_container
]]
local Gui = require 'expcore.gui' --- @dep expcore.gui
local Roles = require 'expcore.roles' --- @dep expcore.roles
local Event = require 'utils.event' --- @dep utils.event
local format_number = require('util').format_number --- @dep util
local config = require 'config.vlayer' --- @dep config.vlayer
local format_number = require('util').format_number
local vlayer = require 'modules.control.vlayer'
local vlayer_container
local vlayer_display = {}
local function pos_to_gps_string(pos)
return '[gps=' .. tostring(pos.x) .. ',' .. tostring(pos.y) .. ']'
end
for i=1, #config.gui.content do
if config.gui.content[i].type == 'item' or config.gui.content[i].type == 'signal' then
vlayer_display[i] = {
type = config.gui.content[i].type,
name = config.gui.content[i].name,
count = 0
}
local function format_energy(amount, unit)
if amount < 1 then
return '0 ' .. unit
end
local suffix = ''
local suffix_list = {
['T'] = 1000000000000,
['G'] = 1000000000,
['M'] = 1000000,
['k'] = 1000
}
for letter, limit in pairs (suffix_list) do
if math.abs(amount) >= limit then
amount = string.format('%.1f', amount / limit)
suffix = letter
break
end
end
local formatted, k = amount
while true do
formatted, k = string.gsub(formatted, '^(-?%d+)(%d%d%d)', '%1,%2')
if (k == 0) then
break
end
end
return formatted .. ' ' .. suffix .. unit
end
local function vlayer_convert_chest(player)
local entities = player.surface.find_entities_filtered{position=player.position, radius=8, name='steel-chest', force=player.force}
if (not entities or (#entities == 0)) then
return nil
local entities = player.surface.find_entities_filtered{position=player.position, radius=8, name='steel-chest', force=player.force, limit=1}
if (not entities or #entities == 0) then
player.print('No steel chest detected')
return
end
local target_chest = player.surface.get_closest(player.position, entities)
if (not target_chest) then
player.print('No Steel Chest Detected')
return nil
end
local entity = entities[1]
local pos = entity.position
if (not target_chest.get_inventory(defines.inventory.chest).is_empty()) then
if (not entity.get_inventory(defines.inventory.chest).is_empty()) then
player.print('Chest is not emptied')
return nil
end
local pos = target_chest.position
if (not target_chest.destroy()) then
player.print('Unable to convert chest')
return nil
end
return {x=math.floor(pos.x), y=math.floor(pos.y)}
entity.destroy()
return {x=string.format('%.1f', pos.x), y=string.format('%.1f', pos.y)}
end
local function vlayer_convert_chest_storage_input(player)
--- Display label for the number of solar panels
-- @element vlayer_gui_display_item_solar_name
local vlayer_gui_display_item_solar_name =
Gui.element{
type = 'label',
name = 'vlayer_display_item_solar_name',
caption = '[img=entity/solar-panel] Solar Panel',
style = 'heading_1_label'
}:style{
width = 200
}
local vlayer_gui_display_item_solar_count =
Gui.element{
type = 'label',
name = 'vlayer_display_item_solar_count',
caption = '0',
style = 'heading_1_label'
}:style{
width = 120
}
--- Display label for the number of accumulators
-- @element vlayer_gui_display_item_accumulator_name
local vlayer_gui_display_item_accumulator_name =
Gui.element{
type = 'label',
name = 'vlayer_display_item_accumulator_name',
caption = '[img=entity/accumulator] Accumulator',
style = 'heading_1_label'
}:style{
width = 200
}
local vlayer_gui_display_item_accumulator_count =
Gui.element{
type = 'label',
name = 'vlayer_display_item_accumulator_count',
caption = '0',
style = 'heading_1_label'
}:style{
width = 120
}
--- Display label for the current energy production
-- @element vlayer_gui_display_signal_production_name
local vlayer_gui_display_signal_production_name =
Gui.element{
type = 'label',
name = 'vlayer_display_signal_peak_name',
caption = '[virtual-signal=signal-P] Current Production',
style = 'heading_1_label'
}:style{
width = 200
}
local vlayer_gui_display_signal_production_count =
Gui.element{
type = 'label',
name = 'vlayer_display_signal_peak_solar_count',
caption = '0',
style = 'heading_1_label'
}:style{
width = 120
}
--- Display label for the sustained energy production
-- @element vlayer_gui_display_signal_sustained_name
local vlayer_gui_display_signal_sustained_name =
Gui.element{
type = 'label',
name = 'vlayer_display_signal_sustained_name',
caption = '[virtual-signal=signal-S] Sustained Production',
style = 'heading_1_label'
}:style{
width = 200
}
local vlayer_gui_display_signal_sustained_count =
Gui.element{
type = 'label',
name = 'vlayer_display_signal_sustained_count',
caption = '0',
style = 'heading_1_label'
}:style{
width = 120
}
--- Display label for the sustained energy capacity
-- @element vlayer_gui_display_signal_capacity_name
local vlayer_gui_display_signal_capacity_name =
Gui.element{
type = 'label',
name = 'vlayer_display_signal_max_name',
caption = '[virtual-signal=signal-C] Battery Capacity',
style = 'heading_1_label'
}:style{
width = 200
}
local vlayer_gui_display_signal_capacity_count =
Gui.element{
type = 'label',
name = 'vlayer_display_signal_max_count',
caption = '0',
style = 'heading_1_label'
}:style{
width = 120
}
--- Display label for the current energy in storage
-- @element vlayer_gui_display_signal_current_name
local vlayer_gui_display_signal_current_name =
Gui.element{
type = 'label',
name = 'vlayer_display_signal_current_name',
caption = '[virtual-signal=signal-E] Battery Charge',
style = 'heading_1_label'
}:style{
width = 200
}
local vlayer_gui_display_signal_current_count =
Gui.element{
type = 'label',
name = 'vlayer_display_signal_current_count',
caption = '0',
style = 'heading_1_label'
}:style{
width = 120
}
--- A vertical flow containing all the displays labels and their counts
-- @element vlayer_display_set
local vlayer_display_set =
Gui.element(function(_, parent, name)
local vlayer_set = parent.add{type='flow', direction='vertical', name=name}
local disp = Gui.scroll_table(vlayer_set, 320, 2, 'disp')
vlayer_gui_display_item_solar_name(disp)
vlayer_gui_display_item_solar_count(disp)
vlayer_gui_display_item_accumulator_name(disp)
vlayer_gui_display_item_accumulator_count(disp)
vlayer_gui_display_signal_production_name(disp)
vlayer_gui_display_signal_production_count(disp)
vlayer_gui_display_signal_sustained_name(disp)
vlayer_gui_display_signal_sustained_count(disp)
vlayer_gui_display_signal_capacity_name(disp)
vlayer_gui_display_signal_capacity_count(disp)
vlayer_gui_display_signal_current_name(disp)
vlayer_gui_display_signal_current_count(disp)
return vlayer_set
end)
local function pos_to_gps_string(pos)
return '[gps=' .. string.format('%.1f', pos.x) .. ',' .. string.format('%.1f', pos.y) .. ']'
end
--- A button used to add a new storage input interface
-- @element vlayer_gui_control_storage_input
local vlayer_gui_control_storage_input =
Gui.element{
type = 'button',
caption = 'Add Input Storage'
}:style{
width = 160
}:on_click(function(player, element, _)
local pos = vlayer_convert_chest(player)
if (pos) then
local vlayer_storage = player.surface.create_entity{name='logistic-chest-storage', position=pos, force='neutral'}
game.print(player.name .. ' built a vlayer input on ' .. pos_to_gps_string(pos))
vlayer_storage.destructible = false
vlayer_storage.minable = false
vlayer_storage.operable = true
vlayer_storage.last_user = player
table.insert(vlayer.storage.input, vlayer_storage)
vlayer.create_input_interface(player.surface, pos, player)
game.print(player.name .. ' built a vlayer storage input on ' .. pos_to_gps_string(pos))
end
end
local function vlayer_convert_chest_power(player)
element.enabled = (vlayer.get_interface_counts().storage_input < config.interface_limit.storage_input)
end)
--- A button used to add a new storage output interface
-- @element vlayer_gui_control_storage_output
local vlayer_gui_control_storage_output =
Gui.element{
type = 'button',
caption = 'Add Output Storage'
}:style{
width = 160
}:on_click(function(player, element, _)
local pos = vlayer_convert_chest(player)
if (pos) then
if (player.surface.can_place_entity{name='electric-energy-interface', position=pos})then
local vlayer_power = player.surface.create_entity{name='electric-energy-interface', position=pos, force='neutral'}
game.print(player.name .. ' built a vlayer energy interface on ' .. pos_to_gps_string(pos))
vlayer_power.destructible = false
vlayer_power.minable = false
vlayer_power.operable = false
vlayer_power.last_user = player
vlayer_power.electric_buffer_size = math.floor(config.energy_base_limit / 2)
vlayer_power.power_production = math.floor(config.energy_base_limit / 60)
vlayer_power.power_usage = math.floor(config.energy_base_limit / 60)
vlayer_power.energy = 0
table.insert(vlayer.power.entity, vlayer_power)
else
player.print('Unable to build energy entity')
end
vlayer.create_output_interface(player.surface, pos, player)
game.print(player.name .. ' built a vlayer storage output on ' .. pos_to_gps_string(pos))
end
end
local function vlayer_convert_chest_circuit(player)
element.enabled = (vlayer.get_interface_counts().storage_output < config.interface_limit.storage_output)
end)
--- A button used to add a new circuit interface
-- @element vlayer_gui_control_circuit
local vlayer_gui_control_circuit =
Gui.element{
type = 'button',
caption = 'Add Circuit'
}:style{
width = 160
}:on_click(function(player, element, _)
local pos = vlayer_convert_chest(player)
if (pos) then
local circuit_o = player.surface.create_entity{name='constant-combinator', position=pos, force='neutral'}
vlayer.create_circuit_interface(player.surface, pos, player)
game.print(player.name .. ' built a vlayer circuit on ' .. pos_to_gps_string(pos))
circuit_o.destructible = false
circuit_o.minable = false
circuit_o.operable = true
circuit_o.last_user = player
local circuit_oc = circuit_o.get_or_create_control_behavior()
circuit_oc.set_signal(1, {signal={type='virtual', name='signal-P'}, count=0})
circuit_oc.set_signal(2, {signal={type='virtual', name='signal-S'}, count=0})
circuit_oc.set_signal(3, {signal={type='virtual', name='signal-M'}, count=0})
circuit_oc.set_signal(4, {signal={type='virtual', name='signal-C'}, count=0})
circuit_oc.set_signal(5, {signal={type='virtual', name='signal-D'}, count=0})
circuit_oc.set_signal(6, {signal={type='virtual', name='signal-T'}, count=0})
circuit_oc.set_signal(7, {signal={type='virtual', name='signal-L'}, count=0})
circuit_oc.set_signal(8, {signal={type='virtual', name='signal-A'}, count=0})
circuit_oc.set_signal(9, {signal={type='virtual', name='signal-B'}, count=0})
circuit_oc.set_signal(10, {signal={type='item', name='solar-panel'}, count=0})
circuit_oc.set_signal(11, {signal={type='item', name='accumulator'}, count=0})
table.insert(vlayer.power.circuit, circuit_o)
end
end
local function vlayer_convert_remove(player)
local entities = player.surface.find_entities_filtered{name={'logistic-chest-storage', 'constant-combinator', 'electric-energy-interface'}, position=player.position, radius=8, force='neutral', limit=1}
element.enabled = (vlayer.get_interface_counts().circuit < config.interface_limit.circuit)
end)
if (not entities or #entities == 0) then
player.print('Entity not found')
else
for _, v in pairs(entities) do
local name = v.name
game.print(player.name .. ' removed a vlayer ' .. config.print_out[v.name] .. ' on ' .. pos_to_gps_string(v.position))
v.destroy()
--- A button used to add a new energy interface
-- @element vlayer_gui_control_power
local vlayer_gui_control_power =
Gui.element{
type = 'button',
caption = 'Add Power'
}:style{
width = 160
}:on_click(function(player, element, _)
local pos = vlayer_convert_chest(player)
if name == 'logistic-chest-storage' then
for k, vl in pairs(vlayer.storage.input) do
if not vl.valid then
vlayer.storage.input[k] = nil
end
end
elseif name == 'constant-combinator' then
for k, vl in pairs(vlayer.power.circuit) do
if not vl.valid then
vlayer.power.circuit[k] = nil
end
end
elseif name == 'electric-energy-interface' then
for k, vl in pairs(vlayer.power.entity) do
if not vl.valid then
vlayer.power.entity[k] = nil
end
end
end
if (pos) then
if vlayer.create_energy_interface(player.surface, pos, player) then
game.print(player.name .. ' built a vlayer energy interface on ' .. pos_to_gps_string(pos))
else
player.print('Unable to build vlayer energy entity')
end
end
end
local vlayer_gui_update
local button_power =
Gui.element{
name = 'button_1',
type = 'button',
caption = 'Power Entity',
style = 'button'
}:on_click(function(player)
vlayer_convert_chest_power(player)
vlayer_gui_update()
element.enabled = (vlayer.get_interface_counts().energy < config.interface_limit.energy)
end)
local button_storage_input =
--- A button used to remove the closest vlayer interface
-- @element vlayer_gui_control_remove
local vlayer_gui_control_remove =
Gui.element{
name = 'button_2',
type = 'button',
caption = 'Storage Input',
style = 'button'
}:on_click(function(player)
vlayer_convert_chest_storage_input(player)
vlayer_gui_update()
end)
local button_circuit =
Gui.element{
name = 'button_3',
type = 'button',
caption = 'Circuit',
style = 'button'
}:on_click(function(player)
vlayer_convert_chest_circuit(player)
vlayer_gui_update()
end)
local button_remove =
Gui.element{
name = 'button_4',
type = 'button',
caption = 'Remove',
style = 'button'
}:on_click(function(player)
vlayer_convert_remove(player)
vlayer_gui_update()
end)
function vlayer_gui_update()
local button_power_enabled = #vlayer.power.entity < config.interface_limit.energy
local button_storage_input_enabled = #vlayer.storage.input < config.interface_limit.storage_input
local button_circuit_enabled = #vlayer.power.circuit < config.interface_limit.circuit
for _, player in pairs(game.connected_players) do
local frame = Gui.get_left_element(player, vlayer_container)
frame.container.scroll.table[button_power.name].enabled = button_power_enabled
frame.container.scroll.table[button_storage_input.name].enabled = button_storage_input_enabled
frame.container.scroll.table[button_circuit.name].enabled = button_circuit_enabled
caption = 'Remove Special'
}:style{
width = 160
}:on_click(function(player, element, _)
local interface_type, interface_position = vlayer.remove_closest_interface(player.surface, player.position, 4)
if not interface_type then
return player.print('Interface not found in range, please move closer')
end
end
vlayer_container =
local interfaces = vlayer.get_interface_counts()
game.print(player.name .. ' removed a vlayer ' .. interface_type .. ' on ' .. pos_to_gps_string(interface_position))
element.parent[vlayer_gui_control_storage_input.name].enabled = (interfaces.storage_input < config.interface_limit.storage_input)
element.parent[vlayer_gui_control_storage_output.name].enabled = (interfaces.storage_output < config.interface_limit.storage_output)
element.parent[vlayer_gui_control_circuit.name].enabled = (interfaces.circuit < config.interface_limit.circuit)
element.parent[vlayer_gui_control_power.name].enabled = (interfaces.energy < config.interface_limit.energy)
end)
--- A vertical flow containing all the control buttons
-- @element vlayer_control_set
local vlayer_control_set =
Gui.element(function(_, parent, name)
local vlayer_set = parent.add{type='flow', direction='vertical', name=name}
local disp = Gui.scroll_table(vlayer_set, 320, 2, 'disp')
local interfaces = vlayer.get_interface_counts()
vlayer_gui_control_storage_input(disp).enabled = (interfaces.storage_input < config.interface_limit.storage_input)
vlayer_gui_control_storage_output(disp).enabled = (interfaces.storage_output < config.interface_limit.storage_output)
vlayer_gui_control_circuit(disp).enabled = (interfaces.circuit < config.interface_limit.circuit)
vlayer_gui_control_power(disp).enabled = (interfaces.energy < config.interface_limit.energy)
vlayer_gui_control_remove(disp)
return vlayer_set
end)
--- The main container for the vlayer gui
-- @element vlayer_container
local vlayer_container =
Gui.element(function(event_trigger, parent)
local player = Gui.get_player_from_element(parent)
local container = Gui.container(parent, event_trigger, 300)
Gui.header(container, 'VLAYER', '', true)
local scroll_table = Gui.scroll_table(container, 300, 2)
for i=1, #config.gui.content do
scroll_table.add{
name = 'vlayer_display_' .. i,
caption = config.gui.content[i].title,
type = config.gui.type,
style = config.gui.style
}
end
button_power(scroll_table)
button_storage_input(scroll_table)
button_circuit(scroll_table)
button_remove(scroll_table)
local container = Gui.container(parent, event_trigger, 320)
if (config.land.enabled ~= true) then
for i=7, 12 do
scroll_table['vlayer_display_' .. i].visible = false
end
end
vlayer_display_set(container, 'vlayer_st_1')
vlayer_control_set(container, 'vlayer_st_2')
if not (Roles.player_allowed(player, 'gui/vlayer-edit')) then
scroll_table['vlayer_display_' .. #config.gui.content - 1].visible = false
scroll_table['vlayer_display_' .. #config.gui.content].visible = false
scroll_table[button_power.name].visible = false
scroll_table[button_storage_input.name].visible = false
scroll_table[button_circuit.name].visible = false
scroll_table[button_remove.name].visible = false
end
scroll_table[button_power.name].enabled = (#vlayer.power.entity < config.interface_limit.energy)
scroll_table[button_storage_input.name].enabled = (#vlayer.storage.input < config.interface_limit.storage_input)
scroll_table[button_circuit.name].enabled = (#vlayer.power.circuit < config.interface_limit.circuit)
local table = container['vlayer_st_2'].disp.table
local visible = Roles.player_allowed(player, 'gui/vlayer-edit')
table[vlayer_gui_control_storage_input.name].visible = visible
table[vlayer_gui_control_storage_output.name].visible = visible
table[vlayer_gui_control_circuit.name].visible = visible
table[vlayer_gui_control_power.name].visible = visible
table[vlayer_gui_control_remove.name].visible = visible
return container.parent
end)
:add_to_left_flow()
--- Button on the top flow used to toggle the task list container
-- @element toggle_left_element
Gui.left_toolbar_button('entity/solar-panel', {'vlayer.main-tooltip'}, vlayer_container, function(player)
return Roles.player_allowed(player, 'gui/vlayer')
end)
Event.on_nth_tick(config.update_tick, function()
for _, v in pairs(vlayer_display) do
if v.type == 'item' then
v.count = format_number(vlayer.storage.item[v.name])
elseif v.type == 'signal' then
v.count = format_number(vlayer.circuit.output[v.name].count)
end
end
--- Update the visibly of the buttons based on a players roles
local function role_update_event(event)
local player = game.players[event.player_index]
local visible = Roles.player_allowed(player, 'gui/vlayer-edit')
local frame = Gui.get_left_element(player, vlayer_container)
frame.container['vlayer_st_2'].visible = visible
end
Event.add(Roles.events.on_role_assigned, role_update_event)
Event.add(Roles.events.on_role_unassigned, role_update_event)
Event.on_nth_tick(config.update_tick_gui, function(_)
local stats = vlayer.get_statistics()
local items = vlayer.get_items()
local vlayer_display = {
[vlayer_gui_display_item_solar_count.name] = format_number(items['solar-panel']),
[vlayer_gui_display_item_accumulator_count.name] = format_number(items['accumulator']),
[vlayer_gui_display_signal_production_count.name] = format_energy(stats.energy_production, 'W'),
[vlayer_gui_display_signal_sustained_count.name] = format_energy(stats.energy_sustained, 'W'),
[vlayer_gui_display_signal_capacity_count.name] = format_energy(stats.energy_capacity, 'J'),
[vlayer_gui_display_signal_current_count.name] = format_energy(stats.energy_storage, 'J'),
}
for _, player in pairs(game.connected_players) do
local frame = Gui.get_left_element(player, vlayer_container)
local table = frame.container['vlayer_st_1'].disp.table
for k, v in pairs(vlayer_display) do
frame.container.scroll.table['vlayer_display_' .. k].caption = v.count
table[k].caption = v
end
end
end)