vlayer surface and quality draft

This commit is contained in:
phidias authored and GitHub committed 2026-11-09 23:57:57 +09:00
1 parent ce29218f14
commit 66722c8291
5 files changed
+342 -489

No files matched your search

+6 -9
View File
@@ -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),
],
},
];
+8 -141
View File
@@ -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<string, Vlayer.ItemProperties>
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<string, string>
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,
},
}
}
+310 -281
View File
@@ -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<number, number>? Power output in MW with a quality level key
--- @field input_limit table<number, number>? Power input limit in MW with a quality level key
--- @field output_limit table<number, number>? Power output limit in MW with a quality level key
--- @field capacity table<number, number>? 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<string, number> Every stored item, allocated or not
--- @field unallocated table<string, number> Items waiting for surface area
--- @field power_items table<string, ExpScenario_Vlayer.PowerItem> Fuel burned when the stored energy is low
--- @field items table<string, ExpScenario_Vlayer.ItemCountWithQuality> Every stored item, allocated or not
--- @field unallocated table<string, ExpScenario_Vlayer.ItemCountWithQuality> 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<number, ExpScenario_Vlayer.SurfaceData> 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<string, Vlayer.ItemProperties>
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<string, number>
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<string, number>
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<string, number>
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
+1 -1
View File
@@ -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
+17 -57
View File
@@ -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<string, ExpGui_Vlayer.elements.stat_bar.display_data>
function Elements.stats_table.calculate_display_data()
local statistics = Vlayer.get_statistics()
local items = Vlayer.get_items()
local allocated = Vlayer.get_allocated_items()
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<LuaGuiElement, ExpGui_Vlayer.elements.control_button.element_data>
--- @overload fun(parent: LuaGuiElement, controls: LuaGuiElement): LuaGuiElement
Elements.refresh_button = Gui.define("vlayer/refresh_button")
:draw{
type = "button",
caption = { "exp-gui_vlayer.caption-refresh" },
}
:style{
width = 200,
}
:element_data{
controls = Gui.from_argument(1),
}
:on_click(function(def, _, refresh_button)
--- @cast def ExpGui_Vlayer.elements.refresh_button
Elements.controls.refresh_interfaces(def.data[refresh_button].controls)
end) --[[@as any]]
--- Button which opens the map at the selected interface
--- @class ExpGui_Vlayer.elements.view_button: ExpElement
--- @field data table<LuaGuiElement, ExpGui_Vlayer.elements.control_button.element_data>
@@ -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