mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-12 07:54:52 +09:00
Move the virtual layer into exp_scenario (#463)
The virtual layer was a legacy control module and a legacy gui built on the legacy event system. The control module moves to exp_scenario/module/control/vlayer.lua in the event_handler shape, with the data typed and the allowed item table built locally rather than by rewriting the config. The gui moves to exp_scenario/module/gui/vlayer.lua in the shape of the other scenario guis: statistics as label and bar defines refreshed from display data computed once, and the interface controls as a define which owns its dropdowns. Api changes while moving: interfaces are removed by entity rather than by searching a position, the dead circuit wiring parameter is gone since connect_neighbour no longer exists, and remove_item now takes only the unallocated amount out of storage and counts that production back out of the total, which the legacy module got wrong. The locale moves to [exp-gui_vlayer] in en, zh-CN and zh-TW and the legacy [vlayer] sections, modules and file loader entries are removed. Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
This commit is contained in:
1 parent
a2ce3ded81
commit
862fe49b60
14 files changed
+1310
-1484
No files matched your search
@@ -6,9 +6,6 @@
|
||||
return {
|
||||
"expcore.player_data", -- must be loaded first to register event handlers
|
||||
|
||||
-- Control
|
||||
"modules.control.vlayer",
|
||||
|
||||
--- Data
|
||||
"modules.data.statistics",
|
||||
"modules.data.player-colours",
|
||||
@@ -17,10 +14,5 @@ return {
|
||||
"modules.data.alt-view",
|
||||
"modules.data.tag",
|
||||
"modules.data.language",
|
||||
--"modules.data.toolbar",
|
||||
|
||||
--- GUI
|
||||
"modules.gui.vlayer",
|
||||
|
||||
"modules.graftorio.require", -- graftorio
|
||||
}
|
||||
@@ -184,42 +184,3 @@ display-fwrsb=Robot Storage
|
||||
display-fwrsb-tooltip=Force worker robots storage bonus
|
||||
display-ffrlm=Robot Lifetime
|
||||
display-ffrlm-tooltip=Force following robots lifetime modifier
|
||||
|
||||
[vlayer]
|
||||
main-tooltip=vlayer
|
||||
description-pbr=Recharge Player Battery upto a portion with vlayer
|
||||
description-vi=Vlayer Info
|
||||
pbr-not-running=vlayer need to be running
|
||||
display-item-solar=[img=entity/solar-panel] Solar Panel
|
||||
display-item-accumulator=[img=entity/accumulator] Accumulator
|
||||
display-current-production=[virtual-signal=signal-P] Current Production
|
||||
display-sustained-production=[virtual-signal=signal-S] Sustained Production
|
||||
display-max-capacity=[virtual-signal=signal-C] Battery Capacity
|
||||
display-current-capacity=[virtual-signal=signal-E] Battery Charge
|
||||
display-remaining-surface-area=[virtual-signal=signal-R] Remaining Surface
|
||||
display-item-solar-tooltip=The amount of Solar Panel
|
||||
display-item-accumulator-tooltip=The amount of Accumulator
|
||||
display-current-production-tooltip=Current Power Production
|
||||
display-sustained-production-tooltip=Sustained Power Production
|
||||
display-max-capacity-tooltip=Battery Max Capacity
|
||||
display-current-capacity-tooltip=Battery Current Charge
|
||||
display-remaining-surface-area-tooltip=Remaining Surface. Insert Landfill to increase
|
||||
steel-chest-detect=No steel chest detected
|
||||
steel-chest-empty=Chest is not emptied
|
||||
control-refresh=Refresh List
|
||||
control-see=See Interface
|
||||
control-build=Build Interface
|
||||
control-remove=Remove Interface
|
||||
result-build=built
|
||||
result-remove=removed
|
||||
result-interface-location=__1__ interface was located at __2__
|
||||
interface-result=__1__ __2__ __4__ interface at __3__
|
||||
result-unable=Unable to build __1__ interface, because of __2__
|
||||
result-multiple=Multiple steel chest detected
|
||||
result-limit=Max limit exceeded
|
||||
result-space=Not enough space
|
||||
control-type-energy=Energy
|
||||
control-type-circuit=Circuit
|
||||
control-type-storage-input=Storage Input
|
||||
control-type-storage-output=Storage Output
|
||||
power-on-space-research=You need to research [technology=__1__] at __2__ level for the space platform vlayer feature.
|
||||
@@ -185,45 +185,6 @@ display-fwrsb-tooltip=勢力機械人儲存
|
||||
display-ffrlm=機械人生命
|
||||
display-ffrlm-tooltip=勢力戰鬥機械人生命時間
|
||||
|
||||
[vlayer]
|
||||
main-tooltip=vlayer
|
||||
description-pbr=使用 Vlayer 為玩家電池充電
|
||||
description-vi=Vlayer 資訊
|
||||
pbr-not-running=vlayer 沒有運行
|
||||
display-item-solar=[img=entity/solar-panel] 太陽能板
|
||||
display-item-accumulator=[img=entity/accumulator] 蓄電池
|
||||
display-current-production=[virtual-signal=signal-P] 現時產能
|
||||
display-sustained-production=[virtual-signal=signal-S] 持續產能
|
||||
display-max-capacity=[virtual-signal=signal-C] 電池容量
|
||||
display-current-capacity=[virtual-signal=signal-E] 電池電量
|
||||
display-remaining-surface-area=[virtual-signal=signal-R] 餘下土地
|
||||
display-item-solar-tooltip=太陽能板數量
|
||||
display-item-accumulator-tooltip=蓄電池數量
|
||||
display-current-production-tooltip=現時電力產能
|
||||
display-sustained-production-tooltip=持續電力產能
|
||||
display-max-capacity-tooltip=電池最大容量
|
||||
display-current-capacity-tooltip=現時電池電量
|
||||
display-remaining-surface-area-tooltip=餘下土地。 放入堆填來增加
|
||||
steel-chest-detect=沒檢測到鋼箱
|
||||
steel-chest-empty=鋼箱不是空的
|
||||
control-refresh=刷新列表
|
||||
control-see=查看介面
|
||||
control-build=建造介面
|
||||
control-remove=拆除介面
|
||||
result-build=建造了
|
||||
result-remove=拆除了
|
||||
result-interface-location=__1__ 介面在 __2__
|
||||
interface-result=__1__ 在 __2__ __3__ __4__ 介面
|
||||
result-unable=無法建造 __1__ 介面,因 __2__
|
||||
result-multiple=檢測到多個鋼箱
|
||||
result-limit=超出最大限制
|
||||
result-space=空間不足
|
||||
control-type-energy=電力
|
||||
control-type-circuit=回路
|
||||
control-type-storage-input=放入箱
|
||||
control-type-storage-output=提取箱
|
||||
power-on-space-research=你要研究 [technology=__1__] 在 __2__ 級 才可使用太空平台 vlayer 功能。
|
||||
|
||||
[toolbar]
|
||||
main-caption=工具箱
|
||||
main-tooltip=工具箱設定\n選上來設定喜好
|
||||
|
||||
@@ -185,45 +185,6 @@ display-fwrsb-tooltip=勢力機械人儲存
|
||||
display-ffrlm=機械人生命
|
||||
display-ffrlm-tooltip=勢力戰鬥機械人生命時間
|
||||
|
||||
[vlayer]
|
||||
main-tooltip=vlayer
|
||||
description-pbr=使用 Vlayer 為玩家電池充電
|
||||
description-vi=Vlayer 資訊
|
||||
pbr-not-running=vlayer 沒有運行
|
||||
display-item-solar=[img=entity/solar-panel] 太陽能板
|
||||
display-item-accumulator=[img=entity/accumulator] 蓄電池
|
||||
display-current-production=[virtual-signal=signal-P] 現時產能
|
||||
display-sustained-production=[virtual-signal=signal-S] 持續產能
|
||||
display-max-capacity=[virtual-signal=signal-C] 電池容量
|
||||
display-current-capacity=[virtual-signal=signal-E] 電池電量
|
||||
display-remaining-surface-area=[virtual-signal=signal-R] 餘下土地
|
||||
display-item-solar-tooltip=太陽能板數量
|
||||
display-item-accumulator-tooltip=蓄電池數量
|
||||
display-current-production-tooltip=現時電力產能
|
||||
display-sustained-production-tooltip=持續電力產能
|
||||
display-max-capacity-tooltip=電池最大容量
|
||||
display-current-capacity-tooltip=現時電池電量
|
||||
display-remaining-surface-area-tooltip=餘下土地。 放入堆填來增加
|
||||
steel-chest-detect=沒檢測到鋼箱
|
||||
steel-chest-empty=鋼箱不是空的
|
||||
control-refresh=刷新列表
|
||||
control-see=查看介面
|
||||
control-build=建造介面
|
||||
control-remove=拆除介面
|
||||
result-build=建造了
|
||||
result-remove=拆除了
|
||||
result-interface-location=__1__ 介面在 __2__
|
||||
interface-result=__1__ 在 __2__ __3__ __4__ 介面
|
||||
result-unable=無法建造 __1__ 介面,因 __2__
|
||||
result-multiple=檢測到多個鋼箱
|
||||
result-limit=超出最大限制
|
||||
result-space=空間不足
|
||||
control-type-energy=電力
|
||||
control-type-circuit=回路
|
||||
control-type-storage-input=放入箱
|
||||
control-type-storage-output=提取箱
|
||||
power-on-space-research=你要研究 [technology=__1__] 在 __2__ 級 才可使用太空平台 vlayer 功能。
|
||||
|
||||
[toolbar]
|
||||
main-caption=工具箱
|
||||
main-tooltip=工具箱設定\n選上來設定喜好
|
||||
|
||||
@@ -1,807 +0,0 @@
|
||||
--[[-- Control Module - vlayer
|
||||
- Adds a virtual layer to store power to save space.
|
||||
@control vlayer
|
||||
@alias vlayer
|
||||
]]
|
||||
|
||||
local ExpUtil = require("modules/exp_util")
|
||||
local Storage = require("modules/exp_util/storage")
|
||||
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
|
||||
local config = require("modules.exp_legacy.config.vlayer") --- @dep config.vlayer
|
||||
|
||||
local mega = 1000000
|
||||
|
||||
local vlayer = {}
|
||||
local vlayer_data = {
|
||||
entity_interfaces = {
|
||||
energy = {}, --- @type LuaEntity[]
|
||||
circuit = {}, --- @type LuaEntity[]
|
||||
storage_input = {}, --- @type LuaEntity[]
|
||||
storage_output = {}, --- @type LuaEntity[]
|
||||
},
|
||||
properties = {
|
||||
total_surface_area = 0,
|
||||
used_surface_area = 0,
|
||||
total_production = 0,
|
||||
production = 0,
|
||||
discharge = 0,
|
||||
capacity = 0,
|
||||
},
|
||||
storage = {
|
||||
items = {}, --- @type table<string, number>
|
||||
power_items = {}, --- @type table<string, { value: number, count: number }>
|
||||
energy = 0,
|
||||
unallocated = {}, --- @type table<string, number>
|
||||
},
|
||||
surface = table.deep_copy(config.surface),
|
||||
}
|
||||
|
||||
Storage.register(vlayer_data, function(tbl)
|
||||
vlayer_data = tbl
|
||||
end)
|
||||
|
||||
for name, properties in pairs(config.allowed_items) do
|
||||
properties.modded = false
|
||||
|
||||
if properties.power then
|
||||
vlayer_data.storage.power_items[name] = {
|
||||
value = assert(properties.fuel_value) * 1000000,
|
||||
count = 0,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- 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
|
||||
|
||||
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
|
||||
|
||||
--- 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
|
||||
|
||||
--- Get all unallocated items in storage
|
||||
-- @treturn table a dictionary of all unallocated items stored in the vlayer
|
||||
function vlayer.get_unallocated_items()
|
||||
return vlayer_data.storage.unallocated
|
||||
end
|
||||
|
||||
--- Get all allocated items in storage
|
||||
-- @treturn table a dictionary of all allocated items stored in the vlayer
|
||||
function vlayer.get_allocated_items()
|
||||
local r = {}
|
||||
|
||||
for k, v in pairs(vlayer_data.storage.items) do
|
||||
r[k] = v
|
||||
|
||||
if vlayer_data.storage.unallocated[k] then
|
||||
r[k] = r[k] - vlayer_data.storage.unallocated[k]
|
||||
end
|
||||
end
|
||||
|
||||
return r
|
||||
end
|
||||
|
||||
--- Get the actual defecit of land
|
||||
local function get_actual_land_defecit()
|
||||
local n = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
|
||||
|
||||
for k, v in pairs(vlayer.get_unallocated_items()) do
|
||||
n = n - (config.allowed_items[k].required_area * v)
|
||||
end
|
||||
|
||||
return n
|
||||
end
|
||||
|
||||
--- 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
|
||||
|
||||
--- Get interfaces
|
||||
-- @treturn table a dictionary of the vlayer interfaces
|
||||
function vlayer.get_interfaces()
|
||||
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,200 / 420 s
|
||||
昼 210秒 ソーラー効率100%
|
||||
夕方 84秒 1秒ごとにソーラー発電量が約1.2%ずつ下がり、やがて0%になる
|
||||
夜 42秒 ソーラー発電量が0%になる
|
||||
朝方 84秒 1秒ごとにソーラー発電量が約1.2%ずつ上がり、やがて100%になる
|
||||
|
||||
(surface.dawn) 0.75 18,900 Day 12,600 210s
|
||||
0.00 0 Noon
|
||||
(surface.dusk) 0.25 6,300 Sunset 5,040 84s
|
||||
(surface.evening) 0.45 11,340 Night 2,520 42s
|
||||
(surface.morning) 0.55 13,860 Sunrise 5,040 84s
|
||||
]]
|
||||
|
||||
--- 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
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
-- 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 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: 210s + (1/2 x (84s + 84s))
|
||||
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
|
||||
local nc = item_properties.production * count
|
||||
vlayer_data.properties.production = vlayer_data.properties.production + nc
|
||||
vlayer_data.properties.total_production = vlayer_data.properties.total_production + nc
|
||||
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
|
||||
|
||||
function vlayer.unable_alloc_item_pwr_calc(item_name, count)
|
||||
local item_properties = config.allowed_items[item_name]
|
||||
|
||||
if item_properties.production then
|
||||
vlayer_data.properties.total_production = vlayer_data.properties.total_production + item_properties.production * 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
|
||||
|
||||
local unallocated = count - allocate_count
|
||||
vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] + unallocated
|
||||
vlayer.unable_alloc_item_pwr_calc(item_name, unallocated)
|
||||
else
|
||||
vlayer.allocate_item(item_name, count)
|
||||
end
|
||||
end
|
||||
|
||||
--- 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
|
||||
|
||||
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])
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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, circuit, last_user)
|
||||
local interface = surface.create_entity{ name = "storage-chest", position = position, force = "neutral" }
|
||||
interface.storage_filter = { name = "deconstruction-planner", quality = "normal" }
|
||||
table.insert(vlayer_data.entity_interfaces.storage_input, interface)
|
||||
|
||||
if last_user then
|
||||
interface.last_user = last_user
|
||||
end
|
||||
|
||||
if circuit then
|
||||
for k, _ in pairs(circuit) do
|
||||
for _, v in pairs(circuit[k]) do
|
||||
interface.connect_neighbour{ wire = defines.wire_type[k], target_entity = v }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
interface.destructible = false
|
||||
interface.minable_flag = 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
|
||||
local inventory = assert(interface.get_inventory(defines.inventory.chest))
|
||||
|
||||
for _, v in pairs(inventory.get_contents()) do
|
||||
if config.allowed_items[v.name] then
|
||||
--[[
|
||||
there are no quality support currently.
|
||||
so instead, using the stats projection value for higher quality.
|
||||
]]
|
||||
local count_deduct
|
||||
local count_add
|
||||
|
||||
if prototypes.quality[v.quality].level == 0 then
|
||||
count_deduct = v.count
|
||||
count_add = v.count
|
||||
|
||||
elseif prototypes.quality[v.quality].level > 0 and v.count >= 10 then
|
||||
local batch = math.floor(v.count / 10)
|
||||
count_deduct = batch * 10
|
||||
count_add = batch * (10 + (prototypes.quality[v.quality].level * 3))
|
||||
end
|
||||
|
||||
if count_deduct and count_add then
|
||||
if config.allowed_items[v.name].modded then
|
||||
local modded_item = assert(config.modded_items[v.name])
|
||||
if config.modded_auto_downgrade then
|
||||
vlayer.insert_item(modded_item.base_game_equivalent, count_add * modded_item.multiplier)
|
||||
else
|
||||
vlayer.insert_item(v.name, count_add)
|
||||
end
|
||||
else
|
||||
if vlayer_data.storage.power_items[v.name] then
|
||||
vlayer_data.storage.power_items[v.name].count = vlayer_data.storage.power_items[v.name].count + count_add
|
||||
else
|
||||
vlayer.insert_item(v.name, count_add)
|
||||
end
|
||||
end
|
||||
|
||||
inventory.remove{ name = v.name, count = count_deduct, quality = v.quality }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- 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, circuit, last_user)
|
||||
local interface = surface.create_entity{ name = "requester-chest", position = position, force = "neutral" }
|
||||
table.insert(vlayer_data.entity_interfaces.storage_output, interface)
|
||||
|
||||
if last_user then
|
||||
interface.last_user = last_user
|
||||
end
|
||||
|
||||
if circuit then
|
||||
for k, _ in pairs(circuit) do
|
||||
for _, v in pairs(circuit[k]) do
|
||||
interface.connect_neighbour{ wire = defines.wire_type[k], target_entity = v }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
interface.destructible = false
|
||||
interface.minable_flag = 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 inventory = assert(interface.get_inventory(defines.inventory.chest))
|
||||
local inventory_request_sections = assert(interface.get_logistic_sections()).sections
|
||||
|
||||
for i = 1, #inventory_request_sections do
|
||||
for _, v in pairs(inventory_request_sections[i].filters) do
|
||||
if v.value and config.allowed_items[v.value.name] then
|
||||
local current_amount = inventory.get_item_count(v.value.name)
|
||||
local request_amount = math.min(assert(v.min) - current_amount, vlayer_data.storage.items[v.value.name])
|
||||
|
||||
if request_amount > 0 and inventory.can_insert{ name = v.value.name, count = request_amount } then
|
||||
local removed_item_count = vlayer.remove_item(v.value.name, request_amount)
|
||||
|
||||
if removed_item_count > 0 then
|
||||
inventory.insert{ name = v.value.name, count = removed_item_count, quality = "normal" }
|
||||
end
|
||||
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)
|
||||
vlayer.unable_alloc_item_pwr_calc(item_name, -allocation_count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the statistics for the vlayer
|
||||
function vlayer.get_statistics()
|
||||
local vdp = vlayer_data.properties.production * mega
|
||||
local gdm = get_production_multiplier()
|
||||
local gsm = get_sustained_multiplier()
|
||||
local gald = get_actual_land_defecit()
|
||||
|
||||
return {
|
||||
total_surface_area = vlayer_data.properties.total_surface_area,
|
||||
used_surface_area = vlayer_data.properties.used_surface_area,
|
||||
remaining_surface_area = gald,
|
||||
surface_area = vlayer_data.properties.total_surface_area - gald,
|
||||
production_multiplier = gdm,
|
||||
energy_max = vdp,
|
||||
energy_production = vdp * gdm,
|
||||
energy_total_production = vlayer_data.properties.total_production * gsm * mega,
|
||||
energy_sustained = vdp * gsm,
|
||||
energy_capacity = vlayer_data.properties.capacity * mega,
|
||||
energy_storage = vlayer_data.storage.energy,
|
||||
}
|
||||
end
|
||||
|
||||
--- add or reduce vlayer power
|
||||
--- @param power number
|
||||
--- @return number
|
||||
function vlayer.energy_changed(power)
|
||||
local new_value = vlayer_data.storage.energy + power
|
||||
vlayer_data.storage.energy = new_value
|
||||
return new_value
|
||||
end
|
||||
|
||||
--- Circuit signals used for the statistics
|
||||
function vlayer.get_circuits()
|
||||
return {
|
||||
total_surface_area = "signal-A",
|
||||
used_surface_area = "signal-U",
|
||||
remaining_surface_area = "signal-R",
|
||||
production_multiplier = "signal-M",
|
||||
energy_production = "signal-P",
|
||||
energy_sustained = "signal-S",
|
||||
energy_capacity = "signal-C",
|
||||
energy_storage = "signal-E",
|
||||
}
|
||||
end
|
||||
|
||||
local vlayer_circuits_string = ""
|
||||
|
||||
for key, value in pairs(vlayer.get_circuits()) do
|
||||
vlayer_circuits_string = vlayer_circuits_string .. string.format("[virtual-signal=%s] = %s\n", value, key:gsub("_", " "))
|
||||
end
|
||||
|
||||
--- 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, circuit, 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
|
||||
|
||||
if circuit then
|
||||
for k, _ in pairs(circuit) do
|
||||
for _, v in pairs(circuit[k]) do
|
||||
interface.connect_neighbour{ wire = defines.wire_type[k], target_entity = v }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
interface.destructible = false
|
||||
interface.minable_flag = 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 control = interface.get_or_create_control_behavior() --[[@as LuaConstantCombinatorControlBehavior]]
|
||||
if control.sections_count == 0 then
|
||||
control.add_section()
|
||||
end
|
||||
local circuit_oc = assert(control.sections[1])
|
||||
local signal_index = 1
|
||||
local circuit = vlayer.get_circuits()
|
||||
|
||||
-- Set the virtual signals based on the vlayer stats
|
||||
for stat_name, signal_name in pairs(circuit) do
|
||||
if stat_name:find("energy") then
|
||||
circuit_oc.set_slot(signal_index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = math.floor(stats[stat_name] / mega) })
|
||||
elseif stat_name == "production_multiplier" then
|
||||
circuit_oc.set_slot(signal_index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = math.floor(stats[stat_name] * 10000) })
|
||||
else
|
||||
circuit_oc.set_slot(signal_index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = 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 prototypes.item[item_name] and count > 0 then
|
||||
local existing_index = circuit_oc.set_slot(signal_index, { value = { type = "item", name = item_name, quality = "normal" }, min = count })
|
||||
if existing_index then
|
||||
circuit_oc.clear_slot(existing_index)
|
||||
circuit_oc.set_slot(signal_index, { value = { type = "item", name = item_name, quality = "normal" }, min = count })
|
||||
end
|
||||
signal_index = signal_index + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Clear remaining signals to prevent outdated values being present (caused by count > 0 check)
|
||||
for clear_index = signal_index, #circuit do
|
||||
if not circuit_oc.get_slot(clear_index).value then
|
||||
break -- There are no more signals to clear
|
||||
end
|
||||
|
||||
circuit_oc.clear_slot(clear_index)
|
||||
end
|
||||
|
||||
interface.combinator_description = vlayer_circuits_string
|
||||
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_flag = 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 _, 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
|
||||
|
||||
-- burn the trash to produce power
|
||||
elseif vlayer_data.storage.power_items then
|
||||
for k, v in pairs(vlayer_data.storage.power_items) do
|
||||
local max_burning = (vlayer_data.properties.capacity * mega / 2) - vlayer_data.storage.energy
|
||||
|
||||
if v.count > 0 and max_burning > 0 then
|
||||
local to_burn = math.min(v.count, math.floor(max_burning / v.value))
|
||||
|
||||
vlayer_data.storage.energy = vlayer_data.storage.energy + (to_burn * v.value)
|
||||
vlayer_data.storage.power_items[k].count = vlayer_data.storage.power_items[k].count - to_burn
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Remove the entity interface using the given position
|
||||
-- @tparam LuaSurface surface The surface to search for an interface on
|
||||
-- @tparam MapPosition position The position of the item
|
||||
-- @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_interface(surface, position)
|
||||
local entities = surface.find_entities_filtered{
|
||||
name = { "storage-chest", "requester-chest", "constant-combinator", "electric-energy-interface" },
|
||||
force = "neutral",
|
||||
position = position,
|
||||
radius = 2,
|
||||
limit = 1,
|
||||
}
|
||||
|
||||
-- Get the details which will be returned
|
||||
if #entities == 0 then
|
||||
return nil, nil, nil
|
||||
end
|
||||
|
||||
local interface = entities[1]
|
||||
local name = interface.name
|
||||
local sur = interface.surface
|
||||
local pos = interface.position
|
||||
|
||||
-- Return the type of interface removed and do some clean up
|
||||
if name == "storage-chest" then
|
||||
local inventory = assert(interface.get_inventory(defines.inventory.chest))
|
||||
ExpUtil.transfer_inventory_to_surface{
|
||||
inventory = inventory,
|
||||
surface = interface.surface,
|
||||
name = "iron-chest",
|
||||
allow_creation = true,
|
||||
}
|
||||
table.remove_element(vlayer_data.entity_interfaces.storage_input, interface)
|
||||
interface.destroy()
|
||||
|
||||
return "storage-input", sur, pos
|
||||
elseif name == "requester-chest" then
|
||||
local inventory = assert(interface.get_inventory(defines.inventory.chest))
|
||||
ExpUtil.transfer_inventory_to_surface{
|
||||
inventory = inventory,
|
||||
surface = interface.surface,
|
||||
name = "iron-chest",
|
||||
allow_creation = true,
|
||||
}
|
||||
table.remove_element(vlayer_data.entity_interfaces.storage_output, interface)
|
||||
interface.destroy()
|
||||
|
||||
return "storage-output", sur, pos
|
||||
elseif name == "constant-combinator" then
|
||||
table.remove_element(vlayer_data.entity_interfaces.circuit, interface)
|
||||
interface.destroy()
|
||||
|
||||
return "circuit", sur, 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", sur, 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
|
||||
@@ -1,547 +0,0 @@
|
||||
--[[-- Gui Module - Virtual Layer
|
||||
- Adds a virtual layer to store power to save space.
|
||||
@gui Virtual Layer
|
||||
@alias vlayer_container
|
||||
]]
|
||||
|
||||
local Gui = require("modules/exp_gui")
|
||||
local Roles = require("modules/exp_roles")
|
||||
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
|
||||
local format_number = require("util").format_number --- @dep util
|
||||
local config = require("modules.exp_legacy.config.vlayer") --- @dep config.vlayer
|
||||
local vlayer = require("modules.exp_legacy.modules.control.vlayer")
|
||||
|
||||
local Selection = require("modules/exp_util/selection")
|
||||
local SelectArea = Selection.connect("ExpGui_VLayer")
|
||||
|
||||
--- Align an aabb to the grid by expanding it
|
||||
local function aabb_align_expand(aabb)
|
||||
return {
|
||||
left_top = { x = math.floor(aabb.left_top.x), y = math.floor(aabb.left_top.y) },
|
||||
right_bottom = { x = math.ceil(aabb.right_bottom.x), y = math.ceil(aabb.right_bottom.y) },
|
||||
}
|
||||
end
|
||||
|
||||
local vlayer_container --- @type ExpElement
|
||||
local vlayer_gui_control_type --- @type ExpElement
|
||||
local vlayer_gui_control_list --- @type ExpElement
|
||||
|
||||
local vlayer_control_type_list = {
|
||||
[1] = "energy",
|
||||
[2] = "circuit",
|
||||
[3] = "storage_input",
|
||||
[4] = "storage_output",
|
||||
}
|
||||
|
||||
local function pos_to_gps_string(pos, surface_name)
|
||||
return "[gps=" .. string.format("%.1f", pos.x) .. "," .. string.format("%.1f", pos.y) .. "," .. surface_name .. "]"
|
||||
end
|
||||
|
||||
local function format_energy(amount, unit)
|
||||
if amount < 1 then
|
||||
return "0 " .. unit
|
||||
end
|
||||
|
||||
local suffix = ""
|
||||
local suffix_list = {
|
||||
["P"] = 1000000000000000,
|
||||
["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 k
|
||||
local formatted = 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 ExpUtil = require("modules/exp_util")
|
||||
--- When an area is selected to add protection to the area
|
||||
SelectArea:on_selection(function(event)
|
||||
log(ExpUtil.format_any(event))
|
||||
local area = aabb_align_expand(event.area)
|
||||
local player = game.players[event.player_index]
|
||||
|
||||
if not player then
|
||||
return
|
||||
end
|
||||
|
||||
local container = Gui.get_left_element(vlayer_container, player)
|
||||
local disp = container.frame["vlayer_st_2"].disp.table
|
||||
local target = vlayer_control_type_list[disp[vlayer_gui_control_type.name].selected_index]
|
||||
local entities --- @type LuaEntity[]
|
||||
|
||||
if config.power_on_space and event.surface and event.surface.platform and target == "energy" then
|
||||
if (player.force --[[@as LuaForce]]).technologies[config.power_on_space_research.name].level >= config.power_on_space_research.level then
|
||||
entities = event.surface.find_entities_filtered{ area = area, name = "constant-combinator", force = player.force }
|
||||
else
|
||||
player.print{ "vlayer.power-on-space-research", config.power_on_space_research.name, config.power_on_space_research.level }
|
||||
return
|
||||
end
|
||||
else
|
||||
entities = event.surface.find_entities_filtered{ area = area, name = "steel-chest", force = player.force }
|
||||
end
|
||||
|
||||
if #entities == 0 then
|
||||
player.print{ "vlayer.steel-chest-detect" }
|
||||
return
|
||||
elseif #entities > 1 then
|
||||
player.print{ "vlayer.result-unable", { "vlayer.control-type-" .. target:gsub("_", "-") }, { "vlayer.result-multiple" } }
|
||||
return
|
||||
end
|
||||
|
||||
if not entities[1] then
|
||||
return
|
||||
end
|
||||
|
||||
local e = entities[1]
|
||||
local e_pos = { x = string.format("%.1f", e.position.x), y = string.format("%.1f", e.position.y) }
|
||||
local e_circ = nil -- e.get_wire_connectors{ or_create = false }
|
||||
|
||||
if e.name and e.name == "steel-chest" and (not assert(e.get_inventory(defines.inventory.chest)).is_empty()) then
|
||||
player.print{ "vlayer.steel-chest-empty" }
|
||||
return
|
||||
end
|
||||
|
||||
if (vlayer.get_interface_counts()[target] >= config.interface_limit[target]) then
|
||||
player.print{ "vlayer.result-unable", { "vlayer.control-type-" .. target:gsub("_", "-") }, { "vlayer.result-limit" } }
|
||||
return
|
||||
end
|
||||
|
||||
e.destroy()
|
||||
|
||||
if target == "energy" then
|
||||
if not vlayer.create_energy_interface(event.surface, e_pos, player) then
|
||||
player.print{ "vlayer.result-unable", { "vlayer.control-type-energy" }, { "vlayer.result-space" } }
|
||||
return
|
||||
end
|
||||
elseif target == "circuit" then
|
||||
vlayer.create_circuit_interface(event.surface, e_pos, e_circ, player)
|
||||
elseif target == "storage_input" then
|
||||
vlayer.create_input_interface(event.surface, e_pos, e_circ, player)
|
||||
elseif target == "storage_output" then
|
||||
vlayer.create_output_interface(event.surface, e_pos, e_circ, player)
|
||||
end
|
||||
|
||||
game.print{ "vlayer.interface-result", player.name, pos_to_gps_string(e_pos, event.surface.name), { "vlayer.result-build" }, { "vlayer.control-type-" .. target:gsub("_", "-") } }
|
||||
end)
|
||||
|
||||
--- Display label for the number of solar panels
|
||||
-- @element vlayer_gui_display_item_solar_name
|
||||
local vlayer_gui_display_item_solar_name = Gui.define("vlayer_gui_display_item_solar_name")
|
||||
:draw{
|
||||
type = "label",
|
||||
name = Gui.from_name,
|
||||
caption = { "vlayer.display-item-solar" },
|
||||
style = "heading_2_label",
|
||||
}:style{
|
||||
width = 200,
|
||||
}
|
||||
|
||||
local vlayer_gui_display_item_solar_count = Gui.define("vlayer_gui_display_item_solar_count")
|
||||
:draw{
|
||||
type = "progressbar",
|
||||
name = Gui.from_name,
|
||||
caption = "",
|
||||
value = 0,
|
||||
style = "electric_satisfaction_statistics_progressbar",
|
||||
}:style{
|
||||
width = 200,
|
||||
font = "heading-2",
|
||||
}
|
||||
|
||||
--- Display label for the number of accumulators
|
||||
-- @element vlayer_gui_display_item_accumulator_name
|
||||
local vlayer_gui_display_item_accumulator_name = Gui.define("vlayer_gui_display_item_accumulator_name")
|
||||
:draw{
|
||||
type = "label",
|
||||
name = Gui.from_name,
|
||||
caption = { "vlayer.display-item-accumulator" },
|
||||
style = "heading_2_label",
|
||||
}:style{
|
||||
width = 200,
|
||||
}
|
||||
|
||||
local vlayer_gui_display_item_accumulator_count = Gui.define("vlayer_gui_display_item_accumulator_count")
|
||||
:draw{
|
||||
type = "progressbar",
|
||||
name = Gui.from_name,
|
||||
caption = "",
|
||||
value = 0,
|
||||
style = "electric_satisfaction_statistics_progressbar",
|
||||
}:style{
|
||||
width = 200,
|
||||
font = "heading-2",
|
||||
}
|
||||
|
||||
--- Display label for the surface area
|
||||
-- @element vlayer_gui_display_signal_surface_area_name
|
||||
local vlayer_gui_display_signal_surface_area_name = Gui.define("vlayer_gui_display_signal_surface_area_name")
|
||||
:draw{
|
||||
type = "label",
|
||||
name = Gui.from_name,
|
||||
caption = { "vlayer.display-remaining-surface-area" },
|
||||
tooltip = { "vlayer.display-remaining-surface-area-tooltip" },
|
||||
style = "heading_2_label",
|
||||
}:style{
|
||||
width = 200,
|
||||
}
|
||||
|
||||
local vlayer_gui_display_signal_surface_area_count = Gui.define("vlayer_gui_display_signal_surface_area_count")
|
||||
:draw{
|
||||
type = "progressbar",
|
||||
name = Gui.from_name,
|
||||
caption = "",
|
||||
value = 0,
|
||||
style = "electric_satisfaction_statistics_progressbar",
|
||||
}:style{
|
||||
width = 200,
|
||||
font = "heading-2",
|
||||
}
|
||||
|
||||
--- Display label for the sustained energy production
|
||||
-- @element vlayer_gui_display_signal_sustained_name
|
||||
local vlayer_gui_display_signal_sustained_name = Gui.define("vlayer_gui_display_signal_sustained_name")
|
||||
:draw{
|
||||
type = "label",
|
||||
name = Gui.from_name,
|
||||
caption = { "vlayer.display-sustained-production" },
|
||||
tooltip = { "vlayer.display-sustained-production-tooltip" },
|
||||
style = "heading_2_label",
|
||||
}:style{
|
||||
width = 200,
|
||||
}
|
||||
|
||||
local vlayer_gui_display_signal_sustained_count = Gui.define("vlayer_gui_display_signal_sustained_count")
|
||||
:draw{
|
||||
type = "progressbar",
|
||||
name = Gui.from_name,
|
||||
caption = "",
|
||||
value = 0,
|
||||
style = "electric_satisfaction_statistics_progressbar",
|
||||
}:style{
|
||||
width = 200,
|
||||
font = "heading-2",
|
||||
}
|
||||
|
||||
--- Display label for the current energy production
|
||||
-- @element vlayer_gui_display_signal_production_name
|
||||
local vlayer_gui_display_signal_production_name = Gui.define("vlayer_gui_display_signal_production_name")
|
||||
:draw{
|
||||
type = "label",
|
||||
name = Gui.from_name,
|
||||
caption = { "vlayer.display-current-production" },
|
||||
tooltip = { "vlayer.display-current-production-tooltip" },
|
||||
style = "heading_2_label",
|
||||
}:style{
|
||||
width = 200,
|
||||
}
|
||||
|
||||
local vlayer_gui_display_signal_production_count = Gui.define("vlayer_gui_display_signal_production_count")
|
||||
:draw{
|
||||
type = "progressbar",
|
||||
name = Gui.from_name,
|
||||
caption = "",
|
||||
value = 0,
|
||||
style = "electric_satisfaction_statistics_progressbar",
|
||||
}:style{
|
||||
width = 200,
|
||||
font = "heading-2",
|
||||
}
|
||||
|
||||
--- Display label for the sustained energy capacity
|
||||
-- @element vlayer_gui_display_signal_capacity_name
|
||||
local vlayer_gui_display_signal_capacity_name = Gui.define("vlayer_gui_display_signal_capacity_name")
|
||||
:draw{
|
||||
type = "label",
|
||||
name = Gui.from_name,
|
||||
caption = { "vlayer.display-current-capacity" },
|
||||
tooltip = { "vlayer.display-current-capacity-tooltip" },
|
||||
style = "heading_2_label",
|
||||
}:style{
|
||||
width = 200,
|
||||
}
|
||||
|
||||
local vlayer_gui_display_signal_capacity_count = Gui.define("vlayer_gui_display_signal_capacity_count")
|
||||
:draw{
|
||||
type = "progressbar",
|
||||
name = Gui.from_name,
|
||||
caption = "",
|
||||
value = 0,
|
||||
style = "electric_satisfaction_statistics_progressbar",
|
||||
}:style{
|
||||
width = 200,
|
||||
font = "heading-2",
|
||||
}
|
||||
|
||||
--- A vertical flow containing all the displays labels and their counts
|
||||
-- @element vlayer_display_set
|
||||
local vlayer_display_set = Gui.define("vlayer_display_set")
|
||||
:draw(function(_, parent, name)
|
||||
local vlayer_set = parent.add{ type = "flow", direction = "vertical", name = name }
|
||||
local disp = Gui.elements.scroll_table(vlayer_set, 400, 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_surface_area_name(disp)
|
||||
vlayer_gui_display_signal_surface_area_count(disp)
|
||||
vlayer_gui_display_signal_sustained_name(disp)
|
||||
vlayer_gui_display_signal_sustained_count(disp)
|
||||
vlayer_gui_display_signal_production_name(disp)
|
||||
vlayer_gui_display_signal_production_count(disp)
|
||||
vlayer_gui_display_signal_capacity_name(disp)
|
||||
vlayer_gui_display_signal_capacity_count(disp)
|
||||
|
||||
return vlayer_set
|
||||
end)
|
||||
|
||||
local function vlayer_gui_list_refresh(player)
|
||||
local container = Gui.get_left_element(vlayer_container, player)
|
||||
local disp = container.frame["vlayer_st_2"].disp.table
|
||||
local target = disp[vlayer_gui_control_type.name].selected_index
|
||||
local full_list = {}
|
||||
|
||||
if target then
|
||||
local interface = vlayer.get_interfaces()[vlayer_control_type_list[target]]
|
||||
|
||||
for i = 1, vlayer.get_interface_counts()[vlayer_control_type_list[target]], 1 do
|
||||
local entity = assert(interface[i])
|
||||
local entity_position = entity.position
|
||||
table.insert(full_list, i .. " X " .. entity_position.x .. " Y " .. entity_position.y)
|
||||
end
|
||||
|
||||
disp[vlayer_gui_control_list.name].items = full_list
|
||||
end
|
||||
end
|
||||
|
||||
--- A drop down list filter by this type
|
||||
-- @element vlayer_gui_control_type
|
||||
vlayer_gui_control_type = Gui.define("vlayer_gui_control_type")
|
||||
:draw{
|
||||
type = "drop-down",
|
||||
name = Gui.from_name,
|
||||
items = { { "vlayer.control-type-energy" }, { "vlayer.control-type-circuit" }, { "vlayer.control-type-storage-input" }, { "vlayer.control-type-storage-output" } },
|
||||
selected_index = 1,
|
||||
}:style{
|
||||
width = 200,
|
||||
}:on_selection_state_changed(function(def, player, element)
|
||||
vlayer_gui_list_refresh(player)
|
||||
end)
|
||||
|
||||
--- A drop down list to see the exact item to remove
|
||||
-- @element vlayer_gui_control_list
|
||||
vlayer_gui_control_list = Gui.define("vlayer_gui_control_list")
|
||||
:draw{
|
||||
type = "drop-down",
|
||||
name = Gui.from_name,
|
||||
}:style{
|
||||
width = 200,
|
||||
}
|
||||
|
||||
--- A button to refresh the remove list
|
||||
-- @element vlayer_gui_control_refresh
|
||||
local vlayer_gui_control_refresh = Gui.define("vlayer_gui_control_refresh")
|
||||
:draw{
|
||||
type = "button",
|
||||
name = Gui.from_name,
|
||||
caption = { "vlayer.control-refresh" },
|
||||
}:style{
|
||||
width = 200,
|
||||
}:on_click(function(def, player, element)
|
||||
vlayer_gui_list_refresh(player)
|
||||
end)
|
||||
|
||||
--- A button to check if the item is the one wanted to remove
|
||||
-- @element vlayer_gui_control_see
|
||||
local vlayer_gui_control_see = Gui.define("vlayer_gui_control_see")
|
||||
:draw{
|
||||
type = "button",
|
||||
name = Gui.from_name,
|
||||
caption = { "vlayer.control-see" },
|
||||
}:style{
|
||||
width = 200,
|
||||
}:on_click(function(def, player, element, event)
|
||||
local target = assert(element.parent)[vlayer_gui_control_type.name].selected_index
|
||||
local n = assert(element.parent)[vlayer_gui_control_list.name].selected_index
|
||||
|
||||
if target and vlayer_control_type_list[target] and n > 0 then
|
||||
local i = vlayer.get_interfaces()
|
||||
local entity = i[vlayer_control_type_list[target]][n]
|
||||
if entity and entity.valid then
|
||||
player.set_controller{ type = defines.controllers.remote, position = entity.position, surface = entity.surface }
|
||||
player.print{ "vlayer.result-interface-location", { "vlayer.control-type-" .. vlayer_control_type_list[target]:gsub("_", "-") }, pos_to_gps_string(entity.position, entity.surface.name) }
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
--- A button used to build the vlayer interface
|
||||
-- @element vlayer_gui_control_build
|
||||
local vlayer_gui_control_build = Gui.define("vlayer_gui_control_build")
|
||||
:draw{
|
||||
type = "button",
|
||||
name = Gui.from_name,
|
||||
caption = { "vlayer.control-build" },
|
||||
}:style{
|
||||
width = 200,
|
||||
}:on_click(function(def, player, element)
|
||||
if SelectArea:stop(player) then
|
||||
player.print{ "exp_util.selection_exit", { "vlayer.main-tooltip" } }
|
||||
else
|
||||
SelectArea:start(player)
|
||||
player.print{ "exp_util.selection_enter", { "vlayer.main-tooltip" } }
|
||||
end
|
||||
|
||||
vlayer_gui_list_refresh(player)
|
||||
end)
|
||||
|
||||
--- A button used to remove the vlayer interface
|
||||
-- @element vlayer_gui_control_remove
|
||||
local vlayer_gui_control_remove = Gui.define("vlayer_gui_control_remove")
|
||||
:draw{
|
||||
type = "button",
|
||||
name = Gui.from_name,
|
||||
caption = { "vlayer.control-remove" },
|
||||
}:style{
|
||||
width = 200,
|
||||
}:on_click(function(def, player, element)
|
||||
local target = assert(element.parent)[vlayer_gui_control_type.name].selected_index
|
||||
local n = assert(element.parent)[vlayer_gui_control_list.name].selected_index
|
||||
|
||||
if target and vlayer_control_type_list[target] and n > 0 then
|
||||
local i = vlayer.get_interfaces()
|
||||
|
||||
if i and i[vlayer_control_type_list[target]] then
|
||||
local entity = assert(i[vlayer_control_type_list[target]][n])
|
||||
local interface_type, interface_surface, interface_position = vlayer.remove_interface(entity.surface, entity.position)
|
||||
|
||||
if interface_type then
|
||||
game.print{ "vlayer.interface-result", player.name, pos_to_gps_string(interface_position, interface_surface.name), { "vlayer.result-remove" }, { "vlayer.control-type-" .. interface_type } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vlayer_gui_list_refresh(player)
|
||||
end)
|
||||
|
||||
--- A vertical flow containing all the control buttons
|
||||
-- @element vlayer_control_set
|
||||
local vlayer_control_set = Gui.define("vlayer_control_set")
|
||||
:draw(function(_, parent, name)
|
||||
local player = Gui.get_player(parent)
|
||||
local vlayer_set = parent.add{ type = "flow", direction = "vertical", name = name }
|
||||
local disp = Gui.elements.scroll_table(vlayer_set, 400, 2, "disp")
|
||||
|
||||
vlayer_gui_control_type(disp)
|
||||
vlayer_gui_control_list(disp)
|
||||
vlayer_gui_control_refresh(disp)
|
||||
vlayer_gui_control_see(disp)
|
||||
local b = vlayer_gui_control_build(disp)
|
||||
local r = vlayer_gui_control_remove(disp)
|
||||
local v = Roles.player_has_permission(player, "exp_scenario.gui.vlayer_edit")
|
||||
b.visible = v
|
||||
r.visible = v
|
||||
|
||||
return vlayer_set
|
||||
end)
|
||||
|
||||
--- The main container for the vlayer gui
|
||||
-- @element vlayer_container
|
||||
vlayer_container = Gui.define("vlayer_container")
|
||||
:draw(function(definition, parent)
|
||||
local container = Gui.elements.container(parent, 400)
|
||||
|
||||
vlayer_display_set(container, "vlayer_st_1")
|
||||
vlayer_control_set(container, "vlayer_st_2")
|
||||
|
||||
return container.parent
|
||||
end)
|
||||
|
||||
--- Add the element to the left flow with a toolbar button
|
||||
Gui.add_left_element(vlayer_container, false)
|
||||
Gui.toolbar.create_button{
|
||||
name = "vlayer_toggle",
|
||||
left_element = vlayer_container,
|
||||
sprite = "entity/solar-panel",
|
||||
tooltip = { "vlayer.main-tooltip" },
|
||||
visible = function(player, element)
|
||||
return Roles.player_has_permission(player, "exp_scenario.gui.vlayer")
|
||||
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_has_permission(player, "exp_scenario.gui.vlayer_edit")
|
||||
local container = Gui.get_left_element(vlayer_container, player)
|
||||
local disp = container.frame["vlayer_st_2"].disp.table
|
||||
disp[vlayer_gui_control_build.name].visible = visible
|
||||
disp[vlayer_gui_control_remove.name].visible = visible
|
||||
end
|
||||
|
||||
Event.add(Roles.events.on_player_roles_changed, role_update_event)
|
||||
|
||||
Event.on_nth_tick(config.update_tick_gui, function(_)
|
||||
local stats = vlayer.get_statistics()
|
||||
local items = vlayer.get_items()
|
||||
local items_alloc = vlayer.get_allocated_items()
|
||||
|
||||
local vlayer_display = {
|
||||
[vlayer_gui_display_item_solar_count.name] = {
|
||||
val = (items_alloc["solar-panel"] / math.max(items["solar-panel"], 1)),
|
||||
cap = format_number(items_alloc["solar-panel"], false) .. " / " .. format_number(items["solar-panel"], false),
|
||||
},
|
||||
[vlayer_gui_display_item_accumulator_count.name] = {
|
||||
val = (items_alloc["accumulator"] / math.max(items["accumulator"], 1)),
|
||||
cap = format_number(items_alloc["accumulator"], false) .. " / " .. format_number(items["accumulator"], false),
|
||||
},
|
||||
[vlayer_gui_display_signal_surface_area_count.name] = {
|
||||
val = (stats.total_surface_area / math.max(stats.surface_area, 1)),
|
||||
cap = format_number(stats.remaining_surface_area)
|
||||
},
|
||||
[vlayer_gui_display_signal_sustained_count.name] = {
|
||||
val = (stats.energy_sustained / math.max(stats.energy_total_production, 1)),
|
||||
cap = format_energy(stats.energy_sustained, "W") .. " / " .. format_energy(stats.energy_total_production, "W")
|
||||
},
|
||||
[vlayer_gui_display_signal_production_count.name] = {
|
||||
val = (stats.energy_production / math.max(stats.energy_max, 1)),
|
||||
cap = format_energy(stats.energy_production, "W") .. " / " .. format_energy(stats.energy_max, "W"),
|
||||
},
|
||||
[vlayer_gui_display_signal_capacity_count.name] = {
|
||||
val = (stats.energy_storage / math.max(stats.energy_capacity, 1)),
|
||||
cap = format_energy(stats.energy_storage, "J") .. " / " .. format_energy(stats.energy_capacity, "J"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, player in pairs(game.connected_players) do
|
||||
local container = Gui.get_left_element(vlayer_container, player)
|
||||
local disp = container.frame["vlayer_st_1"].disp.table
|
||||
|
||||
for k, v in pairs(vlayer_display) do
|
||||
disp[k].caption = v.cap
|
||||
|
||||
if v.val then
|
||||
disp[k].value = v.val
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -3,7 +3,7 @@ Adds a virtual layer to store power to save space.
|
||||
]]
|
||||
|
||||
local Commands = require("modules/exp_commands")
|
||||
local vlayer = require("modules.exp_legacy.modules.control.vlayer")
|
||||
local Vlayer = require("modules/exp_scenario/control/vlayer")
|
||||
|
||||
--- @class ExpCommand_vlayer.commands
|
||||
local commands = {}
|
||||
@@ -14,7 +14,7 @@ commands.vlayer = Commands.new("vlayer-info", { "exp-commands_vlayer.description
|
||||
:register(function(player)
|
||||
local index = 3
|
||||
local response = { "", "exp-commands_vlayer.title" } --- @type LocalisedString
|
||||
for title, value in pairs(vlayer.get_circuits()) do
|
||||
for title, value in pairs(Vlayer.get_circuits()) do
|
||||
response[index] = { "exp-commands_vlayer.result", title, value }
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
@@ -67,6 +67,7 @@ add(require("modules/exp_scenario/control/roles"))
|
||||
add(require("modules/exp_scenario/control/spawn_area"))
|
||||
add(require("modules/exp_scenario/control/spectate"))
|
||||
add(require("modules/exp_scenario/control/station_auto_name"))
|
||||
add(require("modules/exp_scenario/control/vlayer"))
|
||||
|
||||
--- Guis
|
||||
add(require("modules/exp_scenario/gui/autofill"))
|
||||
@@ -83,6 +84,7 @@ add(require("modules/exp_scenario/gui/rocket_info"))
|
||||
add(require("modules/exp_scenario/gui/science_production"))
|
||||
add(require("modules/exp_scenario/gui/surveillance"))
|
||||
add(require("modules/exp_scenario/gui/task_list"))
|
||||
add(require("modules/exp_scenario/gui/vlayer"))
|
||||
add(require("modules/exp_scenario/gui/warp_list"))
|
||||
add(require("modules/exp_scenario/gui/debug/shim"))
|
||||
add(require("modules/exp_scenario/gui/debug/event_view"))
|
||||
@@ -0,0 +1,673 @@
|
||||
--[[-- Control - Virtual Layer
|
||||
Stores solar panels, accumulators and landfill in a virtual layer which produces power through interface entities
|
||||
]]
|
||||
|
||||
local ExpUtil = require("modules/exp_util")
|
||||
local Storage = require("modules/exp_util/storage")
|
||||
local config = require("modules.exp_legacy.config.vlayer")
|
||||
|
||||
local floor = math.floor
|
||||
local min = math.min
|
||||
local max = math.max
|
||||
local mega = 1000000
|
||||
|
||||
--- @alias ExpScenario_Vlayer.InterfaceType "energy" | "circuit" | "storage_input" | "storage_output"
|
||||
|
||||
--- @class ExpScenario_Vlayer.Interfaces
|
||||
--- @field energy LuaEntity[]
|
||||
--- @field circuit LuaEntity[]
|
||||
--- @field storage_input LuaEntity[]
|
||||
--- @field storage_output LuaEntity[]
|
||||
|
||||
--- @class ExpScenario_Vlayer.Properties
|
||||
--- @field total_surface_area number Tiles provided by landfill
|
||||
--- @field used_surface_area number Tiles taken by allocated items
|
||||
--- @field total_production number MW of every solar panel stored, allocated or not
|
||||
--- @field production number MW of the allocated solar panels
|
||||
--- @field discharge number MW the allocated accumulators can discharge
|
||||
--- @field capacity number MJ the allocated accumulators can hold
|
||||
|
||||
--- @class ExpScenario_Vlayer.PowerItem
|
||||
--- @field value number J released by burning one
|
||||
--- @field count number
|
||||
|
||||
--- @class ExpScenario_Vlayer.Storage
|
||||
--- @field items table<string, number> Every stored item, allocated or not
|
||||
--- @field unallocated table<string, number> Items waiting for surface area
|
||||
--- @field power_items table<string, ExpScenario_Vlayer.PowerItem> Fuel burned when the stored energy is low
|
||||
--- @field energy number J
|
||||
|
||||
--- @class ExpScenario_Vlayer.Statistics
|
||||
--- @field total_surface_area number
|
||||
--- @field used_surface_area number
|
||||
--- @field remaining_surface_area number Surface area left after the unallocated items are counted
|
||||
--- @field surface_area number
|
||||
--- @field production_multiplier number
|
||||
--- @field energy_max number W when the sun is up
|
||||
--- @field energy_production number W right now
|
||||
--- @field energy_total_production number W sustained if every stored solar panel was allocated
|
||||
--- @field energy_sustained number W averaged over a day
|
||||
--- @field energy_capacity number J
|
||||
--- @field energy_storage number J
|
||||
|
||||
--- @class ExpScenario_Vlayer.Data
|
||||
--- @field interfaces ExpScenario_Vlayer.Interfaces
|
||||
--- @field properties ExpScenario_Vlayer.Properties
|
||||
--- @field storage ExpScenario_Vlayer.Storage
|
||||
--- @field surface LuaSurface | table The surface the day cycle is taken from, or a copy of the config settings
|
||||
|
||||
--- @class ExpScenario_Vlayer
|
||||
local Vlayer = {
|
||||
--- @package
|
||||
events = {},
|
||||
--- @package
|
||||
on_nth_tick = {},
|
||||
}
|
||||
|
||||
--- The properties of every allowed item, modded items are derived from their base game equivalent
|
||||
local allowed_items = {} --- @type table<string, Vlayer.ItemProperties>
|
||||
for name, properties in pairs(config.allowed_items) do
|
||||
allowed_items[name] = properties
|
||||
end
|
||||
for name, properties in pairs(config.modded_items) do
|
||||
local base = config.allowed_items[properties.base_game_equivalent]
|
||||
local multiplier = properties.multiplier
|
||||
allowed_items[name] = {
|
||||
starting_value = properties.starting_value or 0,
|
||||
required_area = base.required_area or 0,
|
||||
surface_area = (base.surface_area or 0) * multiplier,
|
||||
production = (base.production or 0) * multiplier,
|
||||
capacity = (base.capacity or 0) * multiplier,
|
||||
}
|
||||
end
|
||||
|
||||
--- @type ExpScenario_Vlayer.Data
|
||||
local vlayer_data = {
|
||||
interfaces = {
|
||||
energy = {},
|
||||
circuit = {},
|
||||
storage_input = {},
|
||||
storage_output = {},
|
||||
},
|
||||
properties = {
|
||||
total_surface_area = 0,
|
||||
used_surface_area = 0,
|
||||
total_production = 0,
|
||||
production = 0,
|
||||
discharge = 0,
|
||||
capacity = 0,
|
||||
},
|
||||
storage = {
|
||||
items = {},
|
||||
unallocated = {},
|
||||
power_items = {},
|
||||
energy = 0,
|
||||
},
|
||||
surface = table.deep_copy(config.surface),
|
||||
}
|
||||
|
||||
Storage.register(vlayer_data, function(tbl)
|
||||
vlayer_data = tbl
|
||||
end)
|
||||
|
||||
--- Interface entities are neutral so players can not mine them, these are the prototypes used for each type
|
||||
local interface_names = {
|
||||
energy = "electric-energy-interface",
|
||||
circuit = "constant-combinator",
|
||||
storage_input = "storage-chest",
|
||||
storage_output = "requester-chest",
|
||||
} --- @type table<ExpScenario_Vlayer.InterfaceType, string>
|
||||
|
||||
local interface_types = {} --- @type table<string, ExpScenario_Vlayer.InterfaceType>
|
||||
for interface_type, name in pairs(interface_names) do
|
||||
interface_types[name] = interface_type
|
||||
end
|
||||
|
||||
--- Statistics exposed on circuit interfaces and the signal each is sent as
|
||||
local circuit_signals = {
|
||||
total_surface_area = "signal-A",
|
||||
used_surface_area = "signal-U",
|
||||
remaining_surface_area = "signal-R",
|
||||
production_multiplier = "signal-M",
|
||||
energy_production = "signal-P",
|
||||
energy_sustained = "signal-S",
|
||||
energy_capacity = "signal-C",
|
||||
energy_storage = "signal-E",
|
||||
} --- @type table<string, string>
|
||||
|
||||
local circuit_description = {}
|
||||
for stat_name, signal_name in pairs(circuit_signals) do
|
||||
circuit_description[#circuit_description + 1] = string.format("[virtual-signal=%s] = %s", signal_name, stat_name:gsub("_", " "))
|
||||
end
|
||||
circuit_description = table.concat(circuit_description, "\n")
|
||||
|
||||
--- Get every stored item keyed by name, allocated or not
|
||||
--- @return table<string, number>
|
||||
function Vlayer.get_items()
|
||||
return vlayer_data.storage.items
|
||||
end
|
||||
|
||||
--- Get the items waiting for surface area keyed by name
|
||||
--- @return table<string, number>
|
||||
function Vlayer.get_unallocated_items()
|
||||
return vlayer_data.storage.unallocated
|
||||
end
|
||||
|
||||
--- Get the items which contribute to the properties keyed by name
|
||||
--- @return table<string, number>
|
||||
function Vlayer.get_allocated_items()
|
||||
local allocated = {}
|
||||
local unallocated = vlayer_data.storage.unallocated
|
||||
for name, count in pairs(vlayer_data.storage.items) do
|
||||
allocated[name] = count - (unallocated[name] or 0)
|
||||
end
|
||||
return allocated
|
||||
end
|
||||
|
||||
--- Get the interface entities of each type, do not modify
|
||||
--- @return ExpScenario_Vlayer.Interfaces
|
||||
function Vlayer.get_interfaces()
|
||||
return vlayer_data.interfaces
|
||||
end
|
||||
|
||||
--- Get the signal each statistic is sent as on circuit interfaces
|
||||
--- @return table<string, string>
|
||||
function Vlayer.get_circuits()
|
||||
return circuit_signals
|
||||
end
|
||||
|
||||
--- Surface area left once the unallocated items have taken what they need
|
||||
--- @return number
|
||||
local function get_remaining_surface_area()
|
||||
local properties = vlayer_data.properties
|
||||
local remaining = properties.total_surface_area - properties.used_surface_area
|
||||
for name, count in pairs(vlayer_data.storage.unallocated) do
|
||||
remaining = remaining - allowed_items[name].required_area * count
|
||||
end
|
||||
return remaining
|
||||
end
|
||||
|
||||
--- Fraction of solar production available right now, from the darkness of a real surface or the config day cycle
|
||||
--- @return number
|
||||
local function get_production_multiplier()
|
||||
local surface = vlayer_data.surface
|
||||
local multiplier = surface.solar_power_multiplier
|
||||
if surface.always_day then
|
||||
return multiplier
|
||||
end
|
||||
|
||||
if surface.darkness then
|
||||
local brightness = 1 - surface.darkness
|
||||
if brightness < surface.min_brightness then
|
||||
return 0
|
||||
end
|
||||
return multiplier * (brightness - surface.min_brightness) / (1 - surface.min_brightness)
|
||||
end
|
||||
|
||||
local daytime = (game.tick % surface.ticks_per_day) / surface.ticks_per_day
|
||||
surface.daytime = daytime
|
||||
if daytime <= surface.dusk then
|
||||
return multiplier
|
||||
elseif daytime <= surface.evening then
|
||||
return multiplier * (1 - (daytime - surface.dusk) / (surface.evening - surface.dusk))
|
||||
elseif daytime <= surface.morning then
|
||||
return 0
|
||||
elseif daytime <= surface.dawn then
|
||||
return multiplier * (daytime - surface.morning) / (surface.dawn - surface.morning)
|
||||
end
|
||||
return multiplier
|
||||
end
|
||||
|
||||
--- Fraction of solar production averaged over a whole day, sunrise and sunset count for half
|
||||
--- @return number
|
||||
local function get_sustained_multiplier()
|
||||
local surface = vlayer_data.surface
|
||||
local multiplier = surface.solar_power_multiplier
|
||||
if surface.always_day then
|
||||
return multiplier
|
||||
end
|
||||
|
||||
local day_duration = 1 - surface.dawn + surface.dusk
|
||||
local sunset_duration = surface.evening - surface.dusk
|
||||
local sunrise_duration = surface.dawn - surface.morning
|
||||
return multiplier * (day_duration + 0.5 * (sunset_duration + sunrise_duration))
|
||||
end
|
||||
|
||||
--- Apply the properties of an item to the layer, a negative count deallocates
|
||||
--- @param name string
|
||||
--- @param count number
|
||||
local function allocate_item(name, count)
|
||||
local item = allowed_items[name]
|
||||
local properties = vlayer_data.properties
|
||||
if item.production then
|
||||
properties.production = properties.production + item.production * count
|
||||
properties.total_production = properties.total_production + item.production * count
|
||||
end
|
||||
if item.capacity then
|
||||
properties.capacity = properties.capacity + item.capacity * count
|
||||
end
|
||||
if item.discharge then
|
||||
properties.discharge = properties.discharge + item.discharge * count
|
||||
end
|
||||
if item.surface_area then
|
||||
properties.total_surface_area = properties.total_surface_area + item.surface_area * count
|
||||
end
|
||||
if item.required_area > 0 then
|
||||
properties.used_surface_area = properties.used_surface_area + item.required_area * count
|
||||
end
|
||||
end
|
||||
|
||||
--- Count the production of items which are stored but not allocated, so the total shows what more surface area would give
|
||||
--- @param name string
|
||||
--- @param count number
|
||||
local function count_unallocated_production(name, count)
|
||||
local item = allowed_items[name]
|
||||
if item.production then
|
||||
vlayer_data.properties.total_production = vlayer_data.properties.total_production + item.production * count
|
||||
end
|
||||
end
|
||||
|
||||
for name, item in pairs(allowed_items) do
|
||||
vlayer_data.storage.items[name] = item.starting_value
|
||||
if item.required_area > 0 then
|
||||
vlayer_data.storage.unallocated[name] = 0
|
||||
end
|
||||
if item.power then
|
||||
vlayer_data.storage.power_items[name] = { value = assert(item.fuel_value) * mega, count = 0 }
|
||||
end
|
||||
allocate_item(name, item.starting_value)
|
||||
end
|
||||
|
||||
--- Store an item, it is allocated straight away when there is surface area for it
|
||||
--- @param name string
|
||||
--- @param count number
|
||||
function Vlayer.insert_item(name, count)
|
||||
local item = assert(allowed_items[name], "Item not allowed in the vlayer: " .. name)
|
||||
local storage = vlayer_data.storage
|
||||
storage.items[name] = storage.items[name] + count
|
||||
|
||||
if config.unlimited_surface_area or item.required_area == 0 then
|
||||
allocate_item(name, count)
|
||||
return
|
||||
end
|
||||
|
||||
local properties = vlayer_data.properties
|
||||
local surplus_area = properties.total_surface_area - properties.used_surface_area
|
||||
local allocated = min(count, floor(surplus_area / item.required_area))
|
||||
if allocated > 0 then
|
||||
allocate_item(name, allocated)
|
||||
end
|
||||
|
||||
local unallocated = count - allocated
|
||||
storage.unallocated[name] = storage.unallocated[name] + unallocated
|
||||
count_unallocated_production(name, unallocated)
|
||||
end
|
||||
|
||||
--- Take an item out, unallocated items go first and landfill can only go while nothing is using its area
|
||||
--- @param name string
|
||||
--- @param count number
|
||||
--- @return number # The count actually removed
|
||||
function Vlayer.remove_item(name, count)
|
||||
local item = assert(allowed_items[name], "Item not allowed in the vlayer: " .. name)
|
||||
local storage = vlayer_data.storage
|
||||
local properties = vlayer_data.properties
|
||||
local removed = 0
|
||||
|
||||
if not config.unlimited_surface_area and item.required_area > 0 then
|
||||
removed = min(count, storage.unallocated[name])
|
||||
storage.items[name] = storage.items[name] - removed
|
||||
storage.unallocated[name] = storage.unallocated[name] - removed
|
||||
count_unallocated_production(name, -removed)
|
||||
count = count - removed
|
||||
if count == 0 then
|
||||
return removed
|
||||
end
|
||||
end
|
||||
|
||||
local deallocate = min(count, storage.items[name])
|
||||
if item.surface_area and item.surface_area > 0 then
|
||||
local surplus_area = properties.total_surface_area - properties.used_surface_area
|
||||
deallocate = min(deallocate, floor(surplus_area / item.surface_area))
|
||||
if deallocate <= 0 then
|
||||
return removed
|
||||
end
|
||||
end
|
||||
|
||||
storage.items[name] = storage.items[name] - deallocate
|
||||
allocate_item(name, -deallocate)
|
||||
return removed + deallocate
|
||||
end
|
||||
|
||||
--- Add or take energy, used by other modules which spend the stored energy
|
||||
--- @param energy number J, negative to take
|
||||
--- @return number # J stored afterwards
|
||||
function Vlayer.energy_changed(energy)
|
||||
local storage = vlayer_data.storage
|
||||
storage.energy = storage.energy + energy
|
||||
return storage.energy
|
||||
end
|
||||
|
||||
--- Get the statistics of the layer
|
||||
--- @return ExpScenario_Vlayer.Statistics
|
||||
function Vlayer.get_statistics()
|
||||
local properties = vlayer_data.properties
|
||||
local production = properties.production * mega
|
||||
local production_multiplier = get_production_multiplier()
|
||||
local sustained_multiplier = get_sustained_multiplier()
|
||||
local remaining_surface_area = get_remaining_surface_area()
|
||||
|
||||
return {
|
||||
total_surface_area = properties.total_surface_area,
|
||||
used_surface_area = properties.used_surface_area,
|
||||
remaining_surface_area = remaining_surface_area,
|
||||
surface_area = properties.total_surface_area - remaining_surface_area,
|
||||
production_multiplier = production_multiplier,
|
||||
energy_max = production,
|
||||
energy_production = production * production_multiplier,
|
||||
energy_total_production = properties.total_production * sustained_multiplier * mega,
|
||||
energy_sustained = production * sustained_multiplier,
|
||||
energy_capacity = properties.capacity * mega,
|
||||
energy_storage = vlayer_data.storage.energy,
|
||||
}
|
||||
end
|
||||
|
||||
--- Make an entity into an interface which players can not mine or damage
|
||||
--- @param interface LuaEntity
|
||||
--- @param interface_type ExpScenario_Vlayer.InterfaceType
|
||||
--- @param player LuaPlayer?
|
||||
local function register_interface(interface, interface_type, player)
|
||||
local interfaces = vlayer_data.interfaces[interface_type]
|
||||
interfaces[#interfaces + 1] = interface
|
||||
interface.last_user = player
|
||||
interface.destructible = false
|
||||
interface.minable_flag = false
|
||||
end
|
||||
|
||||
--- Create an interface which powers the electric network it is placed in
|
||||
--- @param surface LuaSurface
|
||||
--- @param position MapPosition
|
||||
--- @param player LuaPlayer?
|
||||
--- @return LuaEntity? # Nil when the interface does not fit
|
||||
function Vlayer.create_energy_interface(surface, position, player)
|
||||
if not surface.can_place_entity{ name = interface_names.energy, position = position } then
|
||||
return nil
|
||||
end
|
||||
|
||||
local interface = assert(surface.create_entity{ name = interface_names.energy, position = position, force = "neutral" })
|
||||
register_interface(interface, "energy", player)
|
||||
interface.operable = false
|
||||
interface.electric_buffer_size = 0
|
||||
interface.power_production = 0
|
||||
interface.power_usage = 0
|
||||
interface.energy = 0
|
||||
return interface
|
||||
end
|
||||
|
||||
--- Create an interface which outputs the statistics and stored items as signals
|
||||
--- @param surface LuaSurface
|
||||
--- @param position MapPosition
|
||||
--- @param player LuaPlayer?
|
||||
--- @return LuaEntity
|
||||
function Vlayer.create_circuit_interface(surface, position, player)
|
||||
local interface = assert(surface.create_entity{ name = interface_names.circuit, position = position, force = "neutral" })
|
||||
register_interface(interface, "circuit", player)
|
||||
interface.combinator_description = circuit_description
|
||||
return interface
|
||||
end
|
||||
|
||||
--- Create an interface which stores every allowed item put into it
|
||||
--- @param surface LuaSurface
|
||||
--- @param position MapPosition
|
||||
--- @param player LuaPlayer?
|
||||
--- @return LuaEntity
|
||||
function Vlayer.create_input_interface(surface, position, player)
|
||||
local interface = assert(surface.create_entity{ name = interface_names.storage_input, position = position, force = "neutral" })
|
||||
register_interface(interface, "storage_input", player)
|
||||
-- A filter nothing can match stops robots filling it with anything
|
||||
interface.storage_filter = { name = "deconstruction-planner", quality = "normal" }
|
||||
return interface
|
||||
end
|
||||
|
||||
--- Create an interface which takes stored items out to fulfil its requests
|
||||
--- @param surface LuaSurface
|
||||
--- @param position MapPosition
|
||||
--- @param player LuaPlayer?
|
||||
--- @return LuaEntity
|
||||
function Vlayer.create_output_interface(surface, position, player)
|
||||
local interface = assert(surface.create_entity{ name = interface_names.storage_output, position = position, force = "neutral" })
|
||||
register_interface(interface, "storage_output", player)
|
||||
return interface
|
||||
end
|
||||
|
||||
--- Remove an interface, anything it holds is dropped in a chest next to it and stored energy is kept
|
||||
--- @param interface LuaEntity
|
||||
--- @return ExpScenario_Vlayer.InterfaceType
|
||||
function Vlayer.remove_interface(interface)
|
||||
local interface_type = assert(interface_types[interface.name], "Entity is not a vlayer interface: " .. interface.name)
|
||||
if interface_type == "storage_input" or interface_type == "storage_output" then
|
||||
ExpUtil.transfer_inventory_to_surface{
|
||||
inventory = assert(interface.get_inventory(defines.inventory.chest)),
|
||||
surface = interface.surface,
|
||||
name = "iron-chest",
|
||||
allow_creation = true,
|
||||
}
|
||||
elseif interface_type == "energy" then
|
||||
vlayer_data.storage.energy = vlayer_data.storage.energy + interface.energy
|
||||
end
|
||||
|
||||
table.remove_element(vlayer_data.interfaces[interface_type], interface)
|
||||
interface.destroy()
|
||||
return interface_type
|
||||
end
|
||||
|
||||
--- Forget interfaces which no longer exist
|
||||
--- @param interfaces LuaEntity[]
|
||||
local function prune_interfaces(interfaces)
|
||||
for index = #interfaces, 1, -1 do
|
||||
if not interfaces[index].valid then
|
||||
table.remove(interfaces, index)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Store the contents of the input interfaces
|
||||
local function handle_input_interfaces()
|
||||
local interfaces = vlayer_data.interfaces.storage_input
|
||||
prune_interfaces(interfaces)
|
||||
for _, interface in ipairs(interfaces) do
|
||||
local inventory = assert(interface.get_inventory(defines.inventory.chest))
|
||||
for _, item in pairs(inventory.get_contents()) do
|
||||
local name = item.name
|
||||
if allowed_items[name] then
|
||||
-- Quality is not tracked, so higher quality is stored as extra normal items in batches of ten
|
||||
local quality_level = prototypes.quality[item.quality].level
|
||||
local removed, stored = 0, 0
|
||||
if quality_level == 0 then
|
||||
removed, stored = item.count, item.count
|
||||
elseif item.count >= 10 then
|
||||
local batches = floor(item.count / 10)
|
||||
removed, stored = batches * 10, batches * (10 + quality_level * 3)
|
||||
end
|
||||
|
||||
if removed > 0 then
|
||||
local modded = config.modded_items[name]
|
||||
if modded and config.modded_auto_downgrade then
|
||||
Vlayer.insert_item(modded.base_game_equivalent, stored * modded.multiplier)
|
||||
elseif vlayer_data.storage.power_items[name] then
|
||||
local power_item = vlayer_data.storage.power_items[name]
|
||||
power_item.count = power_item.count + stored
|
||||
else
|
||||
Vlayer.insert_item(name, stored)
|
||||
end
|
||||
inventory.remove{ name = name, count = removed, quality = item.quality }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Fill the requests of the output interfaces from storage
|
||||
local function handle_output_interfaces()
|
||||
local interfaces = vlayer_data.interfaces.storage_output
|
||||
prune_interfaces(interfaces)
|
||||
for _, interface in ipairs(interfaces) do
|
||||
local inventory = assert(interface.get_inventory(defines.inventory.chest))
|
||||
for _, section in ipairs(assert(interface.get_logistic_sections()).sections) do
|
||||
for _, filter in pairs(section.filters) do
|
||||
local name = filter.value and filter.value.name
|
||||
if name and allowed_items[name] then
|
||||
local wanted = min(assert(filter.min) - inventory.get_item_count(name), vlayer_data.storage.items[name])
|
||||
if wanted > 0 and inventory.can_insert{ name = name, count = wanted } then
|
||||
local removed = Vlayer.remove_item(name, wanted)
|
||||
if removed > 0 then
|
||||
inventory.insert{ name = name, count = removed, quality = "normal" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Allocate waiting items in proportion to the surface area that has become available
|
||||
local function handle_unallocated()
|
||||
if config.unlimited_surface_area then return end
|
||||
|
||||
local unallocated_area = 0
|
||||
for name, count in pairs(vlayer_data.storage.unallocated) do
|
||||
unallocated_area = unallocated_area + allowed_items[name].required_area * count
|
||||
end
|
||||
if unallocated_area == 0 then return end
|
||||
|
||||
local properties = vlayer_data.properties
|
||||
local surplus_area = properties.total_surface_area - properties.used_surface_area
|
||||
for name, count in pairs(vlayer_data.storage.unallocated) do
|
||||
local allocated = min(count, floor(count * surplus_area / unallocated_area))
|
||||
if allocated > 0 then
|
||||
vlayer_data.storage.unallocated[name] = count - allocated
|
||||
allocate_item(name, allocated)
|
||||
count_unallocated_production(name, -allocated)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the signals of the circuit interfaces to the statistics and stored items
|
||||
local function handle_circuit_interfaces()
|
||||
local interfaces = vlayer_data.interfaces.circuit
|
||||
prune_interfaces(interfaces)
|
||||
if #interfaces == 0 then return end
|
||||
|
||||
local stats = Vlayer.get_statistics()
|
||||
for _, interface in ipairs(interfaces) do
|
||||
local control = interface.get_or_create_control_behavior() --[[@as LuaConstantCombinatorControlBehavior]]
|
||||
if control.sections_count == 0 then
|
||||
control.add_section()
|
||||
end
|
||||
local section = assert(control.sections[1])
|
||||
|
||||
-- Slots are cleared first because an item can not be set in two slots at once
|
||||
for index = section.filters_count, 1, -1 do
|
||||
section.clear_slot(index)
|
||||
end
|
||||
|
||||
local index = 1
|
||||
for stat_name, signal_name in pairs(circuit_signals) do
|
||||
local value = stats[stat_name] --[[@as number]]
|
||||
if stat_name:find("energy") then
|
||||
value = value / mega
|
||||
elseif stat_name == "production_multiplier" then
|
||||
value = value * 10000
|
||||
end
|
||||
section.set_slot(index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = floor(value) })
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
for name, count in pairs(vlayer_data.storage.items) do
|
||||
if count > 0 and prototypes.item[name] then
|
||||
section.set_slot(index, { value = { type = "item", name = name, quality = "normal" }, min = count })
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Produce energy, share it between the energy interfaces, and burn fuel when storage runs low
|
||||
local function handle_energy_interfaces()
|
||||
local storage = vlayer_data.storage
|
||||
local properties = vlayer_data.properties
|
||||
local production = properties.production * mega * (config.update_tick_energy / 60)
|
||||
storage.energy = storage.energy + floor(production * get_production_multiplier())
|
||||
|
||||
local interfaces = vlayer_data.interfaces.energy
|
||||
prune_interfaces(interfaces)
|
||||
if #interfaces > 0 then
|
||||
local available_energy = storage.energy
|
||||
for _, interface in ipairs(interfaces) do
|
||||
available_energy = available_energy + interface.energy
|
||||
end
|
||||
|
||||
local discharge_rate = 2 * (production + properties.discharge * mega) / #interfaces
|
||||
local fill_to = min(discharge_rate, floor(available_energy / #interfaces))
|
||||
for _, interface in ipairs(interfaces) do
|
||||
interface.electric_buffer_size = max(discharge_rate, interface.energy) -- A smaller buffer would lose energy
|
||||
local delta = fill_to - interface.energy
|
||||
storage.energy = storage.energy - delta
|
||||
interface.energy = interface.energy + delta
|
||||
end
|
||||
end
|
||||
|
||||
local capacity = properties.capacity * mega
|
||||
if not config.unlimited_capacity and storage.energy > capacity then
|
||||
storage.energy = capacity
|
||||
return
|
||||
end
|
||||
|
||||
for _, power_item in pairs(storage.power_items) do
|
||||
local max_burn = capacity / 2 - storage.energy
|
||||
if power_item.count > 0 and max_burn > 0 then
|
||||
local burn = min(power_item.count, floor(max_burn / power_item.value))
|
||||
storage.energy = storage.energy + burn * power_item.value
|
||||
power_item.count = power_item.count - burn
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Take the day cycle from the mimic surface when it exists, otherwise from the config
|
||||
local function update_surface()
|
||||
if config.mimic_surface then
|
||||
local surface = game.get_surface(config.mimic_surface)
|
||||
if surface then
|
||||
vlayer_data.surface = surface
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- The config copy never has an index, so this avoids replacing it every time a surface changes
|
||||
if not vlayer_data.surface.index then
|
||||
vlayer_data.surface = table.deep_copy(config.surface)
|
||||
end
|
||||
end
|
||||
|
||||
--- Move items between the interfaces and storage
|
||||
local function update_storage()
|
||||
handle_input_interfaces()
|
||||
handle_output_interfaces()
|
||||
handle_unallocated()
|
||||
end
|
||||
|
||||
--- Update the signals and energy of the interfaces
|
||||
local function update_energy()
|
||||
handle_circuit_interfaces()
|
||||
handle_energy_interfaces()
|
||||
end
|
||||
|
||||
local e = defines.events
|
||||
|
||||
Vlayer.on_init = update_surface -- The default surface exists before on_surface_created can fire
|
||||
Vlayer.events[e.on_surface_created] = update_surface
|
||||
Vlayer.events[e.on_surface_renamed] = update_surface
|
||||
Vlayer.events[e.on_surface_imported] = update_surface
|
||||
Vlayer.on_nth_tick[config.update_tick_storage] = update_storage
|
||||
Vlayer.on_nth_tick[config.update_tick_energy] = update_energy
|
||||
|
||||
return Vlayer
|
||||
@@ -5,7 +5,7 @@ Adds a gui that allows players to apply various bonuses
|
||||
local Gui = require("modules/exp_gui")
|
||||
local Roles = require("modules/exp_roles")
|
||||
local config = require("modules/exp_legacy/config/bonus")
|
||||
local vlayer = require("modules/exp_legacy/modules/control/vlayer")
|
||||
local Vlayer = require("modules/exp_scenario/control/vlayer")
|
||||
local format_number = require("util").format_number
|
||||
|
||||
--- @class ExpGui_PlayerBonus.elements
|
||||
@@ -486,7 +486,7 @@ end
|
||||
--- Apply periodic bonus to a player
|
||||
--- @param player LuaPlayer
|
||||
local function apply_personal_battery_recharge(player)
|
||||
local available_energy = vlayer.get_statistics()["energy_storage"]
|
||||
local available_energy = Vlayer.get_statistics().energy_storage
|
||||
if available_energy <= 0 then
|
||||
return -- No power to give
|
||||
end
|
||||
@@ -508,7 +508,7 @@ local function apply_personal_battery_recharge(player)
|
||||
local energy_to_give = math.min(math.floor(equipment.max_energy - equipment.energy), available_energy, recharge_amount)
|
||||
equipment.energy = equipment.energy + energy_to_give
|
||||
recharge_amount = recharge_amount - energy_to_give
|
||||
available_energy = vlayer.energy_changed(-energy_to_give)
|
||||
available_energy = Vlayer.energy_changed(-energy_to_give)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,531 @@
|
||||
--[[-- Gui - Virtual Layer
|
||||
Shows what the virtual layer holds and lets players build and remove its interfaces
|
||||
]]
|
||||
|
||||
local Gui = require("modules/exp_gui")
|
||||
local Roles = require("modules/exp_roles")
|
||||
local Colors = require("modules/exp_util/include/color")
|
||||
local config = require("modules.exp_legacy.config.vlayer")
|
||||
local Vlayer = require("modules/exp_scenario/control/vlayer")
|
||||
local format_number = require("util").format_number
|
||||
|
||||
local Selection = require("modules/exp_util/selection")
|
||||
local SelectArea = Selection.connect("ExpGui_Vlayer")
|
||||
|
||||
local floor = math.floor
|
||||
local ceil = math.ceil
|
||||
local max = math.max
|
||||
local format_string = string.format
|
||||
|
||||
--- @class ExpGui_Vlayer.elements
|
||||
local Elements = {}
|
||||
|
||||
--- Interface types in the order the type dropdown lists them
|
||||
local interface_types = { "energy", "circuit", "storage_input", "storage_output" } --- @type ExpScenario_Vlayer.InterfaceType[]
|
||||
|
||||
--- Locale key of each interface type
|
||||
local type_captions = {} --- @type table<ExpScenario_Vlayer.InterfaceType, LocalisedString>
|
||||
for _, interface_type in ipairs(interface_types) do
|
||||
type_captions[interface_type] = { "exp-gui_vlayer.type-" .. interface_type:gsub("_", "-") }
|
||||
end
|
||||
|
||||
--- Statistics shown as progress bars, in display order
|
||||
--- @type { name: string, caption: LocalisedString, tooltip: LocalisedString }[]
|
||||
local stats = {}
|
||||
for _, name in ipairs{ "solar", "accumulator", "surface-area", "sustained", "production", "capacity" } do
|
||||
stats[#stats + 1] = {
|
||||
name = name,
|
||||
caption = { "exp-gui_vlayer.caption-stat-" .. name },
|
||||
tooltip = { "exp-gui_vlayer.tooltip-stat-" .. name },
|
||||
}
|
||||
end
|
||||
|
||||
local energy_suffixes = {
|
||||
{ 1e15, "P" }, { 1e12, "T" }, { 1e9, "G" }, { 1e6, "M" }, { 1e3, "k" },
|
||||
}
|
||||
|
||||
--- Format an amount of energy with an SI prefix, util.format_number uses B for giga
|
||||
--- @param amount number
|
||||
--- @param unit string
|
||||
--- @return string
|
||||
local function format_energy(amount, unit)
|
||||
if amount < 1 then
|
||||
return "0 " .. unit
|
||||
end
|
||||
for _, suffix in ipairs(energy_suffixes) do
|
||||
if amount >= suffix[1] then
|
||||
return format_string("%.1f %s%s", amount / suffix[1], suffix[2], unit)
|
||||
end
|
||||
end
|
||||
return format_number(floor(amount), false) .. " " .. unit
|
||||
end
|
||||
|
||||
--- Expand an area to whole tiles
|
||||
--- @param area BoundingBox
|
||||
--- @return BoundingBox
|
||||
local function expand_area(area)
|
||||
return {
|
||||
left_top = { x = floor(area.left_top.x), y = floor(area.left_top.y) },
|
||||
right_bottom = { x = ceil(area.right_bottom.x), y = ceil(area.right_bottom.y) },
|
||||
}
|
||||
end
|
||||
|
||||
--- Rich text link to a position
|
||||
--- @param position MapPosition
|
||||
--- @param surface LuaSurface
|
||||
--- @return string
|
||||
local function gps_string(position, surface)
|
||||
return format_string("[gps=%.1f,%.1f,%s]", position.x, position.y, surface.name)
|
||||
end
|
||||
|
||||
--- Label naming a statistic
|
||||
--- @class ExpGui_Vlayer.elements.stat_label: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement, caption: LocalisedString, tooltip: LocalisedString): LuaGuiElement
|
||||
Elements.stat_label = Gui.define("vlayer/stat_label")
|
||||
:draw{
|
||||
type = "label",
|
||||
caption = Gui.from_argument(1),
|
||||
tooltip = Gui.from_argument(2),
|
||||
style = "heading_2_label",
|
||||
}
|
||||
:style{
|
||||
width = 200,
|
||||
} --[[@as any]]
|
||||
|
||||
--- Bar showing a statistic as a fraction with the values as its caption
|
||||
--- @class ExpGui_Vlayer.elements.stat_bar: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
||||
Elements.stat_bar = Gui.define("vlayer/stat_bar")
|
||||
:draw{
|
||||
type = "progressbar",
|
||||
caption = "",
|
||||
value = 0,
|
||||
style = "electric_satisfaction_statistics_progressbar",
|
||||
}
|
||||
:style{
|
||||
width = 200,
|
||||
font = "heading-2",
|
||||
} --[[@as any]]
|
||||
|
||||
--- @class ExpGui_Vlayer.elements.stat_bar.display_data
|
||||
--- @field value number Fraction the bar is filled
|
||||
--- @field caption string
|
||||
|
||||
--- Refresh a bar
|
||||
--- @param stat_bar LuaGuiElement
|
||||
--- @param display_data ExpGui_Vlayer.elements.stat_bar.display_data
|
||||
function Elements.stat_bar.refresh(stat_bar, display_data)
|
||||
stat_bar.value = display_data.value
|
||||
stat_bar.caption = display_data.caption
|
||||
end
|
||||
|
||||
--- Table of a label and bar for each statistic
|
||||
--- @class ExpGui_Vlayer.elements.stats_table: ExpElement
|
||||
--- @field data table<LuaGuiElement, table<string, LuaGuiElement>> Bars keyed by statistic name
|
||||
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
||||
Elements.stats_table = Gui.define("vlayer/stats_table")
|
||||
:track_all_elements()
|
||||
:draw(function(def, parent)
|
||||
--- @cast def ExpGui_Vlayer.elements.stats_table
|
||||
local stats_table = Gui.elements.scroll_table(parent, 400, 2, "stats")
|
||||
local stat_bars = {}
|
||||
for _, stat in ipairs(stats) do
|
||||
Elements.stat_label(stats_table, stat.caption, stat.tooltip)
|
||||
stat_bars[stat.name] = Elements.stat_bar(stats_table)
|
||||
end
|
||||
def.data[stats_table] = stat_bars
|
||||
return stats_table
|
||||
end) --[[@as any]]
|
||||
|
||||
--- Calculate the display data of every statistic, the same for every player
|
||||
--- @return table<string, ExpGui_Vlayer.elements.stat_bar.display_data>
|
||||
function Elements.stats_table.calculate_display_data()
|
||||
local statistics = Vlayer.get_statistics()
|
||||
local items = Vlayer.get_items()
|
||||
local allocated = Vlayer.get_allocated_items()
|
||||
return {
|
||||
solar = {
|
||||
value = allocated["solar-panel"] / max(items["solar-panel"], 1),
|
||||
caption = format_number(allocated["solar-panel"], false) .. " / " .. format_number(items["solar-panel"], false),
|
||||
},
|
||||
accumulator = {
|
||||
value = allocated["accumulator"] / max(items["accumulator"], 1),
|
||||
caption = format_number(allocated["accumulator"], false) .. " / " .. format_number(items["accumulator"], false),
|
||||
},
|
||||
["surface-area"] = {
|
||||
value = statistics.total_surface_area / max(statistics.surface_area, 1),
|
||||
caption = format_number(statistics.remaining_surface_area, false),
|
||||
},
|
||||
sustained = {
|
||||
value = statistics.energy_sustained / max(statistics.energy_total_production, 1),
|
||||
caption = format_energy(statistics.energy_sustained, "W") .. " / " .. format_energy(statistics.energy_total_production, "W"),
|
||||
},
|
||||
production = {
|
||||
value = statistics.energy_production / max(statistics.energy_max, 1),
|
||||
caption = format_energy(statistics.energy_production, "W") .. " / " .. format_energy(statistics.energy_max, "W"),
|
||||
},
|
||||
capacity = {
|
||||
value = statistics.energy_storage / max(statistics.energy_capacity, 1),
|
||||
caption = format_energy(statistics.energy_storage, "J") .. " / " .. format_energy(statistics.energy_capacity, "J"),
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
--- Refresh every bar of a table
|
||||
--- @param stats_table LuaGuiElement
|
||||
--- @param display_data table<string, ExpGui_Vlayer.elements.stat_bar.display_data>
|
||||
function Elements.stats_table.refresh(stats_table, display_data)
|
||||
for name, stat_bar in pairs(Elements.stats_table.data[stats_table]) do
|
||||
Elements.stat_bar.refresh(stat_bar, display_data[name])
|
||||
end
|
||||
end
|
||||
|
||||
--- Refresh the tables of every online player
|
||||
function Elements.stats_table.refresh_online()
|
||||
local display_data = Elements.stats_table.calculate_display_data()
|
||||
for _, stats_table in Elements.stats_table:online_elements() do
|
||||
Elements.stats_table.refresh(stats_table, display_data)
|
||||
end
|
||||
end
|
||||
|
||||
--- @class ExpGui_Vlayer.elements.type_dropdown.element_data
|
||||
--- @field controls LuaGuiElement
|
||||
|
||||
--- Dropdown which picks the type of interface to build or list
|
||||
--- @class ExpGui_Vlayer.elements.type_dropdown: ExpElement
|
||||
--- @field data table<LuaGuiElement, ExpGui_Vlayer.elements.type_dropdown.element_data>
|
||||
--- @overload fun(parent: LuaGuiElement, controls: LuaGuiElement): LuaGuiElement
|
||||
Elements.type_dropdown = Gui.define("vlayer/type_dropdown")
|
||||
:draw{
|
||||
type = "drop-down",
|
||||
items = { type_captions.energy, type_captions.circuit, type_captions.storage_input, type_captions.storage_output },
|
||||
selected_index = 1,
|
||||
}
|
||||
:style{
|
||||
width = 200,
|
||||
}
|
||||
:element_data{
|
||||
controls = Gui.from_argument(1),
|
||||
}
|
||||
:on_selection_state_changed(function(def, _, type_dropdown)
|
||||
--- @cast def ExpGui_Vlayer.elements.type_dropdown
|
||||
Elements.controls.refresh_interfaces(def.data[type_dropdown].controls)
|
||||
end) --[[@as any]]
|
||||
|
||||
--- Get the selected interface type
|
||||
--- @param type_dropdown LuaGuiElement
|
||||
--- @return ExpScenario_Vlayer.InterfaceType
|
||||
function Elements.type_dropdown.get_selected(type_dropdown)
|
||||
return (assert(interface_types[type_dropdown.selected_index]))
|
||||
end
|
||||
|
||||
--- Dropdown listing the interfaces of the selected type by position
|
||||
--- @class ExpGui_Vlayer.elements.interface_dropdown: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
||||
Elements.interface_dropdown = Gui.define("vlayer/interface_dropdown")
|
||||
:draw{
|
||||
type = "drop-down",
|
||||
}
|
||||
:style{
|
||||
width = 200,
|
||||
} --[[@as any]]
|
||||
|
||||
--- Refresh the listed interfaces
|
||||
--- @param interface_dropdown LuaGuiElement
|
||||
--- @param interfaces LuaEntity[]
|
||||
function Elements.interface_dropdown.refresh(interface_dropdown, interfaces)
|
||||
local items = {}
|
||||
for index, interface in ipairs(interfaces) do
|
||||
local position = interface.position
|
||||
items[index] = format_string("%d X %.1f Y %.1f", index, position.x, position.y)
|
||||
end
|
||||
interface_dropdown.items = items
|
||||
end
|
||||
|
||||
--- Get the selected interface
|
||||
--- @param interface_dropdown LuaGuiElement
|
||||
--- @param interfaces LuaEntity[]
|
||||
--- @return LuaEntity?
|
||||
function Elements.interface_dropdown.get_selected(interface_dropdown, interfaces)
|
||||
local interface = interfaces[interface_dropdown.selected_index]
|
||||
if interface and interface.valid then
|
||||
return interface
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- @class ExpGui_Vlayer.elements.control_button.element_data
|
||||
--- @field controls LuaGuiElement
|
||||
|
||||
--- Button which refreshes the listed interfaces
|
||||
--- @class ExpGui_Vlayer.elements.refresh_button: ExpElement
|
||||
--- @field data table<LuaGuiElement, ExpGui_Vlayer.elements.control_button.element_data>
|
||||
--- @overload fun(parent: LuaGuiElement, controls: LuaGuiElement): LuaGuiElement
|
||||
Elements.refresh_button = Gui.define("vlayer/refresh_button")
|
||||
:draw{
|
||||
type = "button",
|
||||
caption = { "exp-gui_vlayer.caption-refresh" },
|
||||
}
|
||||
:style{
|
||||
width = 200,
|
||||
}
|
||||
:element_data{
|
||||
controls = Gui.from_argument(1),
|
||||
}
|
||||
:on_click(function(def, _, refresh_button)
|
||||
--- @cast def ExpGui_Vlayer.elements.refresh_button
|
||||
Elements.controls.refresh_interfaces(def.data[refresh_button].controls)
|
||||
end) --[[@as any]]
|
||||
|
||||
--- Button which opens the map at the selected interface
|
||||
--- @class ExpGui_Vlayer.elements.view_button: ExpElement
|
||||
--- @field data table<LuaGuiElement, ExpGui_Vlayer.elements.control_button.element_data>
|
||||
--- @overload fun(parent: LuaGuiElement, controls: LuaGuiElement): LuaGuiElement
|
||||
Elements.view_button = Gui.define("vlayer/view_button")
|
||||
:draw{
|
||||
type = "button",
|
||||
caption = { "exp-gui_vlayer.caption-view" },
|
||||
}
|
||||
:style{
|
||||
width = 200,
|
||||
}
|
||||
:element_data{
|
||||
controls = Gui.from_argument(1),
|
||||
}
|
||||
:on_click(function(def, player, view_button)
|
||||
--- @cast def ExpGui_Vlayer.elements.view_button
|
||||
local controls = def.data[view_button].controls
|
||||
local interface_type, interface = Elements.controls.get_selected_interface(controls)
|
||||
if not interface then return end
|
||||
player.set_controller{ type = defines.controllers.remote, position = interface.position, surface = interface.surface }
|
||||
player.print{ "exp-gui_vlayer.message-located", type_captions[interface_type], gps_string(interface.position, interface.surface) }
|
||||
end) --[[@as any]]
|
||||
|
||||
--- Button which toggles selecting an area to build an interface in
|
||||
--- @class ExpGui_Vlayer.elements.build_button: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
||||
Elements.build_button = Gui.define("vlayer/build_button")
|
||||
:track_all_elements()
|
||||
:draw{
|
||||
type = "button",
|
||||
caption = { "exp-gui_vlayer.caption-build" },
|
||||
}
|
||||
:style{
|
||||
width = 200,
|
||||
}
|
||||
:on_click(function(_, player)
|
||||
if SelectArea:stop(player) then
|
||||
player.print{ "exp_util.selection_exit", { "exp-gui_vlayer.selection-name" } }
|
||||
else
|
||||
SelectArea:start(player)
|
||||
player.print{ "exp_util.selection_enter", { "exp-gui_vlayer.selection-name" } }
|
||||
end
|
||||
end) --[[@as any]]
|
||||
|
||||
--- Button which removes the selected interface
|
||||
--- @class ExpGui_Vlayer.elements.remove_button: ExpElement
|
||||
--- @field data table<LuaGuiElement, ExpGui_Vlayer.elements.control_button.element_data>
|
||||
--- @overload fun(parent: LuaGuiElement, controls: LuaGuiElement): LuaGuiElement
|
||||
Elements.remove_button = Gui.define("vlayer/remove_button")
|
||||
:track_all_elements()
|
||||
:draw{
|
||||
type = "button",
|
||||
caption = { "exp-gui_vlayer.caption-remove" },
|
||||
}
|
||||
:style{
|
||||
width = 200,
|
||||
}
|
||||
:element_data{
|
||||
controls = Gui.from_argument(1),
|
||||
}
|
||||
:on_click(function(def, player, remove_button)
|
||||
--- @cast def ExpGui_Vlayer.elements.remove_button
|
||||
local controls = def.data[remove_button].controls
|
||||
local _, interface = Elements.controls.get_selected_interface(controls)
|
||||
if not interface then return end
|
||||
local position, surface = interface.position, interface.surface
|
||||
local interface_type = Vlayer.remove_interface(interface)
|
||||
game.print{ "exp-gui_vlayer.message-removed", player.name, type_captions[interface_type], gps_string(position, surface) }
|
||||
Elements.controls.refresh_interfaces_online()
|
||||
end) --[[@as any]]
|
||||
|
||||
--- @class ExpGui_Vlayer.elements.controls.element_data
|
||||
--- @field type_dropdown LuaGuiElement
|
||||
--- @field interface_dropdown LuaGuiElement
|
||||
|
||||
--- Table of the dropdowns and buttons used to manage interfaces
|
||||
--- @class ExpGui_Vlayer.elements.controls: ExpElement
|
||||
--- @field data table<LuaGuiElement, ExpGui_Vlayer.elements.controls.element_data>
|
||||
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
||||
Elements.controls = Gui.define("vlayer/controls")
|
||||
:track_all_elements()
|
||||
:draw(function(def, parent)
|
||||
--- @cast def ExpGui_Vlayer.elements.controls
|
||||
local controls = Gui.elements.scroll_table(parent, 400, 2, "controls")
|
||||
local player = Gui.get_player(parent)
|
||||
local can_edit = Roles.player_has_permission(player, "exp_scenario.gui.vlayer_edit")
|
||||
|
||||
local type_dropdown = Elements.type_dropdown(controls, controls)
|
||||
local interface_dropdown = Elements.interface_dropdown(controls)
|
||||
Elements.refresh_button(controls, controls)
|
||||
Elements.view_button(controls, controls)
|
||||
Elements.build_button(controls).visible = can_edit
|
||||
Elements.remove_button(controls, controls).visible = can_edit
|
||||
|
||||
def.data[controls] = {
|
||||
type_dropdown = type_dropdown,
|
||||
interface_dropdown = interface_dropdown,
|
||||
}
|
||||
Elements.controls.refresh_interfaces(controls)
|
||||
return controls
|
||||
end) --[[@as any]]
|
||||
|
||||
--- Get the selected interface type
|
||||
--- @param controls LuaGuiElement
|
||||
--- @return ExpScenario_Vlayer.InterfaceType
|
||||
function Elements.controls.get_selected_type(controls)
|
||||
return Elements.type_dropdown.get_selected(Elements.controls.data[controls].type_dropdown)
|
||||
end
|
||||
|
||||
--- Get the selected interface and its type
|
||||
--- @param controls LuaGuiElement
|
||||
--- @return ExpScenario_Vlayer.InterfaceType, LuaEntity?
|
||||
function Elements.controls.get_selected_interface(controls)
|
||||
local elements = Elements.controls.data[controls]
|
||||
local interface_type = Elements.type_dropdown.get_selected(elements.type_dropdown)
|
||||
local interfaces = Vlayer.get_interfaces()[interface_type]
|
||||
return interface_type, Elements.interface_dropdown.get_selected(elements.interface_dropdown, interfaces)
|
||||
end
|
||||
|
||||
--- Refresh the listed interfaces to match the selected type
|
||||
--- @param controls LuaGuiElement
|
||||
function Elements.controls.refresh_interfaces(controls)
|
||||
local elements = Elements.controls.data[controls]
|
||||
local interface_type = Elements.type_dropdown.get_selected(elements.type_dropdown)
|
||||
Elements.interface_dropdown.refresh(elements.interface_dropdown, Vlayer.get_interfaces()[interface_type])
|
||||
end
|
||||
|
||||
--- Refresh the listed interfaces of every online player, used when an interface is built or removed
|
||||
function Elements.controls.refresh_interfaces_online()
|
||||
for _, controls in Elements.controls:online_elements() do
|
||||
Elements.controls.refresh_interfaces(controls)
|
||||
end
|
||||
end
|
||||
|
||||
--- Refresh whether a player can see the edit buttons
|
||||
--- @param player LuaPlayer
|
||||
function Elements.controls.refresh_permissions(player)
|
||||
local can_edit = Roles.player_has_permission(player, "exp_scenario.gui.vlayer_edit")
|
||||
for _, build_button in Elements.build_button:tracked_elements(player) do
|
||||
build_button.visible = can_edit
|
||||
end
|
||||
for _, remove_button in Elements.remove_button:tracked_elements(player) do
|
||||
remove_button.visible = can_edit
|
||||
end
|
||||
end
|
||||
|
||||
--- Container added to the left gui flow
|
||||
--- @class ExpGui_Vlayer.elements.container: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement): LuaGuiElement
|
||||
Elements.container = Gui.define("vlayer/container")
|
||||
:draw(function(_, parent)
|
||||
local container = Gui.elements.container(parent, 400)
|
||||
local stats_table = Elements.stats_table(container)
|
||||
Elements.stats_table.refresh(stats_table, Elements.stats_table.calculate_display_data())
|
||||
Elements.controls(container)
|
||||
return Gui.elements.container.get_root_element(container)
|
||||
end) --[[@as any]]
|
||||
|
||||
--- Add the element to the left flow with a toolbar button
|
||||
Gui.add_left_element(Elements.container, false)
|
||||
Gui.toolbar.create_button{
|
||||
name = "toggle_vlayer",
|
||||
left_element = Elements.container,
|
||||
sprite = "entity/solar-panel",
|
||||
tooltip = { "exp-gui_vlayer.tooltip-main" },
|
||||
visible = function(player, element)
|
||||
return Roles.player_has_permission(player, "exp_scenario.gui.vlayer")
|
||||
end
|
||||
}
|
||||
|
||||
--- Replace the one marker entity in a selected area with an interface of the type the player picked
|
||||
--- @param event EventData.on_player_selected_area
|
||||
local function on_area_selected(event)
|
||||
local player = Gui.get_player(event)
|
||||
local surface = event.surface
|
||||
local area = expand_area(event.area)
|
||||
local force = player.force --[[@as LuaForce]]
|
||||
|
||||
local interface_type = "energy" --- @type ExpScenario_Vlayer.InterfaceType
|
||||
for _, controls in Elements.controls:tracked_elements(player) do
|
||||
interface_type = Elements.controls.get_selected_type(controls)
|
||||
end
|
||||
|
||||
-- Platforms have no chests, so a combinator marks the spot once the research allows it
|
||||
local marker_name = "steel-chest"
|
||||
if config.power_on_space and surface.platform and interface_type == "energy" then
|
||||
local research = config.power_on_space_research
|
||||
if force.technologies[research.name].level < research.level then
|
||||
player.print({ "exp-gui_vlayer.message-research", research.name, research.level }, Colors.orange_red)
|
||||
return
|
||||
end
|
||||
marker_name = "constant-combinator"
|
||||
end
|
||||
|
||||
local markers = surface.find_entities_filtered{ area = area, name = marker_name, force = force }
|
||||
if #markers == 0 then
|
||||
player.print({ "exp-gui_vlayer.message-no-chest" }, Colors.orange_red)
|
||||
return
|
||||
elseif #markers > 1 then
|
||||
player.print({ "exp-gui_vlayer.message-multiple", type_captions[interface_type] }, Colors.orange_red)
|
||||
return
|
||||
end
|
||||
|
||||
local marker = assert(markers[1])
|
||||
if marker.name == "steel-chest" and not assert(marker.get_inventory(defines.inventory.chest)).is_empty() then
|
||||
player.print({ "exp-gui_vlayer.message-chest-not-empty" }, Colors.orange_red)
|
||||
return
|
||||
end
|
||||
|
||||
if #Vlayer.get_interfaces()[interface_type] >= config.interface_limit[interface_type] then
|
||||
player.print({ "exp-gui_vlayer.message-limit", type_captions[interface_type] }, Colors.orange_red)
|
||||
return
|
||||
end
|
||||
|
||||
local position = marker.position
|
||||
marker.destroy()
|
||||
|
||||
if interface_type == "energy" then
|
||||
if not Vlayer.create_energy_interface(surface, position, player) then
|
||||
player.print({ "exp-gui_vlayer.message-no-space" }, Colors.orange_red)
|
||||
return
|
||||
end
|
||||
elseif interface_type == "circuit" then
|
||||
Vlayer.create_circuit_interface(surface, position, player)
|
||||
elseif interface_type == "storage_input" then
|
||||
Vlayer.create_input_interface(surface, position, player)
|
||||
else
|
||||
Vlayer.create_output_interface(surface, position, player)
|
||||
end
|
||||
|
||||
game.print{ "exp-gui_vlayer.message-built", player.name, type_captions[interface_type], gps_string(position, surface) }
|
||||
Elements.controls.refresh_interfaces_online()
|
||||
end
|
||||
|
||||
SelectArea:on_selection(on_area_selected)
|
||||
|
||||
--- Refresh the edit buttons as the permissions of a player may have changed
|
||||
--- @param event EventData.ExpRoles.on_player_roles_changed
|
||||
local function refresh_player_permissions(event)
|
||||
Elements.controls.refresh_permissions(Gui.get_player(event))
|
||||
end
|
||||
|
||||
return {
|
||||
elements = Elements,
|
||||
events = {
|
||||
[Roles.events.on_player_roles_changed] = refresh_player_permissions,
|
||||
},
|
||||
on_nth_tick = {
|
||||
[config.update_tick_gui] = Elements.stats_table.refresh_online,
|
||||
}
|
||||
}
|
||||
@@ -490,6 +490,39 @@ caption-create-footer=Create task
|
||||
caption-edit-footer=Edit task
|
||||
caption-view-footer=Task details
|
||||
|
||||
[exp-gui_vlayer]
|
||||
tooltip-main=Virtual Layer
|
||||
selection-name=vlayer
|
||||
caption-stat-solar=[img=entity/solar-panel] Solar Panels
|
||||
tooltip-stat-solar=Solar panels in use out of those stored
|
||||
caption-stat-accumulator=[img=entity/accumulator] Accumulators
|
||||
tooltip-stat-accumulator=Accumulators in use out of those stored
|
||||
caption-stat-surface-area=[virtual-signal=signal-R] Remaining Surface
|
||||
tooltip-stat-surface-area=Remaining surface area, insert landfill to increase it
|
||||
caption-stat-sustained=[virtual-signal=signal-S] Sustained Production
|
||||
tooltip-stat-sustained=Power production averaged over a day, out of what every stored solar panel could give
|
||||
caption-stat-production=[virtual-signal=signal-P] Current Production
|
||||
tooltip-stat-production=Power production right now, out of the production in full daylight
|
||||
caption-stat-capacity=[virtual-signal=signal-E] Battery Charge
|
||||
tooltip-stat-capacity=Stored energy out of the accumulator capacity
|
||||
type-energy=Energy
|
||||
type-circuit=Circuit
|
||||
type-storage-input=Storage Input
|
||||
type-storage-output=Storage Output
|
||||
caption-refresh=Refresh List
|
||||
caption-view=View Interface
|
||||
caption-build=Build Interface
|
||||
caption-remove=Remove Interface
|
||||
message-located=The __1__ interface is at __2__
|
||||
message-built=__1__ built a __2__ interface at __3__
|
||||
message-removed=__1__ removed a __2__ interface at __3__
|
||||
message-no-chest=No steel chest was selected
|
||||
message-chest-not-empty=The steel chest is not empty
|
||||
message-multiple=Cannot build a __1__ interface, more than one steel chest was selected
|
||||
message-limit=Cannot build a __1__ interface, the limit has been reached
|
||||
message-no-space=Cannot build an energy interface here, there is not enough space
|
||||
message-research=You need to research [technology=__1__] to level __2__ to use the vlayer on a space platform
|
||||
|
||||
[exp-gui_warp-list]
|
||||
caption-main=Warp List [img=info]
|
||||
tooltip-main=Warp List
|
||||
|
||||
@@ -517,6 +517,39 @@ tooltip-goto-different-network=該傳送陣未連接, 你可使用電纜連接
|
||||
tooltip-goto-cooldown=傳送冷卻中, 請等候冷卻
|
||||
tooltip-goto-disabled=你不在傳送陣, 請移至任意傳送陣
|
||||
|
||||
[exp-gui_vlayer]
|
||||
tooltip-main=Vlayer 介面
|
||||
selection-name=vlayer
|
||||
caption-stat-solar=[img=entity/solar-panel] 太陽能板
|
||||
tooltip-stat-solar=使用中的太陽能板數量 / 儲存的太陽能板數量
|
||||
caption-stat-accumulator=[img=entity/accumulator] 蓄電池
|
||||
tooltip-stat-accumulator=使用中的蓄電池數量 / 儲存的蓄電池數量
|
||||
caption-stat-surface-area=[virtual-signal=signal-R] 餘下土地
|
||||
tooltip-stat-surface-area=餘下土地。 放入堆填來增加
|
||||
caption-stat-sustained=[virtual-signal=signal-S] 持續產能
|
||||
tooltip-stat-sustained=持續電力產能
|
||||
caption-stat-production=[virtual-signal=signal-P] 現時產能
|
||||
tooltip-stat-production=現時電力產能
|
||||
caption-stat-capacity=[virtual-signal=signal-E] 電池電量
|
||||
tooltip-stat-capacity=現時電池電量 / 電池最大容量
|
||||
type-energy=電力
|
||||
type-circuit=回路
|
||||
type-storage-input=放入箱
|
||||
type-storage-output=提取箱
|
||||
caption-refresh=刷新列表
|
||||
caption-view=查看介面
|
||||
caption-build=建造介面
|
||||
caption-remove=拆除介面
|
||||
message-located=__1__ 介面在 __2__
|
||||
message-built=__1__ 在 __3__ 建造了 __2__ 介面
|
||||
message-removed=__1__ 在 __3__ 拆除了 __2__ 介面
|
||||
message-no-chest=沒檢測到鋼箱
|
||||
message-chest-not-empty=鋼箱不是空的
|
||||
message-multiple=無法建造 __1__ 介面,檢測到多個鋼箱
|
||||
message-limit=無法建造 __1__ 介面,超出最大限制
|
||||
message-no-space=無法在此建造電力介面,空間不足
|
||||
message-research=你要研究 [technology=__1__] 在 __2__ 級 才可使用太空平台 vlayer 功能。
|
||||
|
||||
[exp_afk-kick]
|
||||
kick-message=因地圖中沒有活躍玩家,所以所有人都已被請離。
|
||||
|
||||
|
||||
@@ -517,6 +517,39 @@ tooltip-goto-different-network=該傳送陣未連接, 你可使用電纜連接
|
||||
tooltip-goto-cooldown=傳送冷卻中, 請等候冷卻
|
||||
tooltip-goto-disabled=你不在傳送陣, 請移至任意傳送陣
|
||||
|
||||
[exp-gui_vlayer]
|
||||
tooltip-main=Vlayer 介面
|
||||
selection-name=vlayer
|
||||
caption-stat-solar=[img=entity/solar-panel] 太陽能板
|
||||
tooltip-stat-solar=使用中的太陽能板數量 / 儲存的太陽能板數量
|
||||
caption-stat-accumulator=[img=entity/accumulator] 蓄電池
|
||||
tooltip-stat-accumulator=使用中的蓄電池數量 / 儲存的蓄電池數量
|
||||
caption-stat-surface-area=[virtual-signal=signal-R] 餘下土地
|
||||
tooltip-stat-surface-area=餘下土地。 放入堆填來增加
|
||||
caption-stat-sustained=[virtual-signal=signal-S] 持續產能
|
||||
tooltip-stat-sustained=持續電力產能
|
||||
caption-stat-production=[virtual-signal=signal-P] 現時產能
|
||||
tooltip-stat-production=現時電力產能
|
||||
caption-stat-capacity=[virtual-signal=signal-E] 電池電量
|
||||
tooltip-stat-capacity=現時電池電量 / 電池最大容量
|
||||
type-energy=電力
|
||||
type-circuit=回路
|
||||
type-storage-input=放入箱
|
||||
type-storage-output=提取箱
|
||||
caption-refresh=刷新列表
|
||||
caption-view=查看介面
|
||||
caption-build=建造介面
|
||||
caption-remove=拆除介面
|
||||
message-located=__1__ 介面在 __2__
|
||||
message-built=__1__ 在 __3__ 建造了 __2__ 介面
|
||||
message-removed=__1__ 在 __3__ 拆除了 __2__ 介面
|
||||
message-no-chest=沒檢測到鋼箱
|
||||
message-chest-not-empty=鋼箱不是空的
|
||||
message-multiple=無法建造 __1__ 介面,檢測到多個鋼箱
|
||||
message-limit=無法建造 __1__ 介面,超出最大限制
|
||||
message-no-space=無法在此建造電力介面,空間不足
|
||||
message-research=你要研究 [technology=__1__] 在 __2__ 級 才可使用太空平台 vlayer 功能。
|
||||
|
||||
[exp_afk-kick]
|
||||
kick-message=因地圖中沒有活躍玩家,所以所有人都已被請離。
|
||||
|
||||
|
||||
Reference in new issue
Block a user