--[[-- Control Module - vlayer - Adds a virtual layer to store power to save space. @control vlayer @alias vlayer ]] local ExpUtil = require("modules/exp_util") local Storage = require("modules/exp_util/storage") local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event local config = require("modules.exp_legacy.config.vlayer") --- @dep config.vlayer local mega = 1000000 local vlayer = {} local vlayer_data = { entity_interfaces = { energy = {}, 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 = {}, power_items = {}, energy = 0, unallocated = {}, }, surface = table.deep_copy(config.surface), } Storage.register(vlayer_data, function(tbl) vlayer_data = tbl end) for name, properties in pairs(config.allowed_items) do properties.modded = false if properties.power then vlayer_data.storage.power_items[name] = { value = properties.fuel_value * 1000000, count = 0, } end end -- For all modded items, create a config for them for item_name, properties in pairs(config.modded_items) do local base_properties = config.allowed_items[properties.base_game_equivalent] local m = properties.multiplier config.allowed_items[item_name] = { starting_value = properties.starting_value or 0, required_area = base_properties.required_area or 0, surface_area = (base_properties.surface_area or 0) * m, production = (base_properties.production or 0) * m, capacity = (base_properties.capacity or 0) * m, modded = true, } end --- Get all items in storage, do not modify -- @treturn table a dictionary of all items stored in the vlayer function vlayer.get_items() return vlayer_data.storage.items end --- Get all unallocated items in storage -- @treturn table a dictionary of all unallocated items stored in the vlayer function vlayer.get_unallocated_items() return vlayer_data.storage.unallocated end --- Get all allocated items in storage -- @treturn table a dictionary of all allocated items stored in the vlayer function vlayer.get_allocated_items() local r = {} for k, v in pairs(vlayer_data.storage.items) do r[k] = v if vlayer_data.storage.unallocated[k] then r[k] = r[k] - vlayer_data.storage.unallocated[k] end end return r end --- Get the actual defecit of land local function get_actual_land_defecit() local n = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area for k, v in pairs(vlayer.get_unallocated_items()) do n = n - (config.allowed_items[k].required_area * v) end return n end --- Get interface counts -- @treturn table a dictionary of the vlayer interface counts function vlayer.get_interface_counts() local interfaces = vlayer_data.entity_interfaces return { energy = #interfaces.energy, circuit = #interfaces.circuit, storage_input = #interfaces.storage_input, storage_output = #interfaces.storage_output, } end --- Get interfaces -- @treturn table a dictionary of the vlayer interfaces function vlayer.get_interfaces() local interfaces = vlayer_data.entity_interfaces return { energy = interfaces.energy, circuit = interfaces.circuit, storage_input = interfaces.storage_input, storage_output = interfaces.storage_output, } end --[[ 25,200 / 420 s 昼 210秒 ソーラー効率100% 夕方 84秒 1秒ごとにソーラー発電量が約1.2%ずつ下がり、やがて0%になる 夜 42秒 ソーラー発電量が0%になる 朝方 84秒 1秒ごとにソーラー発電量が約1.2%ずつ上がり、やがて100%になる (surface.dawn) 0.75 18,900 Day 12,600 210s 0.00 0 Noon (surface.dusk) 0.25 6,300 Sunset 5,040 84s (surface.evening) 0.45 11,340 Night 2,520 42s (surface.morning) 0.55 13,860 Sunrise 5,040 84s ]] --- Get the power multiplier based on the surface time local function get_production_multiplier() local mul = vlayer_data.surface.solar_power_multiplier local surface = vlayer_data.surface if surface.always_day then -- Surface is always day, so full production is used return mul end if surface.darkness then -- We are using a real surface, our config does not contain "darkness" local brightness = 1 - surface.darkness if brightness >= surface.min_brightness then return mul * (brightness - surface.min_brightness) / (1 - surface.min_brightness) else return 0 end end -- Caused by using a set config rather than a surface local tick = game.tick % surface.ticks_per_day local daytime = tick / surface.ticks_per_day surface.daytime = daytime if daytime <= surface.dusk then -- Noon to Sunset return mul elseif daytime <= surface.evening then -- Sunset to Night return mul * (1 - ((daytime - surface.dusk) / (surface.evening - surface.dusk))) elseif daytime <= surface.morning then -- Night to Sunrise return 0 elseif daytime <= surface.dawn then -- Sunrise to Morning return mul * ((surface.daytime - surface.morning) / (surface.dawn - surface.morning)) else -- Morning to Noon return mul end end --- Get the sustained power multiplier, this needs improving local function get_sustained_multiplier() local mul = vlayer_data.surface.solar_power_multiplier local surface = vlayer_data.surface if surface.always_day then -- Surface is always day, so full production is used return mul end -- For nauvis vanilla: 210s + (1/2 x (84s + 84s)) local day_duration = 1 - surface.dawn + surface.dusk local sunset_duration = surface.evening - surface.dusk local sunrise_duration = surface.dawn - surface.morning return mul * (day_duration + (0.5 * (sunset_duration + sunrise_duration))) end --- Internal, Allocate items in the vlayer, this will increase the property values of the vlayer such as production and capacity -- Does not increment item storage, so should not be called before insert_item unless during init -- Does not validate area requirements, so checks must be performed before calling this function -- Accepts negative count for deallocating items -- @tparam string item_name The name of the item to allocate -- @tparam number count The count of the item to allocate function vlayer.allocate_item(item_name, count) local item_properties = config.allowed_items[item_name] assert(item_properties, "Item not allowed in vlayer: " .. tostring(item_name)) if item_properties.production then local nc = item_properties.production * count vlayer_data.properties.production = vlayer_data.properties.production + nc vlayer_data.properties.total_production = vlayer_data.properties.total_production + nc end if item_properties.capacity then vlayer_data.properties.capacity = vlayer_data.properties.capacity + item_properties.capacity * count end if item_properties.discharge then vlayer_data.properties.discharge = vlayer_data.properties.discharge + item_properties.discharge * count end if item_properties.surface_area then vlayer_data.properties.total_surface_area = vlayer_data.properties.total_surface_area + item_properties.surface_area * count end if item_properties.required_area and item_properties.required_area > 0 then vlayer_data.properties.used_surface_area = vlayer_data.properties.used_surface_area + item_properties.required_area * count end end function vlayer.unable_alloc_item_pwr_calc(item_name, count) local item_properties = config.allowed_items[item_name] if item_properties.production then vlayer_data.properties.total_production = vlayer_data.properties.total_production + item_properties.production * count end end -- For all allowed items, setup their starting values, default 0 for item_name, properties in pairs(config.allowed_items) do vlayer_data.storage.items[item_name] = properties.starting_value or 0 if properties.required_area and properties.required_area > 0 then vlayer_data.storage.unallocated[item_name] = 0 end vlayer.allocate_item(item_name, properties.starting_value) end --- Insert an item into the vlayer, this will increment its count in storage and allocate it if possible -- @tparam string item_name The name of the item to insert -- @tparam number count The count of the item to insert function vlayer.insert_item(item_name, count) local item_properties = config.allowed_items[item_name] assert(item_properties, "Item not allowed in vlayer: " .. tostring(item_name)) vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] + count if not config.unlimited_surface_area and item_properties.required_area and item_properties.required_area > 0 then -- Calculate how many can be allocated local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area local allocate_count = math.min(count, math.floor(surplus_area / item_properties.required_area)) if allocate_count > 0 then vlayer.allocate_item(item_name, allocate_count) end local unallocated = count - allocate_count vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] + unallocated vlayer.unable_alloc_item_pwr_calc(item_name, unallocated) else vlayer.allocate_item(item_name, count) end end --- Remove an item from the vlayer, this will decrement its count in storage and prioritise unallocated items over deallocation -- Can not always fulfil the remove request for items which provide surface area, therefore returns the amount actually removed -- @tparam string item_name The name of the item to remove -- @tparam number count The count of the item to remove -- @treturn number The count of the item actually removed function vlayer.remove_item(item_name, count) local item_properties = config.allowed_items[item_name] assert(item_properties, "Item not allowed in vlayer: " .. tostring(item_name)) local remove_unallocated = 0 if not config.unlimited_surface_area and item_properties.required_area and item_properties.required_area > 0 then -- Remove from the unallocated storage first remove_unallocated = math.min(count, vlayer_data.storage.unallocated[item_name]) if remove_unallocated > 0 then vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] - count vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] - count end -- Check if any more items need to be removed count = count - remove_unallocated if count == 0 then return remove_unallocated end end -- Calculate the amount to remove based on items in storage local remove_count = math.min(count, vlayer_data.storage.items[item_name]) if item_properties.surface_area and item_properties.surface_area > 0 then -- If the item provides surface area then it has additional limitations local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area remove_count = math.min(remove_count, math.floor(surplus_area / item_properties.surface_area)) if remove_count <= 0 then return remove_unallocated end end -- Remove the item from allocated storage vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] - remove_count vlayer.allocate_item(item_name, -remove_count) return remove_unallocated + remove_count end --- Create a new storage input interface -- @tparam LuaSurface surface The surface to place the interface onto -- @tparam MapPosition position The position on the surface to place the interface at -- @tparam[opt] LuaPlayer player The player to show as the last user of the interface -- @treturn LuaEntity The entity that was created for the interface function vlayer.create_input_interface(surface, position, circuit, last_user) local interface = surface.create_entity{ name = "storage-chest", position = position, force = "neutral" } interface.storage_filter = { name = "deconstruction-planner", quality = "normal" } table.insert(vlayer_data.entity_interfaces.storage_input, interface) if last_user then interface.last_user = last_user end if circuit then for k, _ in pairs({ defines.wire_connector_id.circuit_red, defines.wire_connector_id.circuit_green }) do for _, v in pairs(circuit.get_wire_connector{ wire_connector_id = k, or_create = false }.real_connections) do interface.connect_to{ target = v.target } end end end interface.destructible = false interface.minable = false interface.operable = true return interface end --- Handle all input interfaces, will take their contents and insert it into the vlayer storage local function handle_input_interfaces() for index, interface in pairs(vlayer_data.entity_interfaces.storage_input) do if not interface.valid then vlayer_data.entity_interfaces.storage_input[index] = nil else local inventory = interface.get_inventory(defines.inventory.chest) for _, v in pairs(inventory.get_contents()) do if config.allowed_items[v.name] then --[[ there are no quality support currently. so instead, using the stats projection value for higher quality. ]] local count_deduct local count_add if prototypes.quality[v.quality].level == 0 then count_deduct = v.count count_add = v.count elseif prototypes.quality[v.quality].level > 0 and v.count >= 10 then local batch = math.floor(v.count / 10) count_deduct = batch * 10 count_add = batch * (10 + (prototypes.quality[v.quality].level * 3)) end if count_deduct and count_add then if config.allowed_items[v.name].modded then if config.modded_auto_downgrade then vlayer.insert_item(config.modded_items[v.name].base_game_equivalent, count_add * config.modded_items[v.name].multiplier) else vlayer.insert_item(v.name, count_add) end else if vlayer_data.storage.power_items[v.name] then vlayer_data.storage.power_items[v.name].count = vlayer_data.storage.power_items[v.name].count + count_add else vlayer.insert_item(v.name, count_add) end end inventory.remove{ name = v.name, count = count_deduct, quality = v.quality } end end end end end end --- Create a new storage output interface -- @tparam LuaSurface surface The surface to place the interface onto -- @tparam MapPosition position The position on the surface to place the interface at -- @tparam[opt] LuaPlayer player The player to show as the last user of the interface -- @treturn LuaEntity The entity that was created for the interface function vlayer.create_output_interface(surface, position, circuit, last_user) local interface = surface.create_entity{ name = "requester-chest", position = position, force = "neutral" } table.insert(vlayer_data.entity_interfaces.storage_output, interface) if last_user then interface.last_user = last_user end if circuit then for k, _ in pairs({ defines.wire_connector_id.circuit_red, defines.wire_connector_id.circuit_green }) do for _, v in pairs(circuit.get_wire_connector{ wire_connector_id = k, or_create = false }.real_connections) do interface.connect_to{ target = v.target } end end end interface.destructible = false interface.minable = false interface.operable = true return interface end --- Handle all output interfaces, will take their requests and remove it from the vlayer storage local function handle_output_interfaces() for index, interface in pairs(vlayer_data.entity_interfaces.storage_output) do if not interface.valid then vlayer_data.entity_interfaces.storage_output[index] = nil else local inventory = interface.get_inventory(defines.inventory.chest) local inventory_request_sections = interface.get_logistic_sections().sections for i = 1, #inventory_request_sections do for _, v in pairs(inventory_request_sections[i].filters) do if v.value and config.allowed_items[v.value.name] then local current_amount = inventory.get_item_count(v.value.name) local request_amount = math.min(v.min - current_amount, vlayer_data.storage.items[v.value.name]) if request_amount > 0 and inventory.can_insert{ name = v.value.name, count = request_amount } then local removed_item_count = vlayer.remove_item(v.value.name, request_amount) if removed_item_count > 0 then inventory.insert{ name = v.value.name, count = removed_item_count, quality = "normal" } end end end end end end end end --- Handle the unallocated items because more surface area may have been added local function handle_unallocated() -- unallocated cant happen when its unlimited if config.unlimited_surface_area then return end -- Get the total unallocated area so items can be allocated in equal amounts local unallocated_area = 0 for item_name, count in pairs(vlayer_data.storage.unallocated) do local item_properties = config.allowed_items[item_name] unallocated_area = unallocated_area + item_properties.required_area * count end if unallocated_area == 0 then return end -- Allocate items in an equal distribution local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area for item_name, count in pairs(vlayer_data.storage.unallocated) do local allocation_count = math.min(count, math.floor(count * surplus_area / unallocated_area)) if allocation_count > 0 then vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] - allocation_count vlayer.allocate_item(item_name, allocation_count) vlayer.unable_alloc_item_pwr_calc(item_name, -allocation_count) end end end --- Get the statistics for the vlayer function vlayer.get_statistics() local vdp = vlayer_data.properties.production * mega local gdm = get_production_multiplier() local gsm = get_sustained_multiplier() local gald = get_actual_land_defecit() return { total_surface_area = vlayer_data.properties.total_surface_area, used_surface_area = vlayer_data.properties.used_surface_area, remaining_surface_area = gald, surface_area = vlayer_data.properties.total_surface_area - gald, production_multiplier = gdm, energy_max = vdp, energy_production = vdp * gdm, energy_total_production = vlayer_data.properties.total_production * gsm * mega, energy_sustained = vdp * gsm, energy_capacity = vlayer_data.properties.capacity * mega, energy_storage = vlayer_data.storage.energy, day_time = math.floor(vlayer_data.surface.daytime * vlayer_data.surface.ticks_per_day), day_length = vlayer_data.surface.ticks_per_day, tick = game.tick, } end --- add or reduce vlayer power --- @param power number --- @return number function vlayer.energy_changed(power) local new_value = vlayer_data.storage.energy + power vlayer_data.storage.energy = new_value return new_value end --- Circuit signals used for the statistics function vlayer.get_circuits() return { total_surface_area = "signal-A", used_surface_area = "signal-U", remaining_surface_area = "signal-R", production_multiplier = "signal-M", energy_production = "signal-P", energy_sustained = "signal-S", energy_capacity = "signal-C", energy_storage = "signal-E", day_time = "signal-D", day_length = "signal-L", tick = "signal-T", } end local vlayer_circuits_string = "" for key, value in pairs(vlayer.get_circuits()) do vlayer_circuits_string = vlayer_circuits_string .. string.format("[virtual-signal=%s] = %s\n", value, key:gsub("_", " ")) end --- Create a new circuit interface -- @tparam LuaSurface surface The surface to place the interface onto -- @tparam MapPosition position The position on the surface to place the interface at -- @tparam[opt] LuaPlayer player The player to show as the last user of the interface -- @treturn LuaEntity The entity that was created for the interface function vlayer.create_circuit_interface(surface, position, circuit, last_user) local interface = surface.create_entity{ name = "constant-combinator", position = position, force = "neutral" } table.insert(vlayer_data.entity_interfaces.circuit, interface) if last_user then interface.last_user = last_user end if circuit then for k, _ in pairs({ defines.wire_connector_id.circuit_red, defines.wire_connector_id.circuit_green }) do for _, v in pairs(circuit.get_wire_connector{ wire_connector_id = k, or_create = false }.real_connections) do interface.connect_to{ target = v.target } end end end interface.destructible = false interface.minable = false interface.operable = true return interface end --- Handle all circuit interfaces, updating their signals to match the vlayer statistics local function handle_circuit_interfaces() local stats = vlayer.get_statistics() for index, interface in pairs(vlayer_data.entity_interfaces.circuit) do if not interface.valid then vlayer_data.entity_interfaces.circuit[index] = nil else local circuit_oc = interface.get_or_create_control_behavior() if circuit_oc.sections_count == 0 then circuit_oc.add_section() end circuit_oc = circuit_oc.sections[1] local signal_index = 1 local circuit = vlayer.get_circuits() -- Set the virtual signals based on the vlayer stats for stat_name, signal_name in pairs(circuit) do if stat_name:find("energy") then circuit_oc.set_slot(signal_index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = math.floor(stats[stat_name] / mega) }) elseif stat_name == "production_multiplier" then circuit_oc.set_slot(signal_index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = math.floor(stats[stat_name] * 10000) }) else circuit_oc.set_slot(signal_index, { value = { type = "virtual", name = signal_name, quality = "normal" }, min = math.floor(stats[stat_name]) }) end signal_index = signal_index + 1 end -- Set the item signals based on stored items for item_name, count in pairs(vlayer_data.storage.items) do if prototypes.item[item_name] and count > 0 then local existing_index = circuit_oc.set_slot(signal_index, { value = { type = "item", name = item_name, quality = "normal" }, min = count }) if existing_index then circuit_oc.clear_slot(existing_index) circuit_oc.set_slot(signal_index, { value = { type = "item", name = item_name, quality = "normal" }, min = count }) end signal_index = signal_index + 1 end end -- Clear remaining signals to prevent outdated values being present (caused by count > 0 check) for clear_index = signal_index, #circuit do if not circuit_oc.get_slot(clear_index).signal then break -- There are no more signals to clear end circuit_oc.clear_slot(clear_index) end interface.combinator_description = vlayer_circuits_string end end end --- Create a new energy interface -- @tparam LuaSurface surface The surface to place the interface onto -- @tparam MapPosition position The position on the surface to place the interface at -- @tparam[opt] LuaPlayer player The player to show as the last user of the interface -- @treturn LuaEntity The entity that was created for the interface, or nil if it could not be created function vlayer.create_energy_interface(surface, position, last_user) if not surface.can_place_entity{ name = "electric-energy-interface", position = position } then return nil end local interface = surface.create_entity{ name = "electric-energy-interface", position = position, force = "neutral" } table.insert(vlayer_data.entity_interfaces.energy, interface) if last_user then interface.last_user = last_user end interface.destructible = false interface.minable = false interface.operable = false interface.electric_buffer_size = 0 interface.power_production = 0 interface.power_usage = 0 interface.energy = 0 return interface end --- Handle all energy interfaces as well as the energy storage local function handle_energy_interfaces() -- Add the newly produced power local production = vlayer_data.properties.production * mega * (config.update_tick_energy / 60) vlayer_data.storage.energy = vlayer_data.storage.energy + math.floor(production * get_production_multiplier()) -- Calculate how much power is present in the network, that is storage + all interfaces if #vlayer_data.entity_interfaces.energy > 0 then local available_energy = vlayer_data.storage.energy for index, interface in pairs(vlayer_data.entity_interfaces.energy) do if not interface.valid then vlayer_data.entity_interfaces.energy[index] = nil else available_energy = available_energy + interface.energy end end -- Distribute the energy between all interfaces local discharge_rate = 2 * (production + vlayer_data.properties.discharge * mega) / #vlayer_data.entity_interfaces.energy local fill_to = math.min(discharge_rate, math.floor(available_energy / #vlayer_data.entity_interfaces.energy)) for _, interface in pairs(vlayer_data.entity_interfaces.energy) do interface.electric_buffer_size = math.max(discharge_rate, interface.energy) -- prevent energy loss local delta = fill_to - interface.energy -- positive means storage to interface vlayer_data.storage.energy = vlayer_data.storage.energy - delta interface.energy = interface.energy + delta end end -- Cap the stored energy to the allowed capacity if not config.unlimited_capacity and vlayer_data.storage.energy > vlayer_data.properties.capacity * mega then vlayer_data.storage.energy = vlayer_data.properties.capacity * mega -- burn the trash to produce power elseif vlayer_data.storage.power_items then for k, v in pairs(vlayer_data.storage.power_items) do local max_burning = (vlayer_data.properties.capacity * mega / 2) - vlayer_data.storage.energy if v.count > 0 and max_burning > 0 then local to_burn = math.min(v.count, math.floor(max_burning / v.value)) vlayer_data.storage.energy = vlayer_data.storage.energy + (to_burn * v.value) vlayer_data.storage.power_items[k].count = vlayer_data.storage.power_items[k].count - to_burn end end end end --- Remove the entity interface using the given position -- @tparam LuaSurface surface The surface to search for an interface on -- @tparam MapPosition position The position of the item -- @treturn string The type of interface that was removed, or nil if no interface was found -- @treturn MapPosition The position the interface was at, or nil if no interface was found function vlayer.remove_interface(surface, position) local entities = surface.find_entities_filtered{ name = { "storage-chest", "requester-chest", "constant-combinator", "electric-energy-interface" }, force = "neutral", position = position, radius = 2, limit = 1, } -- Get the details which will be returned if #entities == 0 then return nil, nil, nil end local interface = entities[1] local name = interface.name local sur = interface.surface local pos = interface.position -- Return the type of interface removed and do some clean up if name == "storage-chest" then local inventory = assert(interface.get_inventory(defines.inventory.chest)) ExpUtil.transfer_inventory_to_surface{ inventory = inventory, surface = interface.surface, name = "iron-chest", allow_creation = true, } table.remove_element(vlayer_data.entity_interfaces.storage_input, interface) interface.destroy() return "storage-input", sur, pos elseif name == "requester-chest" then local inventory = assert(interface.get_inventory(defines.inventory.chest)) ExpUtil.transfer_inventory_to_surface{ inventory = inventory, surface = interface.surface, name = "iron-chest", allow_creation = true, } table.remove_element(vlayer_data.entity_interfaces.storage_output, interface) interface.destroy() return "storage-output", sur, pos elseif name == "constant-combinator" then table.remove_element(vlayer_data.entity_interfaces.circuit, interface) interface.destroy() return "circuit", sur, pos elseif name == "electric-energy-interface" then vlayer_data.storage.energy = vlayer_data.storage.energy + interface.energy table.remove_element(vlayer_data.entity_interfaces.energy, interface) interface.destroy() return "energy", sur, pos end end local function on_surface_event() if config.mimic_surface then local surface = game.get_surface(config.mimic_surface) if surface then vlayer_data.surface = surface return end end if not vlayer_data.surface.index then -- Our fake surface data never has an index, we test for this to avoid unneeded copies from the config vlayer_data.surface = table.deep_copy(config.surface) end end --- Handle all storage IO and attempt allocation of unallocated items Event.on_nth_tick(config.update_tick_storage, function(_) handle_input_interfaces() handle_output_interfaces() handle_unallocated() end) --- Handle all energy and circuit updates Event.on_nth_tick(config.update_tick_energy, function(_) handle_circuit_interfaces() handle_energy_interfaces() end) Event.add(defines.events.on_surface_created, on_surface_event) Event.add(defines.events.on_surface_renamed, on_surface_event) Event.add(defines.events.on_surface_imported, on_surface_event) Event.on_init(on_surface_event) -- Default surface always exists, does not trigger on_surface_created return vlayer