z_mirror/EditorExtensions
git clone https://git.raiguard.me/z_mirror/EditorExtensions
891dd4a
1.14
1local format =require ( "__flib__/format" ) 2local gui =require ( "__flib__/gui-lite" ) 3local math =require ( "__flib__/math" ) 4local table =require ( "__flib__/table" ) 5 6local shared_constants =require ( "__EditorExtensions__/shared-constants" ) 7 8local util =require ( "__EditorExtensions__/scripts/util" ) 9 10local crafter_snapping_types ={ 11[ "assembling-machine" ] =true , 12[ "furnace" ] =true , 13[ "rocket-silo" ] =true , 14} 15 16--- @alias InfinityPipeMode 17--- | "at-least", 18--- | "at-most", 19--- | "exactly", 20--- | "add", 21--- | "remove", 22 23--- @type table<string, uint> 24local defines_amount_type ={ 25percent =1 , 26units =2 , 27} 28 29--- @param entity LuaEntity 30--- @param amount_type uint? 31local function store_amount_type ( entity , amount_type ) 32global . infinity_pipe_amount_type [ entity . unit_number ] =amount_type 33end 34 35--- @param entity LuaEntity 36--- @return uint? 37local function get_stored_amount_type ( entity ) 38return global . infinity_pipe_amount_type [ entity . unit_number ] 39end 40 41--- @param entity LuaEntity 42--- @return uint? 43local function remove_stored_amount_type ( entity ) 44local value =global . infinity_pipe_amount_type [ entity . unit_number ] 45global . infinity_pipe_amount_type [ entity . unit_number ] =nil 46return value 47end 48 49--- @param entity LuaEntity 50--- @param new_capacity double 51--- @return LuaEntity? 52local function swap_entity ( entity , new_capacity ) 53local amount_type =remove_stored_amount_type ( entity ) 54local new_entity =entity . surface . create_entity ( { 55name ="ee-infinity-pipe-" .. new_capacity , 56position =entity . position , 57direction =entity . direction , 58force =entity . force , 59fast_replace =true , 60create_build_effect_smoke =false , 61spill =false , 62} ) 63if not new_entity then 64return 65end 66store_amount_type ( new_entity , amount_type ) 67return new_entity 68end 69 70--- @param entity BlueprintEntity|LuaEntity 71local function check_is_our_pipe ( entity ) 72return string . find ( entity . name , "^ee%-infinity%-pipe%-%d+$" ) 73end 74 75--- @param entity LuaEntity 76local function snap ( entity ) 77local own_id =entity . unit_number 78for _ , fluidbox in pairs ( entity . fluidbox . get_connections ( 1 )) do 79if crafter_snapping_types [ fluidbox . owner . type ] then 80for i =1 , # fluidbox do 81--- @cast i uint 82local connections =fluidbox . get_connections ( i ) 83local prototype =fluidbox . get_prototype ( i ) 84for j =1 , # connections do 85if prototype . production_type == "input" and connections [ j ]. owner . unit_number == own_id then 86local fluid =fluidbox . get_locked_fluid ( i ) 87if fluid then 88entity . set_infinity_pipe_filter ( { name =fluid , percentage =1 , mode ="at-least" } ) 89return 90end 91end 92end 93end 94end 95end 96end 97 98--- @param filter InfinityPipeFilter? 99--- @return double, double 100local function get_temperature_limits ( filter ) 101if filter and filter . name then 102local prototype =game . fluid_prototypes [ filter . name ] 103return prototype . default_temperature , prototype . max_temperature 104end 105return 0 , 100 106end 107 108-- GUI 109 110--- @class InfinityPipeGui 111--- @field elems InfinityPipeGuiElems 112--- @field entity LuaEntity 113--- @field mode InfinityPipeMode 114--- @field player LuaPlayer 115--- @field temperature double 116 117--- @class InfinityPipeGuiElems 118--- @field ee_infinity_pipe_window LuaGuiElement 119--- @field titlebar_flow LuaGuiElement 120--- @field drag_handle LuaGuiElement 121--- @field capacity_dropdown LuaGuiElement 122--- @field amount_progressbar LuaGuiElement 123--- @field entity_preview LuaGuiElement 124--- @field filter_button LuaGuiElement 125--- @field amount_slider LuaGuiElement 126--- @field amount_textfield LuaGuiElement 127--- @field amount_type_dropdown LuaGuiElement 128--- @field mode_radio_button_at_least LuaGuiElement 129--- @field mode_radio_button_at_most LuaGuiElement 130--- @field mode_radio_button_exactly LuaGuiElement 131--- @field mode_radio_button_add LuaGuiElement 132--- @field mode_radio_button_remove LuaGuiElement 133--- @field temperature_slider LuaGuiElement 134--- @field temperature_textfield LuaGuiElement 135 136--- @param player_index uint 137local function destroy_gui ( player_index ) 138local self =global . infinity_pipe_gui [ player_index ] 139if not self then 140return 141end 142global . infinity_pipe_gui [ player_index ] =nil 143local window =self . elems . ee_infinity_pipe_window 144if window . valid then 145window . destroy () 146end 147end 148 149--- @param self InfinityPipeGui 150local function update_fluid_content_bar ( self ) 151local bar =self . elems . amount_progressbar 152local fluidbox =self . entity . fluidbox 153local capacity =fluidbox . get_capacity ( 1 ) 154local content =fluidbox [ 1 ] or { amount =0 } 155 156local caption =format . number ( content . amount ) .. " / " .. format . number ( capacity ) 157bar . caption =caption 158bar . value =content . amount / capacity 159 160if not content . name then 161return 162end 163 164local bar_color =game . fluid_prototypes [ content . name ]. base_color 165-- Calculate luminance of the background color 166-- Source: https://stackoverflow.com/questions/596216/formula-to-determine-perceived-brightness-of-rgb-color 167local luminance =( 0.2126 * bar_color . r ) + ( 0.7152 * bar_color . g ) + ( 0.0722 * bar_color . b ) 168if luminance > 0.55 then 169bar . style ="ee_infinity_pipe_progressbar" 170else 171bar . style ="ee_infinity_pipe_progressbar_light_text" 172end 173bar . style . color =bar_color 174end 175 176--- @param self InfinityPipeGui 177--- @param new_entity LuaEntity? 178--- @param reset_temperature boolean? 179local function update_gui ( self , new_entity , reset_temperature ) 180if not new_entity and not self . entity . valid then 181destroy_gui ( self . player . index ) 182return 183end 184local elems =self . elems 185if new_entity then 186elems . entity_preview . entity =new_entity 187self . entity =new_entity 188end 189local entity =self . entity 190local filter =entity . get_infinity_pipe_filter () or {} 191if filter . mode then 192self . mode =filter . mode 193end 194if filter . temperature then 195self . temperature =filter . temperature 196elseif reset_temperature then 197self . temperature =0 198end 199 200local capacity =entity . fluidbox . get_capacity ( 1 ) 201elems . capacity_dropdown . selected_index =table . find ( shared_constants . infinity_pipe_capacities , capacity ) --[[@as uint]] 202 203elems . filter_button . elem_value =filter . name 204 205local amount_type =get_stored_amount_type ( entity ) or 1 206elems . amount_slider . slider_value =filter . percentage or 0 207elems . amount_textfield . style ="ee_slider_textfield" 208local percentage =filter . percentage or 0 209if amount_type == defines_amount_type . percent then 210elems . amount_textfield . text =tostring ( math . floor ( percentage * 100 )) 211else 212elems . amount_textfield . text =tostring ( math . floor ( percentage * capacity )) 213end 214elems . amount_type_dropdown . selected_index =amount_type 215 216local mode =self . mode 217elems . mode_radio_button_at_least . state =mode == "at-least" 218elems . mode_radio_button_at_most . state =mode == "at-most" 219elems . mode_radio_button_exactly . state =mode == "exactly" 220elems . mode_radio_button_add . state =mode == "add" 221elems . mode_radio_button_remove . state =mode == "remove" 222 223local min , max =get_temperature_limits ( filter ) 224if min == max then 225elems . temperature_slider . enabled =false 226elems . temperature_textfield . enabled =false 227elems . temperature_slider . set_slider_minimum_maximum ( min , max + 1 ) 228elems . temperature_slider . slider_value =min 229else 230elems . temperature_slider . enabled =true 231elems . temperature_textfield . enabled =true 232elems . temperature_slider . set_slider_minimum_maximum ( min , max ) 233elems . temperature_slider . slider_value =self . temperature 234end 235elems . temperature_textfield . style ="ee_slider_textfield" 236elems . temperature_textfield . text =tostring ( self . temperature ) 237 238update_fluid_content_bar ( self ) 239end 240 241--- @param entity LuaEntity 242--- @param reset_temperature boolean? 243local function update_all_guis ( entity , reset_temperature ) 244for _ , gui in pairs ( global . infinity_pipe_gui ) do 245if not gui . entity . valid then 246update_gui ( gui , entity , reset_temperature ) 247elseif gui . entity == entity then 248update_gui ( gui , nil , reset_temperature ) 249end 250end 251end 252 253local handlers ={ 254--- @param self InfinityPipeGui 255--- @param e EventData.on_gui_closed|EventData.on_gui_click 256on_ip_gui_closed =function ( self , e ) 257destroy_gui ( e . player_index ) 258local player =self . player 259if not player . valid then 260return 261end 262player . play_sound ( { path ="entity-close/ee-infinity-accumulator-tertiary-buffer" } ) 263end , 264 265--- @param self InfinityPipeGui 266--- @param e EventData.on_gui_selection_state_changed 267on_ip_gui_capacity_dropdown_selection_state_changed =function ( self , e ) 268local entity =self . entity 269local new_capacity =shared_constants . infinity_pipe_capacities [ e . element . selected_index ] 270if new_capacity == entity . fluidbox . get_capacity ( 1 ) then 271return 272end 273local new_entity =swap_entity ( entity , new_capacity ) 274if not new_entity then 275return 276end 277update_all_guis ( new_entity ) 278end , 279 280--- @param self InfinityPipeGui 281--- @param e EventData.on_gui_elem_changed 282on_ip_gui_filter_changed =function ( self , e ) 283local entity =self . entity 284local elem =e . element . elem_value --[[@as string?]] 285if elem then 286if not filter then 287filter ={ mode =self . mode , percentage =1 } 288end 289filter . name =elem 290entity . set_infinity_pipe_filter ( filter ) 291else 292entity . set_infinity_pipe_filter ( nil ) 293end 294update_all_guis ( entity , not elem ) 295end , 296 297--- @param self InfinityPipeGui 298--- @param e EventData.on_gui_elem_changed 299on_ip_gui_amount_slider_value_changed =function ( self , e ) 300local entity =self . entity 301local value =e . element . slider_value 302local filter =entity . get_infinity_pipe_filter () 303if not filter then 304-- Textfield should always match slider 305local textfield =self . elems . amount_textfield 306textfield . style ="ee_slider_textfield" 307textfield . text =tostring ( math . floor ( value * 100 )) 308return 309end 310filter . percentage =value 311entity . set_infinity_pipe_filter ( filter ) 312update_all_guis ( entity ) 313end , 314 315--- @param self InfinityPipeGui 316--- @param e EventData.on_gui_text_changed 317on_ip_gui_amount_textfield_changed =function ( self , e ) 318local entity =self . entity 319local textfield =e . element 320local text =textfield . text 321local value =tonumber ( text ) 322local max =100 323local amount_type =get_stored_amount_type ( entity ) 324if amount_type == defines_amount_type . units then 325max =entity . fluidbox . get_capacity ( 1 ) 326end 327if not value or value < 0 or value > max then 328textfield . style ="ee_invalid_slider_textfield" 329return 330end 331textfield . style ="ee_slider_textfield" 332 333local percentage =value / 100 334if amount_type == defines_amount_type . units then 335local capacity =entity . fluidbox . get_capacity ( 1 ) 336percentage =value / capacity 337end 338 339self . elems . amount_slider . slider_value =percentage 340 341local filter =entity . get_infinity_pipe_filter () 342if filter then 343filter . percentage =percentage 344entity . set_infinity_pipe_filter ( filter ) 345update_all_guis ( entity ) 346end 347end , 348 349--- @param self InfinityPipeGui 350--- @param e EventData.on_gui_selection_state_changed 351on_ip_gui_amount_type_changed =function ( self , e ) 352local amount_type =e . element . selected_index 353store_amount_type ( self . entity , amount_type ) 354update_all_guis ( self . entity ) 355end , 356 357--- @param self InfinityPipeGui 358--- @param e EventData.on_gui_checked_state_changed 359on_ip_gui_mode_radio_button_selected =function ( self , e ) 360local mode =e . element . tags . mode --[[@as string]] 361self . mode =mode 362 363local entity =self . entity 364local filter =entity . get_infinity_pipe_filter () 365if filter then 366filter . mode =mode 367entity . set_infinity_pipe_filter ( filter ) 368end 369 370update_all_guis ( entity ) 371end , 372 373--- @param self InfinityPipeGui 374--- @param e EventData.on_gui_value_changed 375on_ip_gui_temperature_slider_value_changed =function ( self , e ) 376local entity =self . entity 377local value =math . floor ( e . element . slider_value ) 378local filter =entity . get_infinity_pipe_filter () 379if not filter then 380-- Textfield should always match slider 381local textfield =self . elems . temperature_textfield 382textfield . style ="ee_slider_textfield" 383textfield . text =tostring ( value ) 384return 385end 386filter . temperature =value 387entity . set_infinity_pipe_filter ( filter ) 388update_all_guis ( entity ) 389end , 390 391--- @param self InfinityPipeGui 392--- @param e EventData.on_gui_text_changed 393on_ip_gui_temperature_textfield_changed =function ( self , e ) 394local entity =self . entity 395local textfield =e . element 396local text =textfield . text 397local value =tonumber ( text ) 398 399local filter =entity . get_infinity_pipe_filter () 400local min , max =get_temperature_limits ( filter ) 401 402if not value or value < min or value > max then 403textfield . style ="ee_invalid_slider_textfield" 404return 405end 406textfield . style ="ee_slider_textfield" 407 408self . elems . temperature_slider . slider_value =value 409 410if filter then 411filter . temperature =value 412entity . set_infinity_pipe_filter ( filter ) 413update_all_guis ( entity ) 414end 415end , 416} 417 418gui . add_handlers ( handlers , function ( e , handler ) 419local self =global . infinity_pipe_gui [ e . player_index ] 420if not self then 421return 422end 423if not self . entity . valid then 424return 425end 426handler ( self , e ) 427end ) 428 429--- @param player LuaPlayer 430--- @param entity LuaEntity 431local function create_gui ( player , entity ) 432destroy_gui ( player . index ) 433 434--- @type InfinityPipeGuiElems 435local elems =gui . add ( player . gui . screen , { 436type ="frame" , 437name ="ee_infinity_pipe_window" , 438direction ="vertical" , 439elem_mods ={ auto_center =true } , 440handler ={ [ defines . events . on_gui_closed ] =handlers . on_ip_gui_closed } , 441{ 442type ="flow" , 443name ="titlebar_flow" , 444style ="flib_titlebar_flow" , 445drag_target ="ee_infinity_pipe_window" , 446{ 447type ="label" , 448style ="frame_title" , 449caption ={ "entity-name.ee-infinity-pipe" } , 450ignored_by_interaction =true , 451} , 452{ type ="empty-widget" , style ="flib_titlebar_drag_handle" , ignored_by_interaction =true } , 453util . close_button ( handlers . on_ip_gui_closed ), 454} , 455{ 456type ="frame" , 457style ="entity_frame" , 458direction ="vertical" , 459{ 460type ="frame" , 461style ="deep_frame_in_shallow_frame" , 462style_mods ={ bottom_margin =4 } , 463{ 464type ="entity-preview" , 465name ="entity_preview" , 466style ="wide_entity_button" , 467elem_mods ={ entity =entity } , 468} , 469} , 470{ 471type ="flow" , 472style_mods ={ horizontal_spacing =8 , vertical_align ="center" } , 473{ 474type ="progressbar" , 475name ="amount_progressbar" , 476style ="ee_infinity_pipe_progressbar" , 477} , 478{ type ="label" , caption ="/" } , 479{ 480type ="drop-down" , 481name ="capacity_dropdown" , 482items =table . map ( shared_constants . infinity_pipe_capacities , function ( capacity ) 483return format . number ( capacity ) 484end ), 485selected_index =0 , 486handler ={ 487[ defines . events . on_gui_selection_state_changed ] =handlers . on_ip_gui_capacity_dropdown_selection_state_changed , 488} , 489} , 490} , 491{ type ="line" , direction ="horizontal" } , 492{ 493type ="flow" , 494style ="centering_horizontal_flow" , 495direction ="horizontal" , 496{ 497type ="choose-elem-button" , 498name ="filter_button" , 499style ="flib_standalone_slot_button_default" , 500elem_type ="fluid" , 501handler ={ [ defines . events . on_gui_elem_changed ] =handlers . on_ip_gui_filter_changed } , 502} , 503{ 504type ="slider" , 505name ="amount_slider" , 506style_mods ={ horizontally_stretchable =true , margin ={ 0 , 8 , 0 , 8 } } , 507minimum_value =0 , 508maximum_value =1 , 509value_step =0.01 , 510value =0 , 511handler ={ [ defines . events . on_gui_value_changed ] =handlers . on_ip_gui_amount_slider_value_changed } , 512} , 513{ 514type ="textfield" , 515name ="amount_textfield" , 516style ="slider_value_textfield" , 517numeric =true , 518allow_decimal =true , 519clear_and_focus_on_right_click =true , 520text ="0" , 521handler ={ [ defines . events . on_gui_text_changed ] =handlers . on_ip_gui_amount_textfield_changed } , 522} , 523{ 524type ="drop-down" , 525name ="amount_type_dropdown" , 526style_mods ={ width =55 } , 527items ={ { "gui-infinity-pipe.percent" } , { "gui-infinity-pipe.ee-units" } } , 528selected_index =1 , 529handler ={ [ defines . events . on_gui_selection_state_changed ] =handlers . on_ip_gui_amount_type_changed } , 530} , 531} , 532{ 533type ="flow" , 534style_mods ={ horizontal_spacing =0 } , 535direction ="horizontal" , 536util . mode_radio_button ( "at-least" , handlers . on_ip_gui_mode_radio_button_selected ), 537util . pusher (), 538util . mode_radio_button ( "at-most" , handlers . on_ip_gui_mode_radio_button_selected ), 539util . pusher (), 540util . mode_radio_button ( "exactly" , handlers . on_ip_gui_mode_radio_button_selected ), 541util . pusher (), 542util . mode_radio_button ( "add" , handlers . on_ip_gui_mode_radio_button_selected ), 543util . pusher (), 544util . mode_radio_button ( "remove" , handlers . on_ip_gui_mode_radio_button_selected ), 545} , 546{ type ="line" , direction ="horizontal" } , 547{ 548type ="flow" , 549style ="centering_horizontal_flow" , 550direction ="horizontal" , 551{ type ="label" , caption ={ "gui-infinity-pipe.temperature" } } , 552{ 553type ="slider" , 554name ="temperature_slider" , 555style_mods ={ horizontally_stretchable =true , margin ={ 0 , 8 , 0 , 8 } } , 556minimum_value =0 , 557maximum_value =100 , 558value =0 , 559handler ={ 560[ defines . events . on_gui_value_changed ] =handlers . on_ip_gui_temperature_slider_value_changed , 561} , 562} , 563{ 564type ="textfield" , 565name ="temperature_textfield" , 566style ="slider_value_textfield" , 567clear_and_focus_on_right_click =true , 568text ="0" , 569handler ={ 570[ defines . events . on_gui_text_changed ] =handlers . on_ip_gui_temperature_textfield_changed , 571} , 572} , 573} , 574} , 575} ) 576 577player . opened =elems . ee_infinity_pipe_window 578 579--- @type InfinityPipeGui 580local self ={ 581elems =elems , 582entity =entity , 583mode ="at-least" , 584temperature =0 , 585player =player , 586} 587global . infinity_pipe_gui [ player . index ] =self 588 589update_gui ( self ) 590end 591 592--- @param e BuiltEvent 593local function on_entity_built ( e ) 594local entity =e . created_entity or e . entity or e . destination 595if not entity or not entity . valid or not check_is_our_pipe ( entity ) then 596return 597end 598 599local tags =e . tags 600if tags and tags . EditorExtensions then 601store_amount_type ( entity , tags . EditorExtensions . amount_type --[[@as uint]] ) 602end 603 604if e . name == defines . events . on_built_entity then 605snap ( entity ) 606end 607end 608 609--- @param e DestroyedEvent 610local function on_entity_destroyed ( e ) 611local entity =e . entity 612if not entity . valid or not check_is_our_pipe ( entity ) then 613return 614end 615remove_stored_amount_type ( e . entity ) 616end 617 618--- @param e EventData.on_gui_opened 619local function on_gui_opened ( e ) 620local entity =e . entity 621if not entity or not entity . valid or not check_is_our_pipe ( entity ) then 622return 623end 624local player =game . get_player ( e . player_index ) 625if not player then 626return 627end 628create_gui ( player , entity ) 629end 630 631--- @param e EventData.on_player_setup_blueprint 632local function on_player_setup_blueprint ( e ) 633local blueprint =util . get_blueprint ( e ) 634if not blueprint then 635return 636end 637 638local entities =blueprint . get_blueprint_entities () 639if not entities then 640return 641end 642for i , entity in pairs ( entities ) do 643--- @cast i uint 644if not check_is_our_pipe ( entity ) then 645goto continue 646end 647local real_entity =e . surface . find_entity ( entity . name , entity . position ) 648if not real_entity then 649goto continue 650end 651blueprint . set_blueprint_entity_tag ( 652i , 653"EditorExtensions" , 654{ amount_type =global . infinity_pipe_amount_type [ real_entity . unit_number ] } 655) 656:: continue :: 657end 658end 659 660--- @param e EventData.on_entity_settings_pasted 661local function on_entity_settings_pasted ( e ) 662local source , destination =e . source , e . destination 663if not check_is_our_pipe ( source ) or not check_is_our_pipe ( destination ) then 664return 665end 666 667if source . name ~= destination . name then 668local new_destination =swap_entity ( destination , source . fluidbox . get_capacity ( 1 )) 669if not new_destination then 670return 671end 672destination =new_destination 673end 674 675local source_amount_type =get_stored_amount_type ( source ) 676local destination_amount_type =get_stored_amount_type ( destination ) 677if source_amount_type ~= destination_amount_type then 678store_amount_type ( destination , source_amount_type ) 679end 680 681update_all_guis ( destination ) 682end 683 684local infinity_pipe ={} 685 686infinity_pipe . on_init =function () 687global . infinity_pipe_gui ={} 688--- @type table<uint, uint> 689global . infinity_pipe_amount_type ={} 690end 691 692infinity_pipe . on_configuration_changed =function () 693for player_index in pairs ( game . players ) do 694destroy_gui ( player_index --[[@as uint]] ) 695end 696end 697 698infinity_pipe . events ={ 699[ defines . events . on_built_entity ] =on_entity_built , 700[ defines . events . on_entity_cloned ] =on_entity_built , 701[ defines . events . on_entity_died ] =on_entity_destroyed , 702[ defines . events . on_entity_settings_pasted ] =on_entity_settings_pasted , 703[ defines . events . on_gui_opened ] =on_gui_opened , 704[ defines . events . on_player_mined_entity ] =on_entity_destroyed , 705[ defines . events . on_player_setup_blueprint ] =on_player_setup_blueprint , 706[ defines . events . on_robot_built_entity ] =on_entity_built , 707[ defines . events . on_robot_mined_entity ] =on_entity_destroyed , 708[ defines . events . script_raised_built ] =on_entity_built , 709[ defines . events . script_raised_destroy ] =on_entity_destroyed , 710[ defines . events . script_raised_revive ] =on_entity_built , 711} 712 713infinity_pipe . on_nth_tick ={ 714[ 1 ] =function () 715for _ , gui in pairs ( global . infinity_pipe_gui ) do 716update_fluid_content_bar ( gui ) 717end 718end , 719} 720 721return infinity_pipe