From dfe3a6cfcea1a068da1eb12b3a05ae32555fb53c Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Sat, 5 Sep 2026 20:44:37 +0000 Subject: [PATCH] Move the virtual layer into exp_scenario 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. --- exp_legacy/module/config/_file_loader.lua | 4 - exp_legacy/module/locale/en.cfg | 39 - exp_legacy/module/locale/zh-CN.cfg | 39 - exp_legacy/module/locale/zh-TW.cfg | 39 - exp_legacy/module/modules/control/vlayer.lua | 807 ------------------- exp_legacy/module/modules/gui/vlayer.lua | 547 ------------- exp_scenario/module/commands/vlayer.lua | 4 +- exp_scenario/module/control.lua | 2 + exp_scenario/module/control/vlayer.lua | 673 ++++++++++++++++ exp_scenario/module/gui/player_bonus.lua | 6 +- exp_scenario/module/gui/vlayer.lua | 531 ++++++++++++ exp_scenario/module/locale/en.cfg | 33 + exp_scenario/module/locale/zh-CN.cfg | 33 + exp_scenario/module/locale/zh-TW.cfg | 33 + 14 files changed, 1310 insertions(+), 1480 deletions(-) delete mode 100644 exp_legacy/module/modules/control/vlayer.lua delete mode 100644 exp_legacy/module/modules/gui/vlayer.lua create mode 100644 exp_scenario/module/control/vlayer.lua create mode 100644 exp_scenario/module/gui/vlayer.lua diff --git a/exp_legacy/module/config/_file_loader.lua b/exp_legacy/module/config/_file_loader.lua index 88fdec5efe..8b9937d428 100644 --- a/exp_legacy/module/config/_file_loader.lua +++ b/exp_legacy/module/config/_file_loader.lua @@ -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", @@ -21,7 +18,6 @@ return { --- GUI "modules.gui.warp-list", - "modules.gui.vlayer", "modules.graftorio.require", -- graftorio --- Config Files diff --git a/exp_legacy/module/locale/en.cfg b/exp_legacy/module/locale/en.cfg index 708296e0cb..b17233736e 100644 --- a/exp_legacy/module/locale/en.cfg +++ b/exp_legacy/module/locale/en.cfg @@ -215,42 +215,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. diff --git a/exp_legacy/module/locale/zh-CN.cfg b/exp_legacy/module/locale/zh-CN.cfg index 3f4027b1a5..02213c7a6c 100644 --- a/exp_legacy/module/locale/zh-CN.cfg +++ b/exp_legacy/module/locale/zh-CN.cfg @@ -216,45 +216,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選上來設定喜好 diff --git a/exp_legacy/module/locale/zh-TW.cfg b/exp_legacy/module/locale/zh-TW.cfg index 3f4027b1a5..02213c7a6c 100644 --- a/exp_legacy/module/locale/zh-TW.cfg +++ b/exp_legacy/module/locale/zh-TW.cfg @@ -216,45 +216,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選上來設定喜好 diff --git a/exp_legacy/module/modules/control/vlayer.lua b/exp_legacy/module/modules/control/vlayer.lua deleted file mode 100644 index 72afba6c3d..0000000000 --- a/exp_legacy/module/modules/control/vlayer.lua +++ /dev/null @@ -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 - power_items = {}, --- @type table - energy = 0, - unallocated = {}, --- @type table - }, - 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 diff --git a/exp_legacy/module/modules/gui/vlayer.lua b/exp_legacy/module/modules/gui/vlayer.lua deleted file mode 100644 index 1060b78635..0000000000 --- a/exp_legacy/module/modules/gui/vlayer.lua +++ /dev/null @@ -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) diff --git a/exp_scenario/module/commands/vlayer.lua b/exp_scenario/module/commands/vlayer.lua index f4b38e9651..8ba856d9db 100644 --- a/exp_scenario/module/commands/vlayer.lua +++ b/exp_scenario/module/commands/vlayer.lua @@ -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 diff --git a/exp_scenario/module/control.lua b/exp_scenario/module/control.lua index 1cd47d66ef..724b5d988c 100644 --- a/exp_scenario/module/control.lua +++ b/exp_scenario/module/control.lua @@ -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,5 +84,6 @@ 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/debug/shim")) add(require("modules/exp_scenario/gui/debug/event_view")) diff --git a/exp_scenario/module/control/vlayer.lua b/exp_scenario/module/control/vlayer.lua new file mode 100644 index 0000000000..7d0635573f --- /dev/null +++ b/exp_scenario/module/control/vlayer.lua @@ -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 Every stored item, allocated or not +--- @field unallocated table Items waiting for surface area +--- @field power_items table 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 +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 + +local interface_types = {} --- @type table +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 + +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 +function Vlayer.get_items() + return vlayer_data.storage.items +end + +--- Get the items waiting for surface area keyed by name +--- @return table +function Vlayer.get_unallocated_items() + return vlayer_data.storage.unallocated +end + +--- Get the items which contribute to the properties keyed by name +--- @return table +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 +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 diff --git a/exp_scenario/module/gui/player_bonus.lua b/exp_scenario/module/gui/player_bonus.lua index 13317530f4..1fadeccc61 100644 --- a/exp_scenario/module/gui/player_bonus.lua +++ b/exp_scenario/module/gui/player_bonus.lua @@ -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 diff --git a/exp_scenario/module/gui/vlayer.lua b/exp_scenario/module/gui/vlayer.lua new file mode 100644 index 0000000000..b5bea9518c --- /dev/null +++ b/exp_scenario/module/gui/vlayer.lua @@ -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 +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> 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 +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 +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 +--- @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 +--- @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 +--- @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 +--- @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 +--- @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, + } +} diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index 283460361e..a0ff5aa98c 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -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_afk-kick] kick-message=All players were kicked because everyone was AFK. diff --git a/exp_scenario/module/locale/zh-CN.cfg b/exp_scenario/module/locale/zh-CN.cfg index a4783e0cb7..8dbccf7c76 100644 --- a/exp_scenario/module/locale/zh-CN.cfg +++ b/exp_scenario/module/locale/zh-CN.cfg @@ -481,6 +481,39 @@ caption-create-footer=加入工作流程 caption-edit-footer=修改工作流程 caption-view-footer=工作流程細節 +[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=因地圖中沒有活躍玩家,所以所有人都已被請離。 diff --git a/exp_scenario/module/locale/zh-TW.cfg b/exp_scenario/module/locale/zh-TW.cfg index a4783e0cb7..8dbccf7c76 100644 --- a/exp_scenario/module/locale/zh-TW.cfg +++ b/exp_scenario/module/locale/zh-TW.cfg @@ -481,6 +481,39 @@ caption-create-footer=加入工作流程 caption-edit-footer=修改工作流程 caption-view-footer=工作流程細節 +[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=因地圖中沒有活躍玩家,所以所有人都已被請離。