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