345 lines
12 KiB
Lua
345 lines
12 KiB
Lua
local const = require("scripts/constants")
|
|
local utils = require("scripts/utils")
|
|
local highlight_entity = require("scripts/highlight")
|
|
local get_belt_type = utils.get_belt_type
|
|
local connectables = const.connectables
|
|
local lane_cycle = const.lane_cycle
|
|
local side_cycle = const.side_cycle
|
|
local e = defines.events
|
|
|
|
local function setup_globals()
|
|
global.data = {}
|
|
global.in_progress = {}
|
|
global.refresh = {}
|
|
global.belt_lines = {}
|
|
global.clear = global.clear or {}
|
|
global.hover = global.hover or {}
|
|
-- global.colors = const.generate_colors()
|
|
end
|
|
|
|
script.on_init(function()
|
|
setup_globals()
|
|
end)
|
|
|
|
script.on_configuration_changed(function(_)
|
|
rendering.clear("belt-visualizer")
|
|
setup_globals()
|
|
end)
|
|
|
|
local function clear(index)
|
|
global.in_progress[index] = nil
|
|
global.refresh[index] = nil
|
|
local data = global.data[index]
|
|
if not data then return end
|
|
data.checked = nil
|
|
data.belt_line = nil
|
|
if data.ids then
|
|
global.clear[data.ids] = true
|
|
end
|
|
data.ids = nil
|
|
end
|
|
|
|
local function remove_player(event)
|
|
local index = event.player_index
|
|
clear(index)
|
|
global.data[index] = nil
|
|
global.belt_lines[index] = nil
|
|
global.hover[index] = nil
|
|
end
|
|
|
|
script.on_event(e.on_player_left_game, remove_player)
|
|
script.on_event(e.on_player_removed, remove_player)
|
|
|
|
local function highlight(event)
|
|
local index = event.player_index
|
|
clear(index)
|
|
local player = game.get_player(index) --[[@as LuaPlayer]]
|
|
local selected = player.selected
|
|
if not selected then
|
|
if global.hover[index] == "disabled" then
|
|
global.hover[index] = "on"
|
|
end
|
|
return
|
|
end
|
|
local ghost = event.input_name ~= "bv-highlight-belt" or selected.type == "entity-ghost"
|
|
local type = selected.type
|
|
if type == "entity-ghost" then
|
|
if ghost then
|
|
type = selected.ghost_type
|
|
else return end
|
|
end
|
|
if not connectables[type] then return end
|
|
local data = global.data[index] or {}
|
|
global.data[index] = data
|
|
local unit_number = selected.unit_number --[[@as number]]
|
|
local filter = not player.is_cursor_empty() and player.cursor_stack.valid_for_read and player.cursor_stack.name
|
|
if data.filter == filter and data.origin.valid and data.origin.unit_number == unit_number then
|
|
data.cycle = data.cycle % 3 + 1
|
|
else
|
|
data.cycle = 1
|
|
end
|
|
data.index = index
|
|
data.ghost = ghost
|
|
data.filter = filter
|
|
data.origin = selected
|
|
data.drawn_offsets = {}
|
|
data.drawn_arcs = {}
|
|
data.checked = {[unit_number] = utils.empty_check(type)}
|
|
data.belt_line = {}
|
|
data.head = selected
|
|
data.tail = selected
|
|
data.next_entities = {}
|
|
data.next_index = 1
|
|
data.next_len = 2
|
|
local lanes = lane_cycle[data.cycle]
|
|
for path = 1, 2 do
|
|
data.next_entities[path] = {entity = selected, lanes = lanes, path = path}
|
|
local sides = type == "splitter" and side_cycle.both
|
|
for lane in pairs(lanes) do
|
|
utils.check_entity(data, unit_number, lane, path, sides)
|
|
end
|
|
end
|
|
data.ids = {}
|
|
data.container_passthrough = settings.get_player_settings(player)["bv-container-passthrough"].value
|
|
global.in_progress[index] = true
|
|
end
|
|
|
|
local function refresh(data)
|
|
clear(data.index)
|
|
local entity = data.origin
|
|
if not entity.valid then return end
|
|
data.next_entities = {}
|
|
data.next_index = 1
|
|
data.next_len = 2
|
|
for i = 1, 2 do
|
|
data.next_entities[i] = {entity = entity, lanes = lane_cycle[data.cycle], path = i}
|
|
end
|
|
data.drawn_offsets = {}
|
|
data.drawn_arcs = {}
|
|
data.checked = {[entity.unit_number] = utils.empty_check(entity.type)}
|
|
data.belt_line = {}
|
|
data.head = entity
|
|
data.tail = entity
|
|
data.ids = {}
|
|
global.in_progress[data.index] = true
|
|
end
|
|
|
|
local function keybind(event)
|
|
local index = event.player_index
|
|
if global.hover[index] == "on" then
|
|
global.hover[index] = "disabled"
|
|
else
|
|
highlight(event)
|
|
end
|
|
end
|
|
|
|
script.on_event("bv-highlight-belt", keybind)
|
|
script.on_event("bv-highlight-ghost", keybind)
|
|
|
|
script.on_event(e.on_selected_entity_changed, function(event)
|
|
local player = game.get_player(event.player_index) --[[@as LuaPlayer]]
|
|
local data = global.data[event.player_index]
|
|
local selected = player.selected --[[@as LuaEntity]]
|
|
local is_connectable = selected and connectables[get_belt_type(selected)]
|
|
local belt_line = data and data.belt_line
|
|
if is_connectable and belt_line and belt_line[selected.unit_number] then
|
|
data.origin = player.selected
|
|
return
|
|
end
|
|
if global.hover[event.player_index] ~= "on" then return end
|
|
highlight(event)
|
|
end)
|
|
|
|
local function toggle_hover(event)
|
|
local index = event.player_index
|
|
local player = game.get_player(index) --[[@as LuaPlayer]]
|
|
local toggle = not player.is_shortcut_toggled("bv-toggle-hover")
|
|
global.hover[index] = toggle and "on" or "off"
|
|
player.set_shortcut_toggled("bv-toggle-hover", toggle)
|
|
end
|
|
|
|
script.on_event("bv-toggle-hover", toggle_hover)
|
|
script.on_event(e.on_lua_shortcut, function(event)
|
|
if event.prototype_name ~= "bv-toggle-hover" then return end
|
|
toggle_hover(event)
|
|
end)
|
|
|
|
local function highlightable(data, entity)
|
|
local checked = data.checked
|
|
if not checked then return false end
|
|
if checked[entity.unit_number] then return true end
|
|
for _, input in pairs(entity.belt_neighbours.inputs) do
|
|
if checked[input.unit_number] then return true end
|
|
end
|
|
for _, output in pairs(entity.belt_neighbours.outputs) do
|
|
if checked[output.unit_number] then return true end
|
|
end
|
|
if entity.type == "underground-belt" then
|
|
local neighbour = entity.neighbours
|
|
if neighbour and checked[neighbour.unit_number] then return true end
|
|
elseif entity.type == "linked-belt" then
|
|
local neighbour = entity.linked_belt_neighbour
|
|
if neighbour and checked[neighbour.unit_number] then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function on_entity_modified(event)
|
|
local entity = event.entity or event.created_entity or event.destination
|
|
for _, data in pairs(global.data) do
|
|
if highlightable(data, entity) then
|
|
if not global.refresh[data.index] then
|
|
global.refresh[data.index] = event.tick + 60
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local filter = {{filter = "transport-belt-connectable"}}
|
|
|
|
script.on_event(e.on_built_entity, on_entity_modified, filter)
|
|
script.on_event(e.on_robot_built_entity, on_entity_modified, filter)
|
|
script.on_event(e.on_entity_cloned, on_entity_modified, filter)
|
|
script.on_event(e.script_raised_built, on_entity_modified, filter)
|
|
script.on_event(e.script_raised_revive, on_entity_modified, filter)
|
|
script.on_event(e.on_player_mined_entity, on_entity_modified, filter)
|
|
script.on_event(e.on_robot_mined_entity, on_entity_modified, filter)
|
|
script.on_event(e.script_raised_destroy, on_entity_modified, filter)
|
|
script.on_event(e.on_entity_died, on_entity_modified, filter)
|
|
|
|
script.on_event(e.on_player_rotated_entity, function(event)
|
|
if not connectables[event.entity.type] then return end
|
|
on_entity_modified(event)
|
|
local entity = event.entity
|
|
local neighbours = entity.type == "underground-belt" and entity.neighbours or entity.type == "linked-belt" and entity.linked_belt_neighbour
|
|
if neighbours then on_entity_modified{entity = neighbours, tick = event.tick} end
|
|
end)
|
|
|
|
---@param t table
|
|
---@param case any
|
|
local function switch(t, case, ...)
|
|
local fun = t[case]
|
|
if fun then
|
|
return fun(...)
|
|
end
|
|
end
|
|
|
|
---@type table<string, fun(entity: LuaEntity): LuaEntity?>
|
|
local next_switch = {
|
|
["transport-belt"] = function(entity)
|
|
return entity.belt_neighbours.outputs[1]
|
|
end,
|
|
["underground-belt"] = function(entity)
|
|
if entity.belt_to_ground_type == "input" then
|
|
return entity.neighbours
|
|
else
|
|
return entity.belt_neighbours.outputs[1]
|
|
end
|
|
end,
|
|
["linked-belt"] = function(entity)
|
|
if entity.linked_belt_type == "input" then
|
|
return entity.linked_belt_neighbour
|
|
else
|
|
return entity.belt_neighbours.outputs[1]
|
|
end
|
|
end,
|
|
}
|
|
|
|
---@type table<string, fun(entity: LuaEntity): LuaEntity?>
|
|
local previous_switch = {
|
|
["transport-belt"] = function(entity)
|
|
return entity.belt_neighbours.inputs[1]
|
|
end,
|
|
["underground-belt"] = function(entity)
|
|
if entity.belt_to_ground_type == "output" then
|
|
return entity.neighbours
|
|
else
|
|
return entity.belt_neighbours.inputs[1]
|
|
end
|
|
end,
|
|
["linked-belt"] = function(entity)
|
|
if entity.linked_belt_type == "output" then
|
|
return entity.linked_belt_neighbour
|
|
else
|
|
return entity.belt_neighbours.inputs[1]
|
|
end
|
|
end,
|
|
}
|
|
|
|
local function walk_belt(belt, belt_switch, belt_line, max_highlights, ghost, include)
|
|
local c = 0
|
|
while c < max_highlights do
|
|
if not belt then return end
|
|
if belt.type == "entity-ghost" and not ghost then return end
|
|
local belt_type = get_belt_type(belt)
|
|
if belt_type == "splitter" then return end
|
|
if include then
|
|
belt_line[belt.unit_number] = true
|
|
end
|
|
local limit = 1
|
|
if (belt_type == "underground-belt" and belt.belt_to_ground_type == "output")
|
|
or (belt_type == "linked-belt" and belt.linked_belt_type == "output") then
|
|
limit = 0
|
|
end
|
|
if #belt.belt_neighbours.inputs > limit then return end
|
|
belt_line[belt.unit_number] = true
|
|
belt = switch(belt_switch, belt_type, belt)
|
|
c = c + 1
|
|
end
|
|
return belt
|
|
end
|
|
|
|
local function cache_belt_line(data, max_highlights)
|
|
local head, tail = data.head, data.tail
|
|
local belt_line = data.belt_line
|
|
local ghost = data.ghost
|
|
if head and head.valid then
|
|
head = switch(next_switch, get_belt_type(head), head)
|
|
data.head = walk_belt(head, next_switch, belt_line, max_highlights, ghost)
|
|
end
|
|
if tail and tail.valid then
|
|
data.tail = walk_belt(tail, previous_switch, belt_line, max_highlights, ghost, true)
|
|
end
|
|
end
|
|
|
|
script.on_event(e.on_tick, function(event)
|
|
for index, tick in pairs(global.refresh) do
|
|
if tick == event.tick then
|
|
refresh(global.data[index])
|
|
end
|
|
end
|
|
local player_count = table_size(global.in_progress)
|
|
local highlight_maximum = settings.global["bv-highlight-maximum"].value
|
|
local max_highlights = highlight_maximum * 8 / table_size(global.clear)
|
|
for ids in pairs(global.clear) do
|
|
local c = 0
|
|
for id in pairs(ids) do
|
|
if rendering.is_valid(id) then
|
|
rendering.destroy(id)
|
|
end
|
|
ids[id] = nil
|
|
c = c + 1
|
|
if c > max_highlights then break end
|
|
end
|
|
if not next(ids) then global.clear[ids] = nil end
|
|
end
|
|
max_highlights = highlight_maximum / player_count
|
|
for index in pairs(global.in_progress) do
|
|
local data = global.data[index]
|
|
cache_belt_line(data, max_highlights)
|
|
local c = 0
|
|
while c < max_highlights do
|
|
local next_index = data.next_index
|
|
local next_data = data.next_entities[next_index]
|
|
if not next_data then break end
|
|
local entity = next_data.entity
|
|
if entity.valid then
|
|
highlight_entity[get_belt_type(entity)](data, next_data.entity, next_data.lanes, next_data.path)
|
|
c = c + 1
|
|
end
|
|
data.next_entities[next_index] = nil
|
|
data.next_index = next_index + 1
|
|
end
|
|
if not data.next_entities[data.next_index] then global.in_progress[data.index] = nil end
|
|
end
|
|
end) |