z_mirror/Krastorio2
git clone https://git.raiguard.me/z_mirror/Krastorio2
c13e3614
trunk
1local flib_gui =require ( "__flib__.gui" ) 2local flib_math =require ( "__flib__.math" ) 3local flib_position =require ( "__flib__.position" ) 4 5local util =require ( "scripts.util" ) 6 7--- @alias IntergalacticTransceiverStatus 8--- | "charging" 9--- | "discharging" 10--- | "not_enough_input" 11--- | "ready" 12 13local defines_to_status ={ 14[ defines . entity_status . charging ] ="charging" , 15[ defines . entity_status . discharging ] ="discharging" , 16} 17 18--- @type table<IntergalacticTransceiverStatus, { label: LocalisedString, sprite: SpritePath }> 19local status_properties ={ 20charging ={ 21label ={ "entity-status.charging" } , 22icon ="utility/status_working" , 23} , 24discharging ={ 25alert_label ={ "gui.kr-intergalactic-transceiver-alert-discharging" } , 26label ={ "entity-status.discharging" } , 27icon ="utility/status_not_working" , 28} , 29not_enough_input ={ 30alert_label ={ "gui.kr-intergalactic-transceiver-alert-not-enough-input" } , 31label ={ "entity-status.kr-not-enough-input" } , 32icon ="utility/status_not_working" , 33tooltip ={ "entity-status-tooltip.kr-not-enough-input" } , 34} , 35no_power ={ 36alert_label ={ "gui.kr-intergalactic-transceiver-alert-no-power" } , 37label ={ "entity-status.no-power" } , 38icon ="utility/status_not_working" , 39} , 40ready ={ 41alert_label ={ "gui.kr-intergalactic-transceiver-alert-ready" } , 42label ={ "entity-status.kr-ready" } , 43icon ="utility/status_working" , 44} , 45} 46 47local min_charge_delta =2.5e8 -- Requires charge rate of 15 GW 48local max_charge_delta =2e9 49---@type table<string, number> 50local max_energy ={} 51local default_buffer_capacity = 52prototypes . entity [ "kr-intergalactic-transceiver" ]. electric_energy_source_prototype . buffer_capacity 53for _ , quality in pairs ( prototypes . quality ) do 54max_energy [ quality . name ] =( default_buffer_capacity * quality . accumulator_capacity_multiplier ) - max_charge_delta 55end 56local energy_drain =50e9 57 58--- @param transceiver_data IntergalacticTransceiverForceData 59local function activate_transceiver ( transceiver_data ) 60local entity =transceiver_data . entity 61local entity_surface =entity . surface 62local entity_position =entity . position 63for _ , player in pairs ( entity . force . connected_players ) do 64if player . surface == entity_surface and flib_position . distance ( entity_position , player . position ) <= 100 then 65storage . intergalactic_transceiver . player_in_cutscene [ player . index ] =true 66if player . controller_type == defines . controllers . character then 67player . character . destructible =false 68end 69player . set_controller ( { 70type =defines . controllers . cutscene , 71waypoints ={ 72{ 73position =entity . position , 74zoom =1.2 , 75transition_time =70 , 76time_to_wait =30 , 77} , 78{ 79position =entity . position , 80zoom =1.2 , 81transition_time =0 , 82time_to_wait =15 , 83} , 84{ 85position =entity . position , 86zoom =0.8 , 87transition_time =130 , 88time_to_wait =10 , 89} , 90{ 91position =entity . position , 92zoom =1.2 , 93transition_time =200 , 94time_to_wait =60 , 95} , 96} , 97final_transition_time =60 , 98} ) 99end 100end 101storage . intergalactic_transceiver . in_cutscene [ entity . force_index ] =true 102end 103 104--- @param e EventData.on_cutscene_finished 105local function on_cutscene_finished ( e ) 106local player =game . get_player ( e . player_index ) 107if not player then 108return 109end 110if not storage . intergalactic_transceiver then 111return 112end 113 114if storage . intergalactic_transceiver . player_in_cutscene [ e . player_index ] then 115storage . intergalactic_transceiver . player_in_cutscene [ e . player_index ] =nil 116if player . character then 117player . character . destructible =true 118end 119end 120 121if not storage . intergalactic_transceiver . in_cutscene [ player . force . index ] then 122return 123end 124storage . intergalactic_transceiver . in_cutscene [ player . force_index ] =nil 125 126player . force . technologies [ "kr-logo" ]. enabled =true 127 128if not storage . intergalactic_transceiver . is_victory then 129return 130end 131 132game . enable_galaxy_of_fame_button =true -- Does nothing in vanilla 133game . set_win_ending_info ( { 134title ={ "gui.kr-victory-title" } , 135message ={ "gui.kr-victory-message" } , 136bullet_points ={ 137{ "victory-bullet-point-1" } , 138{ "victory-bullet-point-2" } , 139{ "victory-bullet-point-3" } , 140{ "victory-bullet-point-4" } , 141} , 142final_message ={ "gui.kr-victory-final-message" } , 143} ) 144game . set_game_state ( { 145can_continue =true , 146game_finished =true , 147victorious_force =player . force , 148player_won =true , 149} ) 150player . force . play_sound ( { path ="kr-win-joke-voice" } ) 151end 152 153--- @param e EventData.on_cutscene_waypoint_reached 154local function on_cutscene_waypoint_reached ( e ) 155if not storage . intergalactic_transceiver then 156return 157end 158local player =game . get_player ( e . player_index ) 159if not player or not storage . intergalactic_transceiver . in_cutscene [ player . force_index ] then 160return 161end 162 163local transceiver_data =storage . intergalactic_transceiver . forces [ player . force_index ] 164if not transceiver_data then 165return 166end 167local entity =transceiver_data . entity 168if not entity . valid then 169return 170end 171 172if e . waypoint_index == 1 then 173entity . surface . create_entity ( { 174name ="kr-intergalactic-transceiver-wave" , 175force =entity . force , 176position =entity . position , 177speed =0 , 178max_range =100 , 179target =entity , 180create_build_effect_smoke =false , 181raise_built =true , 182} ) 183elseif e . waypoint_index == 2 then 184local entity_force =entity . force 185local entity_player =entity . last_user 186local entity_position =entity . position 187local entity_quality =entity . quality 188local entity_surface =entity . surface 189entity . destroy () 190transceiver_data . activating =true 191local new_entity =entity_surface . create_entity ( { 192name ="kr-activated-intergalactic-transceiver" , 193position =entity_position , 194force =entity_force , 195player =entity_player , 196quality =entity_quality , 197create_build_effect_smoke =false , 198raise_built =true , 199} ) 200transceiver_data . activating =false 201if not new_entity or not new_entity . valid then 202return 203end 204if entity_quality . name ~= "normal" then 205new_entity . power_production =new_entity . power_production * entity_quality . accumulator_capacity_multiplier 206new_entity . electric_buffer_size =new_entity . electric_buffer_size * entity_quality . accumulator_capacity_multiplier 207end 208storage . intergalactic_transceiver . forces [ entity_force . index ] ={ entity =new_entity } 209end 210end 211 212--- @param e EventData.on_gui_click 213local function on_gui_window_closed ( e ) 214local self =storage . intergalactic_transceiver . guis [ e . player_index ] 215if not self then 216return 217end 218self . elems . kr_intergalactic_transceiver_window . destroy () 219self . player . opened =nil 220self . player . play_sound ( { path ="entity-close/kr-intergalactic-transceiver" } ) 221storage . intergalactic_transceiver . guis [ e . player_index ] =nil 222end 223 224--- @param entity LuaEntity 225local function close_open_guis ( entity ) 226for player_index , self in pairs ( storage . intergalactic_transceiver . guis ) do 227if self . entity == entity or not self . entity . valid then 228on_gui_window_closed ( { player_index =player_index } ) 229end 230end 231end 232 233--- @param e EventData.on_gui_click 234local function on_gui_activate_button_clicked ( e ) 235local player =game . get_player ( e . player_index ) 236if not player then 237return 238end 239local transceiver_data =storage . intergalactic_transceiver . forces [ player . force_index ] 240close_open_guis ( transceiver_data . entity ) 241activate_transceiver ( transceiver_data ) 242end 243 244--- @param self IntergalacticTransceiverGuiData 245local function update_gui ( self ) 246local charged =flib_math . round ( self . entity . energy / max_energy [ self . entity . quality . name ], 0.01 ) 247local transceiver_data =storage . intergalactic_transceiver . forces [ self . player . force_index ] 248local props =status_properties [ transceiver_data . status ] 249self . elems . status_icon . sprite =props . icon 250self . elems . status_label . caption =props . label 251self . elems . status_label . tooltip =props . tooltip 252self . elems . charge_progressbar . value =charged 253self . elems . charge_progressbar . caption =math . floor ( charged * 100 ) .. " %" 254self . elems . activate_button . enabled =charged == 1 255end 256 257--- @param player LuaPlayer 258--- @param entity LuaEntity 259local function create_gui ( player , entity ) 260local elems =flib_gui . add ( player . gui . screen , { 261type ="frame" , 262name ="kr_intergalactic_transceiver_window" , 263direction ="vertical" , 264elem_mods ={ auto_center =true } , --- @diagnostic disable-line: missing-fields 265handler ={ [ defines . events . on_gui_closed ] =on_gui_window_closed } , 266{ 267type ="flow" , 268style ="flib_titlebar_flow" , 269drag_target ="kr_intergalactic_transceiver_window" , 270{ 271type ="label" , 272style ="flib_frame_title" , 273caption ={ "entity-name.kr-intergalactic-transceiver" } , 274ignored_by_interaction =true , 275} , 276{ type ="empty-widget" , style ="flib_titlebar_drag_handle" , ignored_by_interaction =true } , 277{ 278type ="sprite-button" , 279style ="frame_action_button" , 280sprite ="utility/close" , 281mouse_button_filter ={ "left" } , 282handler ={ [ defines . events . on_gui_click ] =on_gui_window_closed } , 283} , 284} , 285{ 286type ="frame" , 287style ="entity_frame" , 288style_mods ={ width =424 } , --- @diagnostic disable-line: missing-fields 289direction ="vertical" , 290{ 291type ="flow" , 292style_mods ={ vertical_align ="center" } , 293{ type ="sprite" , name ="status_icon" , style ="flib_indicator" } , 294{ type ="label" , name ="status_label" } , 295} , 296{ 297type ="frame" , 298style ="deep_frame_in_shallow_frame" , 299{ type ="entity-preview" , style ="wide_entity_button" , elem_mods ={ entity =entity } } , 300} , 301{ 302type ="progressbar" , 303name ="charge_progressbar" , 304style ="production_progressbar" , 305style_mods ={ horizontally_stretchable =true } , 306value =0 , 307} , 308{ 309type ="button" , 310name ="activate_button" , 311style_mods ={ horizontally_stretchable =true } , 312caption ={ "gui.kr-intergalactic-transceiver-activate" } , 313enabled =false , 314handler ={ [ defines . events . on_gui_click ] =on_gui_activate_button_clicked } , 315} , 316} , 317} ) 318 319--- @class IntergalacticTransceiverGuiData 320storage . intergalactic_transceiver . guis [ player . index ] ={ 321elems =elems , 322entity =entity , 323player =player , 324} 325 326player . opened =elems . kr_intergalactic_transceiver_window 327if not game . is_multiplayer () then 328player . play_sound ( { path ="entity-open/kr-intergalactic-transceiver" } ) 329end 330end 331 332--- @param entity LuaEntity 333local function on_transceiver_destroyed ( entity ) 334local force_index =entity . force . index 335storage . intergalactic_transceiver . forces [ force_index ] =nil 336close_open_guis ( entity ) 337end 338 339--- @param entity LuaEntity 340local function on_inactive_transceiver_destroyed ( entity ) 341storage . intergalactic_transceiver . inactive [ entity . unit_number ] =nil 342end 343 344--- @param e EntityDestroyedEvent 345local function on_entity_destroyed ( e ) 346local entity =e . entity 347if not entity or not entity . valid then 348return 349end 350 351if entity . name == "kr-intergalactic-transceiver" then 352on_transceiver_destroyed ( entity ) 353elseif entity . name == "kr-inactive-intergalactic-transceiver" then 354on_inactive_transceiver_destroyed ( entity ) 355end 356end 357 358--- @param entity LuaEntity 359local function on_transceiver_built ( entity ) 360local existing =storage . intergalactic_transceiver . forces [ entity . force . index ] 361if existing then 362local position =entity . position 363local force =entity . force 364local player =entity . last_user 365local surface =entity . surface 366entity . destroy () 367 368local new_entity =surface . create_entity ( { 369name ="kr-inactive-intergalactic-transceiver" , 370position =position , 371force =force , 372player =player , 373create_build_effect_smoke =false , 374raise_built =true , 375} ) 376if new_entity and new_entity . valid then 377storage . intergalactic_transceiver . inactive [ new_entity . unit_number ] =new_entity 378end 379 380return 381end 382 383--- @class IntergalacticTransceiverForceData 384storage . intergalactic_transceiver . forces [ entity . force . index ] ={ 385activating =false , 386entity =entity , 387last_alert_tick =game . tick , 388last_energy =entity . energy , 389status ="empty" , 390} 391end 392 393--- @param entity LuaEntity 394local function on_active_transceiver_built ( entity ) 395local existing =storage . intergalactic_transceiver . forces [ entity . force . index ] 396if not existing or not existing . activating then 397local position =entity . position 398local force =entity . force 399local player =entity . last_user 400local surface =entity . surface 401entity . destroy () 402 403local new_entity =surface . create_entity ( { 404name ="kr-intergalactic-transceiver" , 405position =position , 406force =force , 407player =player , 408create_build_effect_smoke =false , 409raise_built =true , 410quality =quality , 411} ) 412 413if new_entity and new_entity . valid then 414on_transceiver_built ( new_entity ) 415end 416end 417end 418 419--- @param e EntityBuiltEvent 420local function on_entity_built ( e ) 421local entity =e . entity or e . destination 422if not entity or not entity . valid then 423return 424end 425 426if entity . name == "kr-intergalactic-transceiver" then 427on_transceiver_built ( entity ) 428elseif entity . name == "kr-activated-intergalactic-transceiver" then 429on_active_transceiver_built ( entity ) 430end 431end 432 433--- @param data IntergalacticTransceiverForceData 434--- @param entity LuaEntity 435local function update_transceiver ( data , entity ) 436if not data . last_energy then 437return 438end 439local current_energy =entity . energy 440local new_energy =current_energy 441local difference =current_energy - data . last_energy 442--- @type IntergalacticTransceiverStatus 443local status ="charging" 444-- If we're not receiving enough power 445if difference < min_charge_delta and current_energy > 0 then 446-- Drain the transceiver at 3 TJ / sec 447new_energy =math . max ( 0 , current_energy - energy_drain ) 448-- Update status 449if new_energy > 0 then 450status ="discharging" 451else 452status ="not_enough_input" 453end 454else 455if current_energy > max_energy [ entity . quality . name ] then 456-- XXX: If the energy remains constant, the entity status will change to "normal", so give it a random offset. 457new_energy =max_energy [ entity . quality . name ] + math . random ( 0 , 20 ) * 1000000 458status ="ready" 459else 460local current_status =defines_to_status [ entity . status ] 461if status_properties [ current_status ] then 462status =current_status 463end 464end 465end 466 467if new_energy ~= current_energy then 468entity . energy =new_energy 469end 470local last_status =data . status 471data . last_energy =new_energy 472data . status =status 473 474local status_properties =status_properties [ status ] 475if status_properties and status_properties . alert_label and game . tick - data . last_alert_tick >= 60 then 476data . last_alert_tick =game . tick 477for _ , player in pairs ( entity . force . players ) do 478player . add_custom_alert ( 479entity , 480{ type ="item" , name ="kr-intergalactic-transceiver" } , 481status_properties . alert_label , 482true 483) 484if status == "discharging" and last_status ~= status then 485player . play_sound ( { 486path ="kr-intergalactic-transceiver-discharging-warning" , 487} ) 488end 489end 490end 491end 492 493--- @param e EventData.on_gui_opened 494local function on_gui_opened ( e ) 495if storage . intergalactic_transceiver . guis [ e . player_index ] then 496return 497end 498if e . gui_type ~= defines . gui_type . entity then 499return 500end 501local entity =e . entity 502if not entity or entity . name ~= "kr-intergalactic-transceiver" then 503return 504end 505local player =game . get_player ( e . player_index ) 506if not player then 507return 508end 509create_gui ( player , entity ) 510end 511 512local function on_tick () 513if not storage . intergalactic_transceiver then 514return 515end 516 517local transceivers =storage . intergalactic_transceiver . forces 518for force_index , data in pairs ( transceivers ) do 519local entity =data . entity 520if entity and entity . valid then 521update_transceiver ( data , entity ) 522else 523transceivers [ force_index ] =nil 524end 525end 526 527for _ , self in pairs ( storage . intergalactic_transceiver . guis ) do 528update_gui ( self ) 529end 530end 531 532local function on_180th_tick () 533if not storage . intergalactic_transceiver then 534return 535end 536for unit_number , entity in pairs ( storage . intergalactic_transceiver . inactive ) do 537if entity . valid then 538util . entity_flying_text ( entity , { "message.kr-transceiver-is-inactive" } , { r =1 } ) 539else 540storage . intergalactic_transceiver . inactive [ unit_number ] =nil 541end 542end 543end 544 545--- @class IntergalacticTransceiverHandler : event_handler 546local intergalactic_transceiver ={} 547 548function intergalactic_transceiver . on_init () 549-- Can be already initialised through remote interface 550if storage . intergalactic_transceiver then 551return 552end 553storage . intergalactic_transceiver ={ 554--- @type table<uint, IntergalacticTransceiverForceData> 555forces ={} , 556--- @type table<uint, IntergalacticTransceiverGuiData> 557guis ={} , 558-- Force index 559--- @type table<uint, boolean> 560in_cutscene ={} , 561-- Player index 562--- @type table<uint, boolean> 563player_in_cutscene ={} , 564--- @type table<uint, LuaEntity> 565inactive ={} , 566is_victory =true , 567} 568end 569 570intergalactic_transceiver . events ={ 571[ defines . events . on_built_entity ] =on_entity_built , 572[ defines . events . on_cutscene_cancelled ] =on_cutscene_finished , 573[ defines . events . on_cutscene_finished ] =on_cutscene_finished , 574[ defines . events . on_cutscene_waypoint_reached ] =on_cutscene_waypoint_reached , 575[ defines . events . on_entity_cloned ] =on_entity_built , 576[ defines . events . on_entity_died ] =on_entity_destroyed , 577[ defines . events . on_gui_opened ] =on_gui_opened , 578[ defines . events . on_player_mined_entity ] =on_entity_destroyed , 579[ defines . events . on_robot_built_entity ] =on_entity_built , 580[ defines . events . on_robot_mined_entity ] =on_entity_destroyed , 581[ defines . events . on_tick ] =on_tick , 582[ defines . events . script_raised_built ] =on_entity_built , 583[ defines . events . script_raised_destroy ] =on_entity_destroyed , 584[ defines . events . script_raised_revive ] =on_entity_built , 585[ defines . events . on_space_platform_built_entity ] =on_entity_built , 586[ defines . events . on_space_platform_mined_entity ] =on_entity_destroyed , 587} 588 589intergalactic_transceiver . on_nth_tick ={ 590[ 180 ] =on_180th_tick , 591} 592 593-- REMOTE 594 595remote . add_interface ( "kr-intergalactic-transceiver" , { 596get_is_victory =function () 597if storage . intergalactic_transceiver then 598intergalactic_transceiver . on_init () 599end 600return storage . intergalactic_transceiver . is_victory 601end , 602set_is_victory =function ( to_state ) 603if to_state == nil or type ( to_state ) ~= "boolean" then 604error ( "`to_state` must be a boolean" ) 605end 606if not storage . intergalactic_transceiver then 607intergalactic_transceiver . on_init () 608end 609storage . intergalactic_transceiver . is_victory =to_state 610end , 611} ) 612 613flib_gui . add_handlers ( { 614transceiver_on_window_closed =on_gui_window_closed , 615transceiver_on_activate_button_clicked =on_gui_activate_button_clicked , 616} ) 617 618return intergalactic_transceiver