z_mirror/Krastorio2
git clone https://git.raiguard.me/z_mirror/Krastorio2
1bad270e
trunk
1--- @class BeamData 2--- @field beam LuaEntity 3--- @field beam_number uint64 4--- @field target_data TargetData 5--- @field tower_data TowerData 6 7--- @class GridData 8--- @field absorber LuaEquipment? 9--- @field grid LuaEquipmentGrid 10 11--- @class TargetData 12--- @field connections TargetConnections 13--- @field entity LuaEntity 14--- @field full_tick uint64? 15--- @field grid_data GridData 16--- @field unit_number uint64 17 18--- @class TargetConnections 19--- @field by_beam table<number, ConnectionData> 20--- @field by_tower table<number, ConnectionData> 21 22--- @class TowerData 23--- @field entities TowerEntities 24--- @field tower_unit_number uint64 25--- @field turret_unit_number uint64 26 27--- @class TowerEntities 28--- @field collision LuaEntity 29--- @field tower LuaEntity 30--- @field turret LuaEntity 31 32local charging_rate_fraction =0.1 -- % of max energy per tick 33local cooldown =10 34local loss_multiplier =1.8 35local range =20 36local required_energy =10000000 -- 10 MW 37 38local absorber_buffer_capacity =prototypes . equipment [ "kr-energy-absorber-equipment" ]. energy_source . buffer_capacity 39 40-- TOWER 41-- The entity that is interacted with 42 43--- @param e EntityBuiltEvent 44local function on_entity_built ( e ) 45local entity =e . entity or e . destination 46if not entity or not entity . valid then 47return 48end 49local entity_name =entity . name 50 51-- Internal entities should not be cloned 52if 53e . name == defines . events . on_entity_cloned 54and ( entity_name == "kr-tesla-coil-turret" or entity_name == "kr-tesla-coil-collision" ) 55then 56entity . destroy () 57return 58end 59 60if entity_name ~= "kr-tesla-coil" then 61return 62end 63 64local surface =entity . surface 65local unit_number =entity . unit_number --[[@as uint]] 66 67local turret =surface . create_entity ( { 68name ="kr-tesla-coil-turret" , 69position =entity . position , 70force =game . forces [ "kr-internal-turrets" ], 71create_build_effect_smoke =false , 72raise_built =true , 73} ) 74if not turret or not turret . valid then 75game . ( 76"Building tesla failed due to AAI Programmable Vehicles; This tesla coil will not function. To fix this, change the 'Deadzone construction denial range' map setting to zero." 77) 78entity . disabled_by_script =true 79return 80end 81turret . destructible =false 82 83local collision_entity =surface . create_entity ( { 84name ="kr-tesla-coil-collision" , 85position =entity . position , 86force =entity . force , 87create_build_effect_smoke =false , 88raise_built =true , 89} ) 90assert ( collision_entity , "Tesla coil collision entity was destroyed" ) 91collision_entity . destructible =false 92 93--- @type TowerData 94local data ={ 95entities ={ 96collision =collision_entity , 97tower =entity , 98turret =turret , 99} , 100tower_unit_number =unit_number , 101turret_unit_number =turret . unit_number , 102} 103 104storage . tesla_coil . turrets [ data . turret_unit_number ] =data 105storage . tesla_coil . towers [ unit_number ] =data 106end 107 108--- @param entity LuaEntity 109local function destroy_if_valid ( entity ) 110if entity . valid then 111entity . destroy () 112end 113end 114 115--- @param e EntityDestroyedEvent 116local function on_entity_destroyed ( e ) 117local entity =e . entity 118if not entity or not entity . valid or entity . name ~= "kr-tesla-coil" then 119return 120end 121 122local unit_number =entity . unit_number 123assert ( unit_number ) 124local tower_data =storage . tesla_coil . towers [ unit_number ] 125if not tower_data then 126return 127end 128 129destroy_if_valid ( tower_data . entities . collision ) 130destroy_if_valid ( tower_data . entities . turret ) 131 132storage . tesla_coil . turrets [ tower_data . turret_unit_number ] =nil 133storage . tesla_coil . towers [ unit_number ] =nil 134end 135 136--- @param grid LuaEquipmentGrid 137--- @param equipment_name string 138--- @return LuaEquipment? 139local function grid_find_any ( grid , equipment_name ) 140for _ , equipment in pairs ( grid . equipment ) do 141if equipment . name == equipment_name then 142return equipment 143end 144end 145end 146 147-- TARGET 148-- An entity that will receive energy from a tesla coil 149 150--- @param target LuaEntity 151--- @return GridData? 152local function get_grid_data ( target ) 153--- @type LuaEquipmentGrid 154local grid 155if target . type == "character" then 156local armor_inventory =target . get_inventory ( defines . inventory . character_armor ) 157if armor_inventory and armor_inventory . valid then 158local armor =armor_inventory [ 1 ] 159if armor and armor . valid_for_read then 160grid =armor . grid 161end 162end 163else 164grid =target . grid 165end 166 167if grid then 168return { 169absorber =grid_find_any ( grid , "kr-energy-absorber-equipment" ), 170grid =grid , 171} 172end 173end 174 175--- Updates the absorber object in a target's equipment grid 176--- @param grid LuaEquipmentGrid 177local function update_target_grid ( grid ) 178for _ , target_data in pairs ( storage . tesla_coil . targets ) do 179local grid_data =target_data . grid_data 180if grid_data . grid . valid and grid_data . grid == grid then 181grid_data . absorber =grid_find_any ( grid , "kr-energy-absorber-equipment" ) 182end 183end 184end 185 186--- @param target LuaEntity 187--- @param tower_data TowerData 188local function add_target ( target , tower_data ) 189local target_unit_number =target . unit_number 190--- @cast target_unit_number -? 191local tower =tower_data . entities . tower 192if not tower . valid or tower . energy < required_energy then 193return 194end 195-- Check the target's equipment grid for an energy absorber 196local grid_data =get_grid_data ( target ) 197if grid_data and grid_data . absorber and grid_data . absorber . valid then 198--- @type TargetData 199local target_data ={ 200connections ={ 201--- @type table<number, ConnectionData> 202by_beam ={} , 203--- @type table<number, ConnectionData> 204by_tower ={} , 205} , 206entity =target , 207--- @type number? 208full_tick =nil , 209grid_data =grid_data , 210unit_number =target_unit_number , 211} 212storage . tesla_coil . targets [ target_unit_number ] =target_data 213return target_data 214end 215end 216 217--- @param target_unit_number number 218local function remove_target ( target_unit_number ) 219storage . tesla_coil . targets [ target_unit_number ] =nil 220end 221 222-- CONNECTION 223-- A connection between a tower and a target, comprising of a beam 224-- There can be unlimited connections per target 225 226--- @param target_data TargetData 227--- @param tower_data TowerData 228--- @return boolean 229local function add_connection ( target_data , tower_data ) 230-- Check if the absorber has space 231local absorber =target_data . grid_data . absorber 232if not absorber or not absorber . valid then 233remove_target ( target_data . unit_number ) 234return false 235end 236 237local tower =tower_data . entities . tower 238if not tower . valid then 239remove_target ( target_data . unit_number ) 240return false 241end 242 243if absorber . energy >= absorber_buffer_capacity then 244return false 245end 246 247-- Create beam entity 248local beam =tower_data . entities . tower . surface . create_entity ( { 249name ="kr-tesla-coil-electric-beam" , 250source =tower_data . entities . tower , 251source_offset ={ 0 , - 2.2 } , 252position =tower_data . entities . tower . position , 253target =target_data . entity , 254duration =0 , 255max_length =range , 256force =tower_data . entities . tower . force , 257raise_built =true , 258} ) 259if not beam then 260return false 261end 262local beam_number =script . register_on_object_destroyed ( beam ) 263 264storage . tesla_coil . beams [ beam_number ] ={ 265beam =beam , 266beam_number =beam_number , 267target_data =target_data , 268tower_data =tower_data , 269} 270 271--- @class ConnectionData 272local connection_data ={ 273beam =beam , 274beam_number =beam_number , 275tower_data =tower_data , 276} 277target_data . connections . by_beam [ beam_number ] =connection_data 278target_data . connections . by_tower [ tower_data . tower_unit_number ] =connection_data 279 280return true 281end 282 283--- @param target_data TargetData 284--- @param tower_data TowerData 285local function remove_connection ( target_data , tower_data ) 286local connection_data =target_data . connections . by_tower [ tower_data . tower_unit_number ] 287 288-- Destroy beam if it still exists 289if connection_data . beam . valid then 290connection_data . beam . destroy () 291end 292 293local beam_number =connection_data . beam_number 294storage . tesla_coil . beams [ beam_number ] =nil 295 296target_data . connections . by_beam [ beam_number ] =nil 297target_data . connections . by_tower [ tower_data . tower_unit_number ] =nil 298 299if table_size ( target_data . connections . by_beam ) == 0 then 300remove_target ( target_data . unit_number ) 301end 302end 303 304--- @param target_data TargetData 305--- @param tower_data TowerData 306local function update_connection ( target_data , tower_data ) 307local absorber =target_data . grid_data . absorber 308 309-- Check if the tower is powered 310if not absorber or not absorber . valid or tower_data . entities . tower . energy < required_energy then 311remove_connection ( target_data , tower_data ) 312return 313end 314 315local energy =absorber . energy 316if energy < absorber . max_energy then 317-- Calculate how much to add 318local to_add =charging_rate_fraction * absorber . max_energy 319local result =energy + to_add 320local tower =tower_data . entities . tower 321 322if result >= absorber . max_energy then 323absorber . energy =absorber . max_energy 324target_data . full_tick =game . tick 325else 326absorber . energy =result 327target_data . full_tick =nil 328end 329 330tower . energy =tower . energy - ( to_add * loss_multiplier ) 331elseif target_data . full_tick and target_data . full_tick + cooldown <= game . tick then 332remove_connection ( target_data , tower_data ) 333end 334end 335 336--- @param target LuaEntity 337--- @param turret LuaEntity 338local function process_turret_fire ( target , turret ) 339local tower_data =storage . tesla_coil . turrets [ turret . unit_number ] 340if not tower_data then 341return 342end 343 344local target_data =storage . tesla_coil . targets [ target . unit_number ] 345if not target_data then 346target_data =add_target ( target , tower_data ) 347end 348 349if target_data then 350-- Just in case (#182) 351if not target_data . entity . valid then 352target_data . entity =target 353end 354local connection =target_data . connections . by_tower [ tower_data . tower_unit_number ] 355if connection or add_connection ( target_data , tower_data ) then 356update_connection ( target_data , tower_data ) 357end 358end 359end 360 361-- EVENTS 362 363--- @param e EventData.on_object_destroyed 364local function on_object_destroyed ( e ) 365local beam_data =storage . tesla_coil . beams [ e . registration_number ] 366if beam_data then 367remove_connection ( beam_data . target_data , beam_data . tower_data ) 368end 369end 370 371--- @param e EventData.on_equipment_inserted 372local function on_equipment_inserted ( e ) 373if not e . equipment . valid or e . equipment . name ~= "kr-energy-absorber-equipment" then 374return 375end 376update_target_grid ( e . grid ) 377end 378 379--- @param e EventData.on_equipment_removed 380local function on_equipment_removed ( e ) 381if e . equipment == "kr-energy-absorber-equipment" then 382update_target_grid ( e . grid ) 383end 384end 385 386--- @param e EventData.on_script_trigger_effect 387local function on_script_trigger_effect ( e ) 388if e . effect_id == "kr-tesla-coil-trigger" then 389process_turret_fire ( e . target_entity , e . source_entity ) 390end 391end 392 393--- @class TeslaCoilHandler : event_handler 394local tesla_coil ={} 395 396function tesla_coil . on_init () 397storage . tesla_coil ={ 398--- @type table<number, BeamData> 399beams ={} , 400--- @type table<number, TowerData> 401turrets ={} , 402--- @type table<number, TowerData> 403towers ={} , 404--- @type table<number, TargetData> 405targets ={} , 406} 407end 408 409tesla_coil . events ={ 410[ defines . events . on_built_entity ] =on_entity_built , 411[ defines . events . on_entity_cloned ] =on_entity_built , 412[ defines . events . on_entity_died ] =on_entity_destroyed , 413[ defines . events . on_equipment_inserted ] =on_equipment_inserted , 414[ defines . events . on_equipment_removed ] =on_equipment_removed , 415[ defines . events . on_object_destroyed ] =on_object_destroyed , 416[ defines . events . on_player_mined_entity ] =on_entity_destroyed , 417[ defines . events . on_robot_built_entity ] =on_entity_built , 418[ defines . events . on_robot_mined_entity ] =on_entity_destroyed , 419[ defines . events . on_script_trigger_effect ] =on_script_trigger_effect , 420[ defines . events . script_raised_built ] =on_entity_built , 421[ defines . events . script_raised_destroy ] =on_entity_destroyed , 422[ defines . events . script_raised_revive ] =on_entity_built , 423[ defines . events . on_space_platform_built_entity ] =on_entity_built , 424[ defines . events . on_space_platform_mined_entity ] =on_entity_destroyed , 425} 426 427return tesla_coil 428 429--[[ 430-- Quick testing code 431event.register(remote.call("EditorExtensions", "debug_world_ready_event"), function() 432local surface = game.surfaces.nauvis 433 434local coils = { 435{ x = -9.5, y = -9.5 }, 436{ x = 10.5, y = -9.5 }, 437{ x = -9.5, y = 10.5 }, 438{ x = 10.5, y = 10.5 }, 439} 440for _, position in pairs(coils) do 441surface.create_entity({ 442name = "kr-tesla-coil", 443direction = defines.direction.north, 444position = position, 445force = game.forces.player, 446raise_built = true, 447create_build_effect_smoke = false, 448}) 449end 450surface.create_entity({ 451name = "ee-infinity-accumulator-primary-output", 452position = { x = -1, y = -9 }, 453force = game.forces.player, 454raise_built = true, 455create_build_effect_smoke = false, 456}) 457surface.create_entity({ 458name = "ee-super-substation", 459position = { x = 1, y = -9 }, 460force = game.forces.player, 461raise_built = true, 462create_build_effect_smoke = false, 463}) 464 465local character = game.get_player(1).character 466local armor_inventory = character.get_inventory(defines.inventory.character_armor) 467if armor_inventory and armor_inventory.valid then 468local armor = armor_inventory[1] 469if armor and armor.valid_for_read then 470armor.grid.put({ name = "kr-energy-absorber-equipment", position = { 5, 5 } }) 471end 472end 473end) 474]]