local mpp_util = require("mpp.mpp_util") local color = require("mpp.color") local floor, ceil = math.floor, math.ceil local min, max = math.min, math.max local EAST, NORTH, SOUTH, WEST = mpp_util.directions() local render_util = {} ---@class RendererParams ---@field origin MapPosition? ---@field target MapPosition? ---@field x number? ---@field y number? ---@field w number? ---@field h number? ---@field r number? ---@field color Color? ---@field width number? ---@field c Color? ---@field left_top MapPosition? ---@field right_bottom MapPosition? ---this went off the rails ---@param event EventData.on_player_reverse_selected_area ---@return MppRendering function render_util.renderer(event) ---@param t RendererParams local function parametrizer(t, overlay) for k, v in pairs(overlay or {}) do t[k] = v end if t.x and t.y then t.origin = {t.x, t.y} end local target = t.origin or t.left_top --[[@as MapPosition]] local left_top, right_bottom = t.left_top or t.origin or target, t.right_bottom or t.origin if t.origin and t.w or t.h then t.w, t.h = t.w or t.h, t.h or t.w right_bottom = {(target[1] or target.x) + t.w, (target[2] or target.y) + t.h} elseif t.r then local r = t.r local ox, oy = target[1] or target.x, target[2] or target.y left_top = {ox-r, oy-r} right_bottom = {ox+r, oy+r} end local new = { surface = event.surface, players = {event.player_index}, filled = false, radius = t.r or 1, color = t.c or t.color or {1, 1, 1}, left_top = left_top, right_bottom = right_bottom, target = target, -- circles from = left_top, to = right_bottom, -- lines width = 1, } for k, v in pairs(t) do new[k]=v end for _, v in ipairs{"x", "y", "h", "w", "r", "origin"} do new[v]=nil end return new end local meta_renderer_meta = {} meta_renderer_meta.__index = function(self, k) return function(t, t2) return { rendering[k]( parametrizer(t, t2) ) } end end local rendering = setmetatable({}, meta_renderer_meta) ---@class MppRendering local rendering_extension = {} ---Draws an x between left_top and right_bottom ---@param params RendererParams function rendering_extension.draw_cross(params) rendering.draw_line(params) rendering.draw_line({ width = params.width, color = params.color, left_top={ params.right_bottom[1], params.left_top[2] }, right_bottom={ params.left_top[1], params.right_bottom[2], } }) end function rendering_extension.draw_rectangle_dashed(params) rendering.draw_line(params, { from={params.left_top[1], params.left_top[2]}, to={params.right_bottom[1], params.left_top[2]}, dash_offset = 0.0, }) rendering.draw_line(params, { from={params.left_top[1], params.right_bottom[2]}, to={params.right_bottom[1], params.right_bottom[2]}, dash_offset = 0.5, }) rendering.draw_line(params, { from={params.right_bottom[1], params.left_top[2]}, to={params.right_bottom[1], params.right_bottom[2]}, dash_offset = 0.0, }) rendering.draw_line(params, { from={params.left_top[1], params.left_top[2]}, to={params.left_top[1], params.right_bottom[2]}, dash_offset = 0.5, }) end local meta = {} function meta:__index(k) return function(t, t2) if rendering_extension[k] then rendering_extension[k](parametrizer(t, t2)) else rendering[k](parametrizer(t, t2)) end end end return setmetatable({}, meta) end function render_util.draw_clear_rendering(player_data, event) rendering.clear("mining-patch-planner") end ---Draws the properties of a mining drill ---@param player_data PlayerData ---@param event EventData.on_player_reverse_selected_area function render_util.draw_drill_struct(player_data, event) local renderer = render_util.renderer(event) local fx1, fy1 = event.area.left_top.x, event.area.left_top.y fx1, fy1 = floor(fx1), floor(fy1) local x, y = fx1 + 0.5, fy1 + 0.5 local fx2, fy2 = event.area.right_bottom.x, event.area.right_bottom.y fx2, fy2 = ceil(fx2), ceil(fy2) --renderer.draw_cross{x=fx1, y=fy1, w=fx2-fx1, h=fy2-fy1} --renderer.draw_cross{x=fx1, y=fy1, w=2} local drill = mpp_util.miner_struct(player_data.choices.miner_choice) renderer.draw_circle{ x = fx1 + drill.drop_pos.x, y = fy1 + drill.drop_pos.y, c = {0, 1, 0}, r = 0.2, } -- drop pos renderer.draw_cross{ x = fx1 + 0.5 + drill.out_x, y = fy1 + 0.5 + drill.out_y, r = 0.3, } for _, pos in pairs(drill.output_rotated) do renderer.draw_cross{ x = fx1 + 0.5 + pos[1], y = fy1 + 0.5 + pos[2], r = 0.15, width = 3, c={0, 0, 0, .5}, } end renderer.draw_line{ from={x + drill.x, y}, to={x + drill.x, y + 2}, width = 2, color={0.5, 0.5, 0.5} } renderer.draw_line{ from={x + drill.x, y}, to={x + drill.x-.5, y + .65}, width = 2, color={0.5, 0.5, 0.5} } renderer.draw_line{ from={x + drill.x, y}, to={x + drill.x+.5, y + .65}, width = 2, color={0.5, 0.5, 0.5} } -- drill origin renderer.draw_circle{ x = fx1 + 0.5, y = fy1 + 0.5, width = 2, r = 0.4, } renderer.draw_text{ target={fx1 + .5, fy1 + .5}, text = "(0, 0)", alignment = "center", vertical_alignment="middle", scale = 0.6, } -- negative extent - cyan renderer.draw_cross{ x = fx1 +.5 + drill.extent_negative, y = fy1 +.5 + drill.extent_negative, r = 0.25, c = {0, 0.8, 0.8}, } -- positive extent - purple renderer.draw_cross{ x = fx1 +.5 + drill.extent_positive, y = fy1 +.5 + drill.extent_positive, r = 0.25, c = {1, 0, 1}, } renderer.draw_rectangle{ x=fx1, y=fy1, w=drill.size, h=drill.size, width=3, gap_length=0.5, dash_length=0.5, } renderer.draw_rectangle_dashed{ x=fx1 + drill.extent_negative, y=fy1 + drill.extent_negative, w=drill.area, h=drill.area, c={0.5, 0.5, 0.5}, width=5, gap_length=0.5, dash_length=0.5, } if drill.supports_fluids then -- pipe connections renderer.draw_line{ width=4, color = {0, .7, 1}, from={fx1-.3, y+drill.pipe_left-.5}, to={fx1-.3, y+drill.pipe_left+.5}, } renderer.draw_line{ width=4, color = {.7, .7, 0}, from={fx1+drill.size+.3, y+drill.pipe_left-.5}, to={fx1+drill.size+.3, y+drill.pipe_left+.5}, } end renderer.draw_text{ target={fx1 + drill.extent_negative, fy1 + drill.extent_negative-1.5}, text = string.format("skip_outer: %s", drill.skip_outer), alignment = "left", vertical_alignment="middle", } renderer.draw_circle{x = fx1, y = fy1, r = 0.1} --renderer.draw_circle{ x = fx2, y = fy2, r = 0.15, color={1, 0, 0} } end ---Preview the pole coverage ---@param player_data PlayerData ---@param event EventData.on_player_reverse_selected_area function render_util.draw_pole_layout(player_data, event) rendering.clear("mining-patch-planner") local renderer = render_util.renderer(event) local fx1, fy1 = event.area.left_top.x, event.area.left_top.y fx1, fy1 = floor(fx1), floor(fy1) --renderer.draw_cross{x=fx1, y=fy1, w=fx2-fx1, h=fy2-fy1} --renderer.draw_cross{x=fx1, y=fy1, w=2} local drill = mpp_util.miner_struct(player_data.choices.miner_choice) local pole = mpp_util.pole_struct(player_data.choices.pole_choice) local function draw_lane(x, y, count) for i = 0, count-1 do renderer.draw_rectangle{ x = x + drill.size * i + 0.15 , y = y+.15, w = drill.size-.3, h=1-.3, color = i % 2 == 0 and {143/255, 86/255, 59/255} or {223/255, 113/255, 38/255}, width=2, } end ---@diagnostic disable-next-line: param-type-mismatch local coverage = mpp_util.calculate_pole_coverage(player_data.choices, count, 1) renderer.draw_circle{ x=x+.5, y=y-0.5, radius = .25, color={0.7, 0.7, 0.7}, } for i = coverage.pole_start, coverage.full_miner_width, coverage.pole_step do renderer.draw_circle{ x = x + i + .5, y = y - .5, radius = 0.3, width=2, color = {0, 1, 1}, } renderer.draw_line{ x = x + i +.5 - pole.supply_width / 2+.2, y = y - .2, h = 0, w = pole.supply_width-.4, color = {0, 1, 1}, width = 2, } renderer.draw_line{ x = x + i +.5 - pole.supply_width / 2 + .2, y = y - .7, h = .5, w = 0, color = {0, 1, 1}, width = 2, } renderer.draw_line{ x = x + i +.5 + pole.supply_width / 2 - .2, y = y - .7, h = .5, w = 0, color = {0, 1, 1}, width = 2, } end end for i = 1, 10 do draw_lane(fx1, fy1+(i-1)*3, i) end end ---Preview the pole coverage ---@param player_data PlayerData ---@param event EventData.on_player_reverse_selected_area function render_util.draw_pole_layout_compact(player_data, event) rendering.clear("mining-patch-planner") local renderer = render_util.renderer(event) local fx1, fy1 = event.area.left_top.x, event.area.left_top.y fx1, fy1 = floor(fx1), floor(fy1) --renderer.draw_cross{x=fx1, y=fy1, w=fx2-fx1, h=fy2-fy1} --renderer.draw_cross{x=fx1, y=fy1, w=2} local drill = mpp_util.miner_struct(player_data.choices.miner_choice) local pole = mpp_util.pole_struct(player_data.choices.pole_choice) local function draw_lane(x, y, count) for i = 0, count-1 do renderer.draw_rectangle{ x = x + drill.size * i + 0.15 , y = y+.15, w = drill.size-.3, h=1-.3, color = i % 2 == 0 and {143/255, 86/255, 59/255} or {223/255, 113/255, 38/255}, width=2, } end ---@diagnostic disable-next-line: param-type-mismatch local coverage = mpp_util.calculate_pole_spacing(player_data.choices, count, 1) renderer.draw_circle{ x=x+.5, y=y-0.5, radius = .25, color={0.7, 0.7, 0.7}, } for i = coverage.pole_start, coverage.full_miner_width, coverage.pole_step do renderer.draw_circle{ x = x + i + .5, y = y - .5, radius = 0.3, width=2, color = {0, 1, 1}, } renderer.draw_line{ x = x + i +.5 - pole.supply_width / 2+.2, y = y - .2, h = 0, w = pole.supply_width-.4, color = {0, 1, 1}, width = 2, } renderer.draw_line{ x = x + i +.5 - pole.supply_width / 2 + .2, y = y - .7, h = .5, w = 0, color = {0, 1, 1}, width = 2, } renderer.draw_line{ x = x + i +.5 + pole.supply_width / 2 - .2, y = y - .7, h = .5, w = 0, color = {0, 1, 1}, width = 2, } end end for i = 1, 10 do draw_lane(fx1, fy1+(i-1)*3, i) end end ---Displays the labels of built things on the grid ---@param player_data PlayerData ---@param event EventData.on_player_reverse_selected_area function render_util.draw_built_things(player_data, event) rendering.clear("mining-patch-planner") local renderer = render_util.renderer(event) local state = player_data.last_state if not state then return end local C = state.coords local G = state.grid for _, row in pairs(G) do for _, tile in pairs(row) do ---@cast tile GridTile local thing = tile.built_on if thing then -- renderer.draw_circle{ -- x = C.gx + tile.x, y = C.gy + tile.y, -- w = 1, -- color = {0, 0.5, 0, 0.1}, -- r = 0.5, -- } renderer.draw_rectangle{ x = C.ix1 + tile.x -.9, y = C.iy1 + tile.y -.9, w = .8, color = {0, 0.2, 0, 0.1}, } renderer.draw_text{ x = C.gx + tile.x, y = C.gy + tile.y - .3, alignment = "center", vertical_alignment = "top", --vertical_alignment = tile.x % 2 == 1 and "top" or "bottom", text = thing, scale = 0.6, } end end end end ---@param player_data PlayerData ---@param event EventData.on_player_reverse_selected_area function render_util.draw_drill_convolution(player_data, event) rendering.clear("mining-patch-planner") local renderer = render_util.renderer(event) local fx1, fy1 = event.area.left_top.x, event.area.left_top.y fx1, fy1 = floor(fx1), floor(fy1) local state = player_data.last_state if not state then return end local C = state.coords local grid = state.grid for _, row in pairs(grid) do for _, tile in pairs(row) do ---@cast tile GridTile --local c1, c2 = tile.neighbor_counts[m_size], tile.neighbor_counts[m_area] local c1, c2 = tile.neighbors_inner, tile.neighbors_outer if c1 == 0 and c2 == 0 then goto continue end rendering.draw_circle{ surface = state.surface, filled=false, color = {0.3, 0.3, 1}, width=1, radius = 0.5, target={C.gx + tile.x, C.gy + tile.y}, } local stagger = (.5 - (tile.x % 2)) * .25 local col = c1 == 0 and {0.3, 0.3, 0.3} or {0.6, 0.6, 0.6} rendering.draw_text{ surface = state.surface, filled = false, color = col, target={C.gx + tile.x, C.gy + tile.y + stagger}, text = string.format("%i,%i", c1, c2), alignment = "center", vertical_alignment="middle", } ::continue:: end end end ---@param player_data PlayerData ---@param event EventData.on_player_reverse_selected_area function render_util.draw_power_grid(player_data, event) local renderer = render_util.renderer(event) local fx1, fy1 = event.area.left_top.x, event.area.left_top.y fx1, fy1 = floor(fx1), floor(fy1) local state = player_data.last_state if not state then return end ---@cast state SimpleState local C = state.coords local grid = state.grid local connectivity = state.power_connectivity if not connectivity then game.print("No connectivity exists") return end local rendered = {} for set_id, set in pairs(connectivity) do local set_color = color.hue_sequence(set_id) if set_id == 0 then set_color = {1, 1, 1} end for pole, _ in pairs(set) do ---@cast pole GridPole -- if rendered[pole] then goto continue end -- rendered[pole] = true local pole_color = set_color -- if not pole.backtracked and not pole.has_consumers then -- pole_color = {0, 0, 0} -- end renderer.draw_circle{ surface = state.surface, filled = not pole.backtracked, color = pole_color, width = 5, target = {C.gx + pole.grid_x, C.gy + pole.grid_y}, radius = 0.65, } renderer.draw_text{ surface = state.surface, target={C.gx + pole.grid_x, C.gy + pole.grid_y}, text = set_id, alignment = "center", vertical_alignment="middle", scale = 2, } renderer.draw_text{ surface = state.surface, target={C.gx + pole.grid_x, C.gy + pole.grid_y-1.25}, text = ("%i,%i"):format(pole.ix, pole.iy), alignment = "center", vertical_alignment="middle", scale = 2, } ::continue:: end end --]] end ---@param player_data PlayerData ---@param event EventData.on_player_reverse_selected_area function render_util.draw_centricity(player_data, event) local renderer = render_util.renderer(event) local fx1, fy1 = event.area.left_top.x, event.area.left_top.y fx1, fy1 = floor(fx1), floor(fy1) local state = player_data.last_state if not state then return end local C = state.coords local grid = state.grid end ---@param player_data PlayerData ---@param event EventData.on_player_reverse_selected_area function render_util.draw_blueprint_data(player_data, event) local renderer = render_util.renderer(event) local fx1, fy1 = event.area.left_top.x, event.area.left_top.y fx1, fy1 = floor(fx1), floor(fy1) local x, y = fx1 + 0.5, fy1 + 0.5 local id = player_data.choices.blueprint_choice and player_data.choices.blueprint_choice.item_number if not id then return end local bp = player_data.blueprints.cache[id] if not bp then return end renderer.draw_line{x = fx1, y = fy1-1, w = 0, h = 2, width = 7, color={0, 0, 0}} renderer.draw_line{x = fx1-1, y = fy1, w = 2, h = 0, width = 7, color={0, 0, 0}} renderer.draw_rectangle{ x = fx1, y = fy1, w = bp.w, h = bp.h, } for _, ent in pairs(bp.entities) do local struct = mpp_util.entity_struct(ent.name) local clr = {1, 1, 1} if ent.capstone_x and ent.capstone_y then clr = {0, 1, 1} elseif ent.capstone_x then clr = {0, 1, 0} elseif ent.capstone_y then clr = {0, 0, 1} end renderer.draw_circle{ x = fx1 + ent.position.x, y = fy1 + ent.position.y, r = struct.size / 2, color = clr, } end end ---@param player_data PlayerData ---@param event EventData.on_player_reverse_selected_area function render_util.draw_deconstruct_preview(player_data, event) local renderer = render_util.renderer(event) local fx1, fy1 = event.area.left_top.x, event.area.left_top.y fx1, fy1 = floor(fx1), floor(fy1) local state = player_data.last_state if not state then return end local c = state.coords local grid = state.grid local layout = algorithm.layouts[state.layout_choice] if not layout._get_deconstruction_objects then return end local objects = layout:_get_deconstruction_objects(state) local DIR = state.direction_choice for _, t in pairs(objects) do for _, object in ipairs(t) do ---@cast object GhostSpecification local extent_w = object.extent_w or object.radius or 0.5 local extent_h = object.extent_h or extent_w local x1, y1 = object.grid_x-extent_w, object.grid_y-extent_h local x2, y2 = object.grid_x+extent_w, object.grid_y+extent_h x1, y1 = mpp_util.revert_ex(c.gx, c.gy, DIR, x1, y1, c.tw, c.th) x2, y2 = mpp_util.revert_ex(c.gx, c.gy, DIR, x2, y2, c.tw, c.th) rendering.draw_rectangle{ surface=state.surface, players={state.player}, filled=false, width=3, color={1, 0, 0}, left_top={x1+.1,y1+.1}, right_bottom={x2-.1,y2-.1}, } end end end ---@param player_data PlayerData ---@param event EventData.on_player_reverse_selected_area function render_util.draw_can_place_entity(player_data, event) local renderer = render_util.renderer(event) local fx1, fy1 = event.area.left_top.x, event.area.left_top.y fx1, fy1 = floor(fx1), floor(fy1) local x, y = fx1 + 0.5, fy1 + 0.5 local id = player_data.choices.blueprint_choice and player_data.choices.blueprint_choice.item_number if not id then return end local bp = player_data.blueprints.cache[id] if not bp then return end renderer.draw_line{x = fx1, y = fy1-1, w = 0, h = 1, width = 7, color={0.3, 0.3, 0.3}} renderer.draw_line{x = fx1-1, y = fy1, w = 1, h = 0, width = 7, color={0.3, 0.3, 0.3}} local build_check_type = defines.build_check_type local can_forced = { [{false, false}] = 0, [{true, false}] = 1, [{false, true}] = 2, [{true, true}] = 3, } for check_type, i1 in pairs(build_check_type) do for forced_ghost, i2 in pairs(can_forced) do local forced, ghost = forced_ghost[1], forced_ghost[2] local nx = x + (bp.w + 3) * i1 local ny = y + (bp.h + 3) * i2 renderer.draw_rectangle{ x = nx-.5, y = ny-.5, w = bp.w, h = bp.h, color = {0.2, 0.2, .7}, } -- renderer.draw_text{ -- x = nx+(bp.w-1)/2, y = ny-.5, -- text = ("%i,%i"):format(i1, i2), -- alignment = "center", -- vertical_alignment="bottom", -- } renderer.draw_text{ x = nx+(bp.w-1)/2, y = ny-1.5, text = ("%s"):format(check_type), alignment = "center", vertical_alignment="bottom", } renderer.draw_text{ x = nx+(bp.w-1)/2, y = ny-1, text = ("forced: %s"):format(forced), alignment = "center", vertical_alignment="bottom", } renderer.draw_text{ x = nx+(bp.w-1)/2, y = ny-.5, text = ("ghost: %s"):format(ghost), alignment = "center", vertical_alignment="bottom", } for _, ent in pairs(bp.entities) do local ex = nx + ent.position.x - .5 local ey = ny + ent.position.y - .5 local t = { name = ent.name, position = {ex, ey}, direction = ent.direction, build_check_type = defines.build_check_type[check_type], force = game.get_player(event.player_index).force, forced = forced, } if ghost then t.name, t.inner_name = "entity-ghost", t.name end local can_place = event.surface.can_place_entity(t) renderer.draw_circle{x = ex, y = ey, r = 0.5, width = 3, color = can_place and {0.1, .9, .1} or {0.9, .1, .1}, } if can_place then if not ghost then t.name, t.inner_name = "entity-ghost", t.name end event.surface.create_entity(t) end end end end end return render_util