From 66722c8291fd9acd03deab89b79c3751a0023c29 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Mon, 9 Nov 2026 23:57:57 +0900 Subject: [PATCH] vlayer surface and quality draft --- exp_scenario/features.ts | 15 +- exp_scenario/module/config/vlayer_items.lua | 149 +---- exp_scenario/module/control/vlayer.lua | 591 ++++++++++---------- exp_scenario/module/gui/player_bonus.lua | 2 +- exp_scenario/module/gui/vlayer.lua | 74 +-- 5 files changed, 342 insertions(+), 489 deletions(-) diff --git a/exp_scenario/features.ts b/exp_scenario/features.ts index da71dee9..4a4a987e 100644 --- a/exp_scenario/features.ts +++ b/exp_scenario/features.ts @@ -399,15 +399,12 @@ export const features: Feature[] = [ fields: [ bool("unlimited_capacity", "Unlimited capacity", "Energy storage without accumulators", false), bool("unlimited_surface_area", "Unlimited area", "Place items without landfill", false), - bool("modded_auto_downgrade", "Downgrade modded items", "Convert modded items to their base game equivalent, the originals can not be recovered", false), - bool("power_on_space", "Power on platforms", "Energy interfaces can be built on space platforms", false), - str("power_on_space_research.name", "Platform research", "The research needed to build energy interfaces on platforms", "research-productivity"), - num("power_on_space_research.level", "Platform research level", "The level of that research", 10, 0), - str("mimic_surface", "Mimic surface", "The surface the day cycle is copied from, empty for a fixed cycle", "nauvis"), - num("interface_limit.energy", "Energy interfaces", "More than one lets disconnected power networks receive power", 1, 0), - num("interface_limit.circuit", "Circuit interfaces", "How many circuit interfaces can exist", 20, 0), - num("interface_limit.storage_input", "Input interfaces", "How many storage input interfaces can exist", 20, 0), - num("interface_limit.storage_output", "Output interfaces", "More than zero allows item teleportation of the allowed items", 1, 0), + bool("global_surface", "Power and item sharing", "Every surface point to the main surface, allowing sharing of power and item", false), + num("landfill_area", "Landfill area", "How much area a landfill provide", 20, 1), + num("interface_limit.energy", "Energy interfaces", "Per surface setting. More than one lets disconnected power networks receive power. 0 means disabled, -1 means unlimited.", 1, -1), + num("interface_limit.circuit", "Circuit interfaces", "Per surface setting. How many circuit interfaces can exist. 0 means disabled, -1 means unlimited.", -1, -1), + num("interface_limit.storage_input", "Input interfaces", "Per surface setting. How many storage input interfaces can exist. 0 means disabled, -1 means unlimited.", -1, -1), + num("interface_limit.storage_output", "Output interfaces", "Per surface setting. More than zero allows item teleportation of the allowed items. 0 means disabled, -1 means unlimited.", 1, -1), ], }, ]; diff --git a/exp_scenario/module/config/vlayer_items.lua b/exp_scenario/module/config/vlayer_items.lua index 75e9449f..25e88975 100644 --- a/exp_scenario/module/config/vlayer_items.lua +++ b/exp_scenario/module/config/vlayer_items.lua @@ -2,146 +2,13 @@ -- @config Vlayer-Items return { ---- @class Vlayer.ItemProperties ---- @field starting_value number ---- @field required_area number ---- @field production number? ---- @field discharge number? ---- @field capacity number? ---- @field surface_area number? ---- @field fuel_value number? ---- @field power boolean? ---- @field modded boolean? Set when the item is registered from a modded equivalent ---- @field base_game_equivalent string? ---- @field multiplier number? - ---- @type table -allowed_items = { -- List of all items allowed in vlayer storage and their properties - --[[ - Allowed properties: - starting_value : The amount of the item placed into the vlayer on game start, ignores area requirements - required_area : When greater than 0 the items properties are not applied unless their is sufficient surplus surface area - production : The energy production of the item in MW, used for solar panels - discharge : The energy discharge of the item in MW, used for accumulators - capacity : The energy capacity of the item in MJ, used for accumulators - surface_area : The surface area provided by the item, used for landfill - ]] - ["solar-panel"] = { - starting_value = 0, - required_area = 9, - production = 0.06, -- MW - }, - ["accumulator"] = { - starting_value = 0, - required_area = 4, - discharge = 0.3, -- MW - capacity = 5, -- MJ - }, - ["landfill"] = { - starting_value = 0, - required_area = 0, - surface_area = 20, -- Tiles - }, - ["wood"] = { - starting_value = 0, - required_area = 0, - surface_area = 0, - fuel_value = 2, -- MJ - power = true, -- turn all wood to power to reduce trash - }, - ["coal"] = { - starting_value = 0, - required_area = 0, - surface_area = 0, - fuel_value = 4, -- MJ - power = false, -- turn all coal to power to reduce trash - }, - ["solid-fuel"] = { - starting_value = 0, - required_area = 0, - surface_area = 0, - fuel_value = 12, -- MJ - power = false, -- turn all solid fuel to power to reduce trash - }, - ["rocket-fuel"] = { - starting_value = 0, - required_area = 0, - surface_area = 0, - fuel_value = 100, -- MJ - power = false, -- turn all rocket fuel to power to reduce trash + --- @type table + allowed_categories = { -- List of categories allowed in vlayer storage + ["production"] = { + "solar-panel" + }, + ["storage"] = { + "accumulator" + }, } -}, - -modded_items = { -- List of all modded items allowed in vlayer storage and their base game equivalent - ["solar-panel-2"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 4, - }, - ["solar-panel-3"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 16, - }, - ["solar-panel-4"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 64, - }, - ["solar-panel-5"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 256, - }, - ["solar-panel-6"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 1024, - }, - ["solar-panel-7"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 4096, - }, - ["solar-panel-8"] = { - starting_value = 0, - base_game_equivalent = "solar-panel", - multiplier = 16384, - }, - ["accumulator-2"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 4, - }, - ["accumulator-3"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 16, - }, - ["accumulator-4"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 64, - }, - ["accumulator-5"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 256, - }, - ["accumulator-6"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 1024, - }, - ["accumulator-7"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 4096, - }, - ["accumulator-8"] = { - starting_value = 0, - base_game_equivalent = "accumulator", - multiplier = 16384, - }, -} } diff --git a/exp_scenario/module/control/vlayer.lua b/exp_scenario/module/control/vlayer.lua index c59d5a03..a12d6999 100644 --- a/exp_scenario/module/control/vlayer.lua +++ b/exp_scenario/module/control/vlayer.lua @@ -10,37 +10,18 @@ local items = require("modules/exp_scenario/config/vlayer_items") local feature, config = Feature.register("vlayer", { unlimited_capacity = false, -- an unlimited energy capacity, accumulators are not required unlimited_surface_area = false, -- an unlimited surface area, landfill is not required - modded_auto_downgrade = false, -- modded items are converted into their base game equivalent, the originals can not be recovered - power_on_space = false, -- energy interfaces can be built on platforms - power_on_space_research = { -- the research level needed to build them there - name = "research-productivity", - level = 10, - }, - mimic_surface = "nauvis", -- the surface the day cycle is copied from, empty to use the fixed cycle below - interface_limit = { -- how many of each interface can exist + global_surface = false, -- every surface point to the main surface, allowing sharing of power and item + landfill_area = 20, -- How much area a landfill provide + interface_limit = { -- how many of each interface per surface can exist energy = 1, -- >1 allows for disconnected power networks to receive power - circuit = 20, - storage_input = 20, + circuit = -1, + storage_input = -1, storage_output = 1, -- >0 allows for item teleportation of the allowed items }, }) -local update_tick_storage = 60 -local update_tick_energy = 10 - ---- The day cycle used when there is no mimic surface, see LuaSurface for details -local surface_settings = { - always_day = false, - solar_power_multiplier = 1, - min_brightness = 0.15, - ticks_per_day = 25200, - daytime = 0, - dusk = 0.25, - evening = 0.45, - morning = 0.55, - dawn = 0.75, - -} +local update_tick_storage = 60, -- How frequent the storage updates, in tick +local update_tick_energy = 10, -- How frequent the energy updates, in tick local floor = math.floor local min = math.min @@ -49,6 +30,14 @@ local mega = 1000000 --- @alias ExpScenario_Vlayer.InterfaceType "energy" | "circuit" | "storage_input" | "storage_output" +--- @class Vlayer.ItemProperties +--- @field required_area number +--- @field production table? Power output in MW with a quality level key +--- @field input_limit table? Power input limit in MW with a quality level key +--- @field output_limit table? Power output limit in MW with a quality level key +--- @field capacity table? Power capacity in MJ with a quality level key +--- @field surface_area number? + --- @class ExpScenario_Vlayer.Interfaces --- @field energy LuaEntity[] --- @field circuit LuaEntity[] @@ -63,14 +52,13 @@ local mega = 1000000 --- @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 +--- @class ExpScenario_Vlayer.ItemCountWithQuality +--- @field quality string --- @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 items table Every stored item, allocated or not +--- @field unallocated table Items waiting for surface area --- @field energy number J --- @class ExpScenario_Vlayer.Statistics @@ -86,11 +74,13 @@ local mega = 1000000 --- @field energy_capacity number J --- @field energy_storage number J ---- @class ExpScenario_Vlayer.Data +--- @class ExpScenario_Vlayer.SurfaceData --- @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.Data +--- @field surfaces table Surface ID key --- @class ExpScenario_Vlayer local Vlayer = { @@ -103,46 +93,66 @@ local Vlayer = { on_nth_tick = {}, } ---- The properties of every allowed item, modded items are derived from their base game equivalent +local quality_level_to_name = {} + +for _, q in pairs(prototypes.quality) do + quality_level_to_name[q.level] = q.name +end + +--- The properties of every allowed item local allowed_items = {} --- @type table -for name, properties in pairs(items.allowed_items) do - allowed_items[name] = properties + +for _, name in pairs(items.allowed_categories["production"]) do + local item_in_category = prototypes.get_entity_filtered{filter = "type", type = name} + + for _, item in pairs(item_in_category) do + local item_name = item.name + + allowed_items[item_name] = { + required_area = item.tile_width * item.tile_height, + -- production_day = item.solar_panel_performance_at_day or 1, + -- production_night = item.solar_panel_performance_at_night or 1, + -- Quality affected items + production = {}, + } + + for l, n in pairs(quality_level_to_name) do + allowed_items[item_name]["production"][l] = item.get_max_energy_production(n) / mega -- W -> MW + end + end end -for name, properties in pairs(items.modded_items) do - local base = items.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, - } + +for _, name in pairs(items.allowed_categories["storage"]) do + local item_in_category = prototypes.get_entity_filtered{filter = "type", type = name} + + for _, item in pairs(item_in_category) do + local item_name = item.name + + allowed_items[item_name] = { + required_area = item.tile_width * item.tile_height, + -- Quality affected items + input_limit = {}, + output_limit = {}, + capacity = {}, + } + + if item.electric_energy_source_prototype then + for l, n in pairs(quality_level_to_name) do + allowed_items[item_name]["input_limit"][l] = item.electric_energy_source_prototype.get_input_flow_limit(n) / mega -- W -> MW + allowed_items[item_name]["output_limit"][l] = item.electric_energy_source_prototype.get_output_flow_limit(n) / mega -- W -> MW + allowed_items[item_name]["capacity"][l] = item.electric_energy_source_prototype.buffer_capacity * prototypes.quality[n].accumulator_capacity_multiplier / mega -- J -> MJ + end + end + end end +allowed_items["landfill"] = { + surface_area = config.landfill_area +} + --- @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(surface_settings), + surfaces = {} } Storage.register(vlayer_data, function(tbl) @@ -181,32 +191,39 @@ end circuit_description = table.concat(circuit_description, "\n") --- Get every stored item keyed by name, allocated or not +--- @param s_index number --- @return table -function Vlayer.get_items() - return vlayer_data.storage.items +function Vlayer.get_items(s_index) + return vlayer_data[s_index].storage.items end --- Get the items waiting for surface area keyed by name +--- @param s_index number --- @return table -function Vlayer.get_unallocated_items() - return vlayer_data.storage.unallocated +function Vlayer.get_unallocated_items(s_index) + return vlayer_data[s_index].storage.unallocated end --- Get the items which contribute to the properties keyed by name +--- @param s_index number --- @return table -function Vlayer.get_allocated_items() +function Vlayer.get_allocated_items(s_index) 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) + local storage = vlayer_data[s_index].storage + local unallocated = storage.unallocated + for name, qnc in pairs(storage.items) do + for quality, count in pairs(qnc) do + allocated[name][quality] = count - (unallocated[name][quality] or 0) + end end return allocated end --- Get the interface entities of each type, do not modify +--- @param s_index number --- @return ExpScenario_Vlayer.Interfaces -function Vlayer.get_interfaces() - return vlayer_data.interfaces +function Vlayer.get_interfaces(s_index) + return vlayer_data[s_index].interfaces end --- Get the signal each statistic is sent as on circuit interfaces @@ -216,20 +233,24 @@ function Vlayer.get_circuits() end --- Surface area left once the unallocated items have taken what they need +--- @param s_index number --- @return number -local function get_remaining_surface_area() - local properties = vlayer_data.properties +local function get_remaining_surface_area(s_index) + local properties = vlayer_data[s_index].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 + for name, qnc in pairs(vlayer_data[s_index].storage.unallocated) do + for _, count in pairs(qnc) do + remaining = remaining - allowed_items[name].required_area * count + end end return remaining end --- Fraction of solar production available right now, from the darkness of a real surface or the config day cycle +--- @param s_index number --- @return number -local function get_production_multiplier() - local surface = vlayer_data.surface +local function get_production_multiplier(s_index) + local surface = game.surfaces[s_index] local multiplier = surface.solar_power_multiplier if surface.always_day then return multiplier @@ -245,48 +266,55 @@ local function get_production_multiplier() local daytime = (game.tick % surface.ticks_per_day) / surface.ticks_per_day surface.daytime = daytime - if daytime <= surface.dusk then + if daytime <= surface.daytime_parameters.dusk then return multiplier - elseif daytime <= surface.evening then - return multiplier * (1 - (daytime - surface.dusk) / (surface.evening - surface.dusk)) - elseif daytime <= surface.morning then + elseif daytime <= surface.daytime_parameters.evening then + return multiplier * (1 - (daytime - surface.daytime_parameters.dusk) / (surface.daytime_parameters.evening - surface.daytime_parameters.dusk)) + elseif daytime <= surface.daytime_parameters.morning then return 0 - elseif daytime <= surface.dawn then - return multiplier * (daytime - surface.morning) / (surface.dawn - surface.morning) + elseif daytime <= surface.daytime_parameters.dawn then + return multiplier * (daytime - surface.daytime_parameters.morning) / (surface.daytime_parameters.dawn - surface.daytime_parameters.morning) end return multiplier end --- Fraction of solar production averaged over a whole day, sunrise and sunset count for half +--- @param s_index number --- @return number -local function get_sustained_multiplier() - local surface = vlayer_data.surface +local function get_sustained_multiplier(s_index) + local surface = game.surfaces[s_index] 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 + local day_duration = 1 - surface.daytime_parameters.dawn + surface.daytime_parameters.dusk + local sunset_duration = surface.daytime_parameters.evening - surface.daytime_parameters.dusk + local sunrise_duration = surface.daytime_parameters.dawn - surface.daytime_parameters.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 s_index number --- @param name string --- @param count number -local function allocate_item(name, count) +--- @param quality_level number +local function allocate_item(s_index, name, count, quality_level) local item = allowed_items[name] - local properties = vlayer_data.properties + local properties = vlayer_data[s_index].properties if item.production then - properties.production = properties.production + item.production * count - properties.total_production = properties.total_production + item.production * count + local prod = item.production[quality_level] * count + properties.production = properties.production + prod + properties.total_production = properties.total_production + prod + end + if item.input_limit then + properties.input_limit = properties.input_limit + item.input_limit[quality_level] * count + end + if item.output_limit then + properties.output_limit = properties.output_limit + item.output_limit[quality_level] * 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 + properties.capacity = properties.capacity + item.capacity[quality_level] * count end if item.surface_area then properties.total_surface_area = properties.total_surface_area + item.surface_area * count @@ -297,73 +325,69 @@ local function allocate_item(name, count) end --- Count the production of items which are stored but not allocated, so the total shows what more surface area would give +--- @param s_index number --- @param name string --- @param count number -local function count_unallocated_production(name, count) +--- @param quality_level number +local function count_unallocated_production(s_index, name, count, quality_level) local item = allowed_items[name] + local properties = vlayer_data[s_index].properties if item.production then - vlayer_data.properties.total_production = vlayer_data.properties.total_production + item.production * count + properties.total_production = properties.total_production + item.production[quality_level] * 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 s_index number --- @param name string --- @param count number -function Vlayer.insert_item(name, count) +--- @param quality_level number +function Vlayer.insert_item(s_index, name, count, quality_level) 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 + local storage = vlayer_data[s_index].storage + storage.items[name][quality_level] = storage.items[name][quality_level] + count if config.unlimited_surface_area or item.required_area == 0 then - allocate_item(name, count) + allocate_item(s_index, name, count, quality_level) return end - local properties = vlayer_data.properties + local properties = vlayer_data[s_index].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) + allocate_item(s_index, name, allocated, quality_level) end local unallocated = count - allocated - storage.unallocated[name] = storage.unallocated[name] + unallocated - count_unallocated_production(name, unallocated) + storage.unallocated[name][quality_level] = storage.unallocated[name][quality_level] + unallocated + count_unallocated_production(s_index, name, unallocated, quality_level) end --- Take an item out, unallocated items go first and landfill can only go while nothing is using its area +--- @param s_index number --- @param name string --- @param count number +--- @param quality_level number --- @return number # The count actually removed -function Vlayer.remove_item(name, count) +function Vlayer.remove_item(s_index, name, wanted, quality_level) local item = assert(allowed_items[name], "Item not allowed in the vlayer: " .. name) - local storage = vlayer_data.storage - local properties = vlayer_data.properties + local storage = vlayer_data[s_index].storage + local properties = vlayer_data[s_index].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) + removed = min(count, storage.unallocated[name][quality_level]) + storage.items[name][quality_level] = storage.items[name][quality_level] - removed + storage.unallocated[name][quality_level] = storage.unallocated[name][quality_level] - removed + count_unallocated_production(s_index, name, -removed, quality_level) count = count - removed if count == 0 then return removed end end - local deallocate = min(count, storage.items[name]) + local deallocate = min(count, storage.items[name][quality_level]) 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)) @@ -372,28 +396,30 @@ function Vlayer.remove_item(name, count) end end - storage.items[name] = storage.items[name] - deallocate - allocate_item(name, -deallocate) + storage.items[name][quality_level] = storage.items[name][quality_level] - deallocate + allocate_item(s_index, name, -deallocate, quality_level) return removed + deallocate end --- Add or take energy, used by other modules which spend the stored energy +--- @param s_index number --- @param energy number J, negative to take --- @return number # J stored afterwards -function Vlayer.energy_changed(energy) - local storage = vlayer_data.storage +function Vlayer.energy_changed(s_index, energy) + local storage = vlayer_data[s_index].storage storage.energy = storage.energy + energy return storage.energy end --- Get the statistics of the layer +--- @param s_index number --- @return ExpScenario_Vlayer.Statistics -function Vlayer.get_statistics() - local properties = vlayer_data.properties +function Vlayer.get_statistics(s_index) + local properties = vlayer_data[s_index].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() + local production_multiplier = get_production_multiplier(s_index) + local sustained_multiplier = get_sustained_multiplier(s_index) + local remaining_surface_area = get_remaining_surface_area(s_index) return { total_surface_area = properties.total_surface_area, @@ -406,16 +432,17 @@ function Vlayer.get_statistics() 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, + energy_storage = vlayer_data[s_index].storage.energy, } end --- Make an entity into an interface which players can not mine or damage +--- @param s_index number --- @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] +local function register_interface(s_index, interface, interface_type, player) + local interfaces = vlayer_data[s_index].interfaces[interface_type] interfaces[#interfaces + 1] = interface interface.last_user = player interface.destructible = false @@ -433,7 +460,7 @@ function Vlayer.create_energy_interface(surface, position, player) end local interface = assert(surface.create_entity{ name = interface_names.energy, position = position, force = "neutral" }) - register_interface(interface, "energy", player) + register_interface(surface.index, interface, "energy", player) interface.operable = false interface.electric_buffer_size = 0 interface.power_production = 0 @@ -449,7 +476,7 @@ end --- @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) + register_interface(surface.index, interface, "circuit", player) interface.combinator_description = circuit_description return interface end @@ -461,7 +488,7 @@ end --- @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) + register_interface(surface.index, 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 @@ -474,7 +501,7 @@ end --- @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) + register_interface(surface.index, interface, "storage_output", player) return interface end @@ -482,6 +509,7 @@ end --- @param interface LuaEntity --- @return ExpScenario_Vlayer.InterfaceType function Vlayer.remove_interface(interface) + local s_index = interface.surface.index 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{ @@ -491,10 +519,10 @@ function Vlayer.remove_interface(interface) allow_creation = true, } elseif interface_type == "energy" then - vlayer_data.storage.energy = vlayer_data.storage.energy + interface.energy + vlayer_data[s_index].storage.energy = vlayer_data[s_index].storage.energy + interface.energy end - table.remove_element(vlayer_data.interfaces[interface_type], interface) + table.remove_element(vlayer_data[s_index].interfaces[interface_type], interface) interface.destroy() return interface_type end @@ -511,34 +539,18 @@ 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 = items.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 } + for s_index, prop in pairs(vlayer_data) do + local interfaces = prop.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 + local count = item.count + if allowed_items[name] then + local quality_level = prototypes.quality[item.quality].level + Vlayer.insert_item(s_index, name, count, quality_level) + inventory.remove{ name = name, count = count, quality = item.quality } end end end @@ -547,19 +559,23 @@ 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" } + for s_index, prop in pairs(vlayer_data) do + local interfaces = prop.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 + local quality = filter.value and filter.value.quality + if name and allowed_items[name] then + local quality_level = prototypes.quality[quality].level + local wanted = min(assert(filter.min) - inventory.get_item_count(name), prop.storage.items[name][quality_level]) + if wanted > 0 and inventory.can_insert{ name = name, count = wanted, quality = quality } then + local removed = Vlayer.remove_item(s_index, name, wanted, quality_level) + if removed > 0 then + inventory.insert{ name = name, count = removed, quality = quality } + end end end end @@ -572,118 +588,136 @@ end 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 + for s_index, prop in pairs(vlayer_data) do + local unallocated_area = 0 + for name, qnc in pairs(prop.storage.unallocated) do + for _, count in pairs(qnc) do + unallocated_area = unallocated_area + allowed_items[name].required_area * count + end + 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) + local properties = prop.properties + local surplus_area = properties.total_surface_area - properties.used_surface_area + for name, qnc in pairs(prop.storage.unallocated) do + for quality, count in pairs(qnc) do + local allocated = min(count, floor(count * surplus_area / unallocated_area)) + if allocated > 0 then + prop.storage.unallocated[name][quality] = count - allocated + allocate_item(s_index, name, allocated, quality) + count_unallocated_production(s_index, name, -allocated, quality) + end + end 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 + for s_index, prop in pairs(vlayer_data) do + local interfaces = prop.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 + 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 - section.set_slot(index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = floor(value) }) - index = index + 1 - end + local section = assert(control.sections[1]) - 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 }) + -- 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, qnc in pairs(prop.storage.items) do + for quality, count in pairs(qnc) do + if count > 0 and prototypes.item[name][quality] then + section.set_slot(index, { value = { type = "item", name = name, quality = quality_level_to_name[quality] }, min = count }) + index = index + 1 + end + end + 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 * (update_tick_energy / 60) - storage.energy = storage.energy + floor(production * get_production_multiplier()) + for s_index, prop in pairs(vlayer_data) do + local storage = prop.storage + local properties = prop.properties + local production = properties.production * mega * (update_tick_energy / 60) + storage.energy = storage.energy + min(properties.input_limit, floor(production * get_production_multiplier(s_index))) - 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 + local interfaces = prop.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.output_limit * 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 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 + local capacity = properties.capacity * mega + if not config.unlimited_capacity and storage.energy > capacity then + storage.energy = capacity + return 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 +local function update_surface(event) + -- global surface will have its data point to the main surface + if not config.global_surface then + return 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(surface_settings) + if not vlayer_data[event.surface_index] then + vlayer_data[event.surface_index] = { + interfaces = { + energy = {}, + circuit = {}, + storage_input = {}, + storage_output = {}, + }, + properties = { + total_surface_area = 0, + used_surface_area = 0, + total_production = 0, + production = 0, + input_limit = 0, + output_limit = 0, + capacity = 0, + }, + storage = { + items = {}, + unallocated = {}, + energy = 0, + } + } end end @@ -706,11 +740,6 @@ Vlayer.on_init = update_surface -- The default surface exists before on_surface_ 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.events[Feature.events.on_config_updated] = function(event) - if event.feature_name == feature.name and event.path == "mimic_surface" then - update_surface() - end -end Vlayer.on_nth_tick[update_tick_storage] = update_storage Vlayer.on_nth_tick[update_tick_energy] = update_energy diff --git a/exp_scenario/module/gui/player_bonus.lua b/exp_scenario/module/gui/player_bonus.lua index ac940a3c..fa73d18a 100644 --- a/exp_scenario/module/gui/player_bonus.lua +++ b/exp_scenario/module/gui/player_bonus.lua @@ -538,7 +538,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(player.physical_surface_index, -energy_to_give) end end end diff --git a/exp_scenario/module/gui/vlayer.lua b/exp_scenario/module/gui/vlayer.lua index 1295ea92..6938580c 100644 --- a/exp_scenario/module/gui/vlayer.lua +++ b/exp_scenario/module/gui/vlayer.lua @@ -32,7 +32,7 @@ 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 +for _, name in ipairs{ "production", "capacity", "sustained", "surface-area" } do stats[#stats + 1] = { name = name, caption = { "exp-gui_vlayer.caption-stat-" .. name }, @@ -140,34 +140,26 @@ Elements.stats_table = Gui.define("vlayer/stats_table") --- 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() + local statistics = Vlayer.get_statistics(game.surfaces["nauvis"].index) + local items = Vlayer.get_items(game.surfaces["nauvis"].index) + local allocated = Vlayer.get_allocated_items(game.surfaces["nauvis"].index) 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), + value = ceil(statistics.energy_production) / max(ceil(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"), }, + sustained = { + value = ceil(statistics.energy_sustained) / max(statistics.energy_total_production, 1), + caption = format_energy(statistics.energy_sustained, "W") .. " / " .. format_energy(statistics.energy_total_production, "W"), + }, + ["surface-area"] = { + value = statistics.total_surface_area / max(statistics.surface_area, 1), + caption = format_number(statistics.remaining_surface_area, false), + }, } end @@ -257,26 +249,6 @@ 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 @@ -367,7 +339,6 @@ Elements.controls = Gui.define("vlayer/controls") 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 @@ -393,7 +364,7 @@ end 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] + local interfaces = Vlayer.get_interfaces(game.surfaces["nauvis"].index)[interface_type] return interface_type, Elements.interface_dropdown.get_selected(elements.interface_dropdown, interfaces) end @@ -402,7 +373,7 @@ end 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]) + Elements.interface_dropdown.refresh(elements.interface_dropdown, Vlayer.get_interfaces(game.surfaces["nauvis"].index)[interface_type]) end --- Refresh the listed interfaces of every online player, used when an interface is built or removed @@ -461,18 +432,7 @@ local function on_area_selected(event) 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 } + local markers = surface.find_entities_filtered{ area = area, name = "steel-chest", force = force } if #markers == 0 then player.print({ "exp-gui_vlayer.message-no-chest" }, Colors.orange_red) return @@ -487,7 +447,7 @@ local function on_area_selected(event) return end - if #Vlayer.get_interfaces()[interface_type] >= config.interface_limit[interface_type] then + if config.interface_limit[interface_type] >= 0 and #Vlayer.get_interfaces(game.surfaces["nauvis"].index)[interface_type] >= config.interface_limit[interface_type] then player.print({ "exp-gui_vlayer.message-limit", type_captions[interface_type] }, Colors.orange_red) return end