z_mirror/PipeVisualizer
git clone https://git.raiguard.me/z_mirror/PipeVisualizer
73a26f6
main
1local flib_bounding_box =require ( "__flib__.bounding-box" ) 2local flib_math =require ( "__flib__.math" ) 3local flib_position =require ( "__flib__.position" ) 4local flib_table =require ( "__flib__.table" ) 5 6local iterator =require ( "scripts.iterator" ) 7 8-- In the source code, 200 is defined as the maximum viewable distance, but in reality it's around 220 9-- Map editor is 3x that, but we will ignore that for now 10-- Add five for a comfortable margin 11local max_overlay_size =220 + 5 12 13--- @alias UnitNumber uint 14--- @alias EntityChunkPositionLookup table<int, LuaEntity[]?> 15 16--- @class Overlay 17--- @field background RenderObjectID 18--- @field entities_x table<int, LuaEntity[]> 19--- @field entities_y table<int, LuaEntity[]> 20--- @field dimensions DisplayResolution 21--- @field last_position MapPosition? 22--- @field player LuaPlayer 23 24--- @param self Overlay 25--- @param position MapPosition 26local function get_areas ( self , position ) 27local dimensions =self . dimensions 28local last_position =self . last_position 29if not last_position then 30return { flib_bounding_box . from_dimensions ( position , dimensions . width , dimensions . height ) } 31end 32local position_x =position . x 33local position_y =position . y 34local radius_x =dimensions . width / 2 35local radius_y =dimensions . height / 2 36 37local delta =flib_position . sub ( position , last_position ) 38--- @type BoundingBox[] 39local areas ={} 40 41if delta . x < 0 then 42areas [ # areas + 1 ] ={ 43left_top ={ x =position_x - radius_x , y =position_y - radius_y } , 44right_bottom ={ x =last_position . x - radius_x , y =position_y + radius_y } , 45} 46elseif delta . x > 0 then 47areas [ # areas + 1 ] ={ 48left_top ={ x =last_position . x + radius_x , y =position_y - radius_y } , 49right_bottom ={ x =position_x + radius_x , y =position_y + radius_y } , 50} 51end 52 53if delta . y < 0 then 54areas [ # areas + 1 ] ={ 55left_top ={ x =position_x - radius_x - math . min ( delta . x , 0 ), y =position_y - radius_y } , 56right_bottom ={ x =position_x + radius_x - math . max ( delta . x , 0 ), y =last_position . y - radius_y } , 57} 58elseif delta . y > 0 then 59areas [ # areas + 1 ] ={ 60left_top ={ x =position_x - radius_x - math . min ( delta . x , 0 ), y =last_position . y + radius_y } , 61right_bottom ={ x =position_x + radius_x - math . max ( delta . x , 0 ), y =position_y + radius_y } , 62} 63end 64 65return areas 66end 67 68--- @param player LuaPlayer 69--- @return DisplayResolution 70local function get_dimensions ( player ) 71local resolution =player . display_resolution 72local divisor =math . max ( resolution . width , resolution . height ) / max_overlay_size 73resolution . width =flib_math . ceiled ( resolution . width / divisor , 64 ) + 32 74resolution . height =flib_math . ceiled ( resolution . height / divisor , 64 ) + 32 75return resolution 76end 77 78--- @param self Overlay 79--- @param area BoundingBox 80local function visualize_area ( self , area ) 81local entities =self . player . surface . find_entities_filtered ( { 82area =area , 83force =self . player . force , 84type ={ 85"assembling-machine" , 86"boiler" , 87"fluid-turret" , 88"furnace" , 89"generator" , 90"infinity-pipe" , 91"inserter" , 92"lab" , 93"loader" , 94"loader-1x1" , 95"mining-drill" , 96"offshore-pump" , 97"pipe" , 98"pipe-to-ground" , 99"pump" , 100"reactor" , 101"rocket-silo" , 102"storage-tank" , 103} , 104} ) 105for _ , entity in pairs ( entities ) do 106if iterator . request ( entity , self . player . index , true ) then 107local chunk_position =flib_position . to_chunk ( entity . position ) 108local x_lookup =flib_table . get_or_insert ( self . entities_x , chunk_position . x , {} ) 109x_lookup [ # x_lookup + 1 ] =entity 110local y_lookup =flib_table . get_or_insert ( self . entities_y , chunk_position . y , {} ) 111y_lookup [ # y_lookup + 1 ] =entity 112end 113end 114end 115 116--- @param self Overlay 117--- @param lookup EntityChunkPositionLookup 118--- @param min int 119--- @param max int 120local function remove_out_of_bounds ( self , lookup , min , max ) 121for pos , entities in pairs ( lookup ) do 122if pos < min or pos >= max then 123for _ , entity in pairs ( entities ) do 124iterator . clear ( self . player . index , entity ) 125end 126lookup [ pos ] =nil 127end 128end 129end 130 131--- @param self Overlay 132local function update_overlay ( self ) 133local pos =self . player . position 134local position ={ 135x =flib_math . floored ( pos . x , 32 ) + 16 , 136y =flib_math . floored ( pos . y , 32 ) + 16 , 137} 138if self . last_position and flib_position . eq ( position , self . last_position ) then 139return 140end 141if not rendering . is_valid ( self . background ) then 142self . background =rendering . draw_sprite ( { 143sprite ="pv-overlay-box" , 144tint ={ 145a =self . player . mod_settings [ "pv-overlay-opacity" ]. value --[[@as double]] , 146} , 147render_layer ="191" , 148target =self . player . position , 149surface =self . player . surface , 150players ={ self . player } , 151} ) 152end 153rendering . set_target ( self . background , position ) 154rendering . set_x_scale ( self . background , self . dimensions . width ) 155rendering . set_y_scale ( self . background , self . dimensions . height ) 156 157local areas =get_areas ( self , position ) 158 159self . last_position =position 160 161for _ , area in pairs ( areas ) do 162visualize_area ( self , area ) 163end 164 165remove_out_of_bounds ( 166self , 167self . entities_x , 168math . floor (( position . x - ( self . dimensions . width / 2 )) / 32 ), 169math . ceil (( position . x + ( self . dimensions . width / 2 )) / 32 ) 170) 171remove_out_of_bounds ( 172self , 173self . entities_y , 174math . floor (( position . y - ( self . dimensions . height / 2 )) / 32 ), 175math . ceil (( position . y + ( self . dimensions . height / 2 )) / 32 ) 176) 177end 178 179--- @param player LuaPlayer 180--- @return Overlay 181local function create_overlay ( player ) 182iterator . clear_all ( player . index ) 183local opacity =player . mod_settings [ "pv-overlay-opacity" ]. value --[[@as double]] 184local background =rendering . draw_sprite ( { 185sprite ="pv-overlay-box" , 186tint ={ a =opacity } , 187render_layer ="191" , 188target =player . position , 189surface =player . surface , 190players ={ player } , 191} ) 192--- @type Overlay 193local self ={ 194background =background , 195entities_x ={} , 196entities_y ={} , 197dimensions =get_dimensions ( player ), 198player =player , 199} 200global . overlay [ player . index ] =self 201update_overlay ( self ) 202return self 203end 204 205--- @param self Overlay 206local function destroy_overlay ( self ) 207rendering . destroy ( self . background ) 208iterator . clear_all ( self . player . index ) 209global . overlay [ self . player . index ] =nil 210end 211 212--- @param e EventData.CustomInputEvent|EventData.on_lua_shortcut 213local function on_toggle_overlay ( e ) 214if ( e . input_name or e . prototype_name ) ~= "pv-toggle-overlay" then 215return 216end 217local player =game . get_player ( e . player_index ) 218if not player then 219return 220end 221local entity =player . selected 222if entity and entity . prototype . belt_speed and script . active_mods [ "belt-visualizer" ] then 223return 224end 225local cursor_stack =player . cursor_stack 226if cursor_stack and cursor_stack . valid_for_read and ( cursor_stack . is_blueprint_book or cursor_stack . is_blueprint ) then 227return 228end 229local self =global . overlay [ e . player_index ] 230if self then 231destroy_overlay ( self ) 232else 233create_overlay ( player ) 234end 235player . set_shortcut_toggled ( "pv-toggle-overlay" , global . overlay [ e . player_index ] ~= nil ) 236end 237 238--- @param e EventData.on_player_changed_position 239local function on_player_changed_position ( e ) 240local self =global . overlay [ e . player_index ] 241if self then 242update_overlay ( self ) 243end 244end 245 246--- @param e EventData.on_player_display_resolution_changed 247local function on_player_display_resolution_changed ( e ) 248if not global . overlay then 249return 250end 251local self =global . overlay [ e . player_index ] 252if not self then 253return 254end 255destroy_overlay ( self ) 256create_overlay ( self . player ) 257end 258 259--- @param e EventData.CustomInputEvent 260local function on_color_by_system_pressed ( e ) 261local self =global . overlay [ e . player_index ] 262if not self then 263return 264end 265destroy_overlay ( self ) 266create_overlay ( game . get_player ( e . player_index ) --[[@as LuaPlayer]] ) 267end 268 269local function reset () 270--- @type table<uint, Overlay> 271global . overlay ={} 272for _ , player in pairs ( game . players ) do 273player . set_shortcut_toggled ( "pv-toggle-overlay" , false ) 274end 275end 276 277local overlay ={} 278 279overlay . on_init =reset 280overlay . on_configuration_changed =reset 281 282overlay . events ={ 283[ defines . events . on_lua_shortcut ] =on_toggle_overlay , 284[ defines . events . on_player_changed_position ] =on_player_changed_position , 285[ defines . events . on_player_display_resolution_changed ] =on_player_display_resolution_changed , 286[ "pv-toggle-overlay" ] =on_toggle_overlay , 287[ "pv-color-by-fluid-system" ] =on_color_by_system_pressed , 288} 289 290return overlay