z_mirror/Tapeline
git clone https://git.raiguard.me/z_mirror/Tapeline
828e4a0
revert-47-freeze-fix
1--- @class Tape 2--- @field anchor MapPosition 3--- @field box BoundingBox 4--- @field cursor MapPosition 5--- @field drawing boolean 6--- @field editing boolean 7--- @field editing_box LuaEntity? 8--- @field move_drag_anchor MapPosition? 9--- @field tick_to_die MapTick 10--- @field settings TapeSettings 11--- @field id integer 12--- @field player LuaPlayer 13--- @field player_index uint 14--- @field surface LuaSurface 15--- @field background LuaRenderObject 16--- @field border LuaRenderObject 17--- @field label_north LuaRenderObject 18--- @field label_west LuaRenderObject 19--- @field lines LuaRenderObject[] 20 21local flib_bounding_box =require ( "__flib__.bounding-box" ) 22local flib_position =require ( "__flib__.position" ) 23local flib_table =require ( "__flib__.table" ) 24 25local tool =require ( "scripts.tool" ) 26 27--- @param player LuaPlayer 28--- @param position MapPosition 29--- @param include_temporary_tapes boolean 30local function get_tape_at_position ( player , position , include_temporary_tapes ) 31--- @type Tape? 32local result 33for _ , stored_tape in pairs ( storage . tapes ) do 34if 35( include_temporary_tapes or stored_tape . tick_to_die == math . huge ) 36and stored_tape . player == player 37and stored_tape . surface == player . surface 38and flib_bounding_box . contains_position ( stored_tape . box , position ) 39and ( not result or result . id < stored_tape . id ) 40then 41result =stored_tape 42end 43end 44return result 45end 46 47--- @param player LuaPlayer 48--- @param position MapPosition 49--- @param surface LuaSurface 50--- @return Tape 51local function new_tape ( player , position , surface ) 52local id =storage . next_tape_id 53storage . next_tape_id =id + 1 54local box =flib_bounding_box . from_position ( position , true ) 55local center =flib_bounding_box . center ( box ) 56local width =flib_bounding_box . width ( box ) 57local height =flib_bounding_box . height ( box ) 58 59--- @type Tape 60local self ={ 61anchor =position , 62box =box , 63cursor =position , 64drawing =true , 65editing =false , 66id =id , 67player =player , 68player_index =player . index , 69tick_to_die =math . huge , 70settings =flib_table . deep_copy ( storage . player_settings [ player . index ]), 71surface =surface , 72background =rendering . draw_rectangle ( { 73color =player . mod_settings [ "tl-tape-background-color" ]. value --[[@as Color]] , 74filled =true , 75players ={ player } , 76surface =surface , 77left_top =box . left_top , 78right_bottom =box . right_bottom , 79draw_on_ground =player . mod_settings [ "tl-draw-tape-on-ground" ]. value --[[@as boolean]] , 80} ), 81border =rendering . draw_rectangle ( { 82color =player . mod_settings [ "tl-tape-border-color" ]. value --[[@as Color]] , 83filled =false , 84width =player . mod_settings [ "tl-tape-line-width" ]. value --[[@as double]] , 85players ={ player } , 86surface =surface , 87left_top =box . left_top , 88right_bottom =box . right_bottom , 89draw_on_ground =player . mod_settings [ "tl-draw-tape-on-ground" ]. value --[[@as boolean]] , 90} ), 91label_north =rendering . draw_text ( { 92text =tostring ( width ), 93surface =surface , 94target ={ x =center . x , y =box . left_top . y } , 95color =player . mod_settings [ "tl-tape-label-color" ]. value --[[@as Color]] , 96scale =2 , 97alignment ="center" , 98vertical_alignment ="bottom" , 99visible =width > 1 , 100players ={ player } , 101} ), 102label_west =rendering . draw_text ( { 103text =tostring ( height ), 104surface =surface , 105target ={ x =box . left_top . x , y =center . y } , 106color =player . mod_settings [ "tl-tape-label-color" ]. value --[[@as Color]] , 107scale =2 , 108alignment ="center" , 109vertical_alignment ="bottom" , 110orientation =0.75 , 111visible =height > 1 , 112players ={ player } , 113} ), 114lines ={} , 115} 116storage . tapes [ id ] =self 117tool . set ( self . player ) 118return self 119end 120 121--- @param self Tape 122local function update_tape ( self ) 123local line_width =self . player . mod_settings [ "tl-tape-line-width" ]. value --[[@as double]] 124 125local box =self . box 126local draw_on_ground =self . player . mod_settings [ "tl-draw-tape-on-ground" ]. value --[[@as boolean]] 127self . background . color =self . player . mod_settings [ "tl-tape-background-color" ]. value --[[@as Color]] 128self . background . left_top =box . left_top 129self . background . right_bottom =box . right_bottom 130self . background . draw_on_ground =draw_on_ground 131self . border . color =self . player . mod_settings [ "tl-tape-border-color" ]. value --[[@as Color]] 132self . border . left_top =box . left_top 133self . border . right_bottom =box . right_bottom 134self . border . draw_on_ground =draw_on_ground 135self . border . width =line_width 136local center =flib_bounding_box . center ( box ) 137self . label_north . color =self . player . mod_settings [ "tl-tape-label-color" ]. value --[[@as Color]] 138self . label_north . target ={ x =center . x , y =box . left_top . y } 139local width =flib_bounding_box . width ( box ) 140self . label_north . text =tostring ( width ) 141self . label_north . visible =width > 1 142local height =flib_bounding_box . height ( box ) 143self . label_west . color =self . player . mod_settings [ "tl-tape-label-color" ]. value --[[@as Color]] 144self . label_west . target ={ x =box . left_top . x , y =center . y } 145self . label_west . text =height 146self . label_west . visible =height > 1 147 148local lines =self . lines 149local i =0 150 151--- @param color Color 152--- @param step_x integer 153--- @param step_y integer? 154local function draw_lines ( color , step_x , step_y ) 155step_y =step_y or step_x 156local from_x =self . anchor . x <= center . x and box . left_top . x or box . right_bottom . x 157local from_y =self . anchor . y <= center . y and box . left_top . y or box . right_bottom . y 158local to_x =self . anchor . x > center . x and box . left_top . x or box . right_bottom . x 159local to_y =self . anchor . y > center . y and box . left_top . y or box . right_bottom . y 160 161if flib_bounding_box . width ( self . box ) > 1 then 162step_x =from_x <= to_x and step_x or - step_x 163for x =from_x + step_x , to_x , step_x do 164i =i + 1 165local line =lines [ i ] 166if line then 167line . color =color 168line . width =line_width 169line . from ={ x =x , y =from_y } 170line . to ={ x =x , y =to_y } 171line . visible =true 172line . draw_on_ground =draw_on_ground 173else 174line =rendering . draw_line ( { 175color =color , 176width =line_width , 177from ={ x =x , y =from_y } , 178to ={ x =x , y =to_y } , 179surface =self . surface , 180players ={ self . player } , 181draw_on_ground =draw_on_ground , 182} ) 183lines [ i ] =line 184end 185end 186end 187 188if flib_bounding_box . height ( self . box ) > 1 then 189step_y =from_y <= to_y and step_y or - step_y 190for y =from_y + step_y , to_y , step_y do 191i =i + 1 192local line =lines [ i ] 193if line then 194line . color =color 195line . width =line_width 196line . from ={ x =from_x , y =y } 197line . to ={ x =to_x , y =y } 198line . visible =true 199line . draw_on_ground =draw_on_ground 200else 201line =rendering . draw_line ( { 202color =color , 203width =line_width , 204from ={ x =from_x , y =y } , 205to ={ x =to_x , y =y } , 206surface =self . surface , 207players ={ self . player } , 208draw_on_ground =draw_on_ground , 209} ) 210lines [ i ] =line 211end 212end 213end 214end 215 216draw_lines ( self . player . mod_settings [ "tl-tape-line-color-1" ]. value --[[@as Color]] , 1 ) 217 218if self . settings . mode == "subgrid" then 219draw_lines ( self . player . mod_settings [ "tl-tape-line-color-2" ]. value --[[@as Color]] , self . settings . subgrid_size ) 220draw_lines ( self . player . mod_settings [ "tl-tape-line-color-3" ]. value --[[@as Color]] , self . settings . subgrid_size ^ 2 ) 221draw_lines ( self . player . mod_settings [ "tl-tape-line-color-4" ]. value --[[@as Color]] , self . settings . subgrid_size ^ 3 ) 222else 223draw_lines ( 224self . player . mod_settings [ "tl-tape-line-color-2" ]. value --[[@as Color]] , 225flib_bounding_box . width ( self . box ) / self . settings . splits , 226flib_bounding_box . height ( self . box ) / self . settings . splits 227) 228draw_lines ( 229self . player . mod_settings [ "tl-tape-line-color-3" ]. value --[[@as Color]] , 230flib_bounding_box . width ( self . box ) / 2 , 231flib_bounding_box . height ( self . box ) / 2 232) 233end 234 235for j =i + 1 , # lines do 236lines [ j ]. visible =false 237end 238 239self . border . bring_to_front () 240if self . editing_box then 241self . editing_box . destroy () 242end 243if self . editing then 244self . editing_box =self . surface . create_entity ( { 245name ="tl-highlight-box" , 246position =flib_bounding_box . center ( self . box ), 247bounding_box =flib_bounding_box . resize ( self . box , 0.3 ), 248cursor_box_type ="electricity" , 249render_player_index =self . player_index , 250blink_interval =30 , 251} ) 252end 253 254tool . set ( self . player , self ) 255end 256 257--- @param self Tape 258--- @param position MapPosition 259--- @param constrained boolean 260local function resize_tape ( self , position , constrained ) 261if constrained then 262local delta =flib_position . abs ( flib_position . sub ( self . anchor , position )) 263if delta . x > delta . y then 264position . y =self . anchor . y 265else 266position . x =self . anchor . x 267end 268end 269local box =flib_bounding_box . from_position ( self . anchor , true ) 270box =flib_bounding_box . expand_to_contain_position ( box , position ) 271box =flib_bounding_box . ceil ( box ) 272self . box =box 273self . cursor =position 274update_tape ( self ) 275end 276 277--- @param self Tape 278--- @param position MapPosition 279local function move_tape ( self , position ) 280if not self . move_drag_anchor then 281if flib_bounding_box . contains_position ( self . box , position ) then 282self . move_drag_anchor =position 283end 284return 285end 286local delta =flib_position . sub ( position , self . move_drag_anchor --[[@as MapPosition]] ) 287self . anchor =flib_position . add ( self . anchor , delta ) 288self . box =flib_bounding_box . move ( self . box , delta ) 289self . move_drag_anchor =position 290update_tape ( self ) 291end 292 293--- @param self Tape 294local function destroy_tape ( self ) 295if self . background . valid then 296self . background . destroy () 297end 298if self . border . valid then 299self . border . destroy () 300end 301if self . label_north . valid then 302self . label_north . destroy () 303end 304if self . label_west . valid then 305self . label_west . destroy () 306end 307for _ , line in pairs ( self . lines ) do 308if line . valid then 309line . destroy () 310end 311end 312 313if self . editing_box and self . editing_box . valid then 314self . editing_box . destroy () 315end 316 317if self . editing then 318storage . editing [ self . player_index ] =nil 319elseif self . drawing then 320storage . drawing [ self . player_index ] =nil 321else 322storage . tapes [ self . id ] =nil 323end 324end 325 326--- @param e EventData.on_built_entity 327local function on_built_entity ( e ) 328local entity =e . entity 329if not entity . valid then 330return 331end 332local name =entity . name 333if name == "entity-ghost" then 334name =entity . ghost_name 335end 336if name ~= "tl-dummy-entity" then 337return 338end 339local is_ghost =entity . name == "entity-ghost" 340local position , surface =entity . position , entity . surface 341entity . destroy () 342local last_position =storage . last_position [ e . player_index ] 343if last_position and flib_position . eq ( position , last_position . position ) and surface == last_position . surface then 344return 345end 346storage . last_position [ e . player_index ] ={ position =position , surface =surface } 347 348local should_cancel =last_position and surface ~= last_position . surface 349 350local editing =storage . editing [ e . player_index ] 351if editing then 352if should_cancel then 353destroy_tape ( editing ) 354return 355end 356move_tape ( editing , position ) 357return 358end 359local drawing =storage . drawing [ e . player_index ] 360local player =game . get_player ( e . player_index ) --[[@as LuaPlayer]] 361if drawing then 362if should_cancel then 363destroy_tape ( drawing ) 364return 365end 366resize_tape ( drawing , position , not is_ghost ) 367else 368drawing =new_tape ( player , position , surface ) 369storage . drawing [ e . player_index ] =drawing 370end 371end 372 373--- @param e EventData.on_player_selected_area|EventData.on_player_alt_selected_area 374local function on_player_selected_area ( e ) 375local editing_tape =storage . editing [ e . player_index ] 376if editing_tape then 377editing_tape . move_drag_anchor =nil 378return 379end 380local drawing_tape =storage . drawing [ e . player_index ] 381if not drawing_tape then 382return 383end 384storage . drawing [ e . player_index ] =nil 385drawing_tape . drawing =false 386if e . name == defines . events . on_player_selected_area then 387local time_to_live =drawing_tape . player . mod_settings [ "tl-tape-clear-delay" ]. value --[[@as double]] 388drawing_tape . tick_to_die =game . tick + time_to_live * 60 389end 390storage . tapes [ drawing_tape . id ] =drawing_tape 391end 392 393--- @param e EventData.on_tick 394local function on_tick ( e ) 395for _ , tape_data in pairs ( storage . tapes or {} ) do 396if tape_data . tick_to_die > e . tick then 397goto continue 398end 399 400destroy_tape ( tape_data ) 401 402:: continue :: 403end 404end 405 406--- @param e EventData.CustomInputEvent 407local function on_edit_tape ( e ) 408if storage . editing [ e . player_index ] then 409return 410end 411local player =game . get_player ( e . player_index ) --[[@as LuaPlayer]] 412local tape =get_tape_at_position ( player , e . cursor_position , false ) 413if not tape then 414return 415end 416tape . editing =true 417storage . editing [ e . player_index ] =tape 418storage . tapes [ tape . id ] =nil 419update_tape ( tape ) 420end 421 422--- @param e EventData.CustomInputEvent 423local function on_delete_tape ( e ) 424if storage . drawing [ e . player_index ] then 425return 426end 427 428local player =game . get_player ( e . player_index ) --[[@as LuaPlayer]] 429 430local editing =storage . editing [ e . player_index ] 431if editing and flib_bounding_box . contains_position ( editing . box , e . cursor_position ) then 432destroy_tape ( editing ) 433return 434end 435 436local tape =get_tape_at_position ( player , e . cursor_position , true ) 437if tape then 438destroy_tape ( tape ) 439end 440end 441 442--- @param e EventData.CustomInputEvent 443local function on_clear_cursor ( e ) 444local tape =storage . editing [ e . player_index ] 445if not tape then 446return 447end 448tape . editing =false 449storage . editing [ e . player_index ] =nil 450storage . tapes [ tape . id ] =tape 451update_tape ( tape ) 452end 453 454--- @param e EventData.CustomInputEvent 455local function on_change_mode ( e ) 456local tape =storage . editing [ e . player_index ] 457if tape then 458tape . settings . mode =tape . settings . mode == "subgrid" and "split" or "subgrid" 459update_tape ( tape ) 460return 461end 462local settings =storage . player_settings [ e . player_index ] 463settings . mode =settings . mode == "subgrid" and "split" or "subgrid" 464local player =game . get_player ( e . player_index ) 465--- @cast player -? 466tool . set ( player ) 467end 468 469--- @param e EventData.CustomInputEvent 470local function on_change_divisor ( e ) 471local delta =e . input_name == "tl-increase-divisor" and 1 or - 1 472local tape =storage . editing [ e . player_index ] 473local settings =tape and tape . settings or storage . player_settings [ e . player_index ] 474if not settings then 475return 476end 477if settings . mode == "subgrid" then 478settings . subgrid_size =math . max ( 0 , settings . subgrid_size + delta ) 479else 480settings . splits =math . max ( 2 , settings . splits + delta ) 481end 482if tape then 483update_tape ( tape ) 484else 485local player =game . get_player ( e . player_index ) 486--- @cast player -? 487tool . set ( player ) 488end 489end 490 491--- @param e EventData.on_runtime_mod_setting_changed 492local function on_runtime_mod_setting_changed ( e ) 493if not string . find ( e . setting , "^tl%-" ) then 494return 495end 496local editing =storage . editing [ e . player_index ] 497if editing then 498update_tape ( editing ) 499end 500local drawing =storage . drawing [ e . player_index ] 501if drawing then 502update_tape ( drawing ) 503end 504for _ , tape in pairs ( storage . tapes ) do 505if tape . player . index == e . player_index then 506update_tape ( tape ) 507end 508end 509end 510 511--- @param e EventData.on_player_removed 512local function on_player_removed ( e ) 513local editing =storage . editing [ e . player_index ] 514if editing then 515destroy_tape ( editing ) 516end 517local drawing =storage . drawing [ e . player_index ] 518if drawing then 519destroy_tape ( drawing ) 520end 521for _ , tape in pairs ( storage . tapes ) do 522if tape . player_index == e . player_index then 523destroy_tape ( tape ) 524end 525end 526end 527 528local tape ={} 529 530function tape . on_init () 531--- @type table<uint, Tape> 532storage . editing ={} 533--- @type table<uint, Tape> 534storage . drawing ={} 535--- @type table<uint, {position: MapPosition, surface: LuaSurface}> 536storage . last_position ={} 537storage . next_tape_id =1 538--- @type table<uint, Tape> 539storage . tapes ={} 540end 541 542tape . events ={ 543[ defines . events . on_built_entity ] =on_built_entity , 544[ defines . events . on_player_alt_selected_area ] =on_player_selected_area , 545[ defines . events . on_player_selected_area ] =on_player_selected_area , 546[ defines . events . on_tick ] =on_tick , 547[ "tl-delete-tape" ] =on_delete_tape , 548[ "tl-edit-tape" ] =on_edit_tape , 549[ "tl-linked-clear-cursor" ] =on_clear_cursor , 550[ "tl-next-mode" ] =on_change_mode , 551[ "tl-previous-mode" ] =on_change_mode , 552[ "tl-increase-divisor" ] =on_change_divisor , 553[ "tl-decrease-divisor" ] =on_change_divisor , 554[ defines . events . on_runtime_mod_setting_changed ] =on_runtime_mod_setting_changed , 555[ defines . events . on_player_removed ] =on_player_removed , 556} 557 558return tape