#using scripts\codescripts\struct; #using scripts\shared\array_shared; #using scripts\shared\hud_util_shared; #using scripts\shared\system_shared; #using scripts\shared\util_shared; #using scripts\shared\clientfield_shared; #using scripts\mp\gametypes\_spawning; #using scripts\mp\_util; #using scripts\mp\killstreaks\_qrdrone; #using scripts\mp\killstreaks\_rcbomb; #using scripts\shared\vehicle_death_shared; #precache( "fx", "_t6/vehicle/vexplosion/fx_vexplode_helicopter_exp_mp" ); //#precache( "fx", "_t6/vehicle/vexplosion/fx_vexplode_helicopter_exp_mp" ); #using_animtree ( "mp_vehicles" ); #namespace vehicle; function autoexec __init__sytem__() { system::register("vehicle",&__init__,undefined,undefined); } function __init__() { SetDvar( "scr_veh_cleanupdebugprint", "0" ); // We can control whether vehicle occupants can take damage (such as from bullets) SetDvar( "scr_veh_driversarehidden", "1" ); SetDvar( "scr_veh_driversareinvulnerable", "1" ); // "cleanuptime" must pass before veh is considered for cleanup (either by abandonment or drift) // these are given in seconds SetDvar( "scr_veh_alive_cleanuptimemin", "119" ); SetDvar( "scr_veh_alive_cleanuptimemax", "120" ); SetDvar( "scr_veh_dead_cleanuptimemin", "20" ); SetDvar( "scr_veh_dead_cleanuptimemax", "30" ); // These cleanuptime factors only modify cleanup due to abandonment or drift, // i.e. the cleanup of an alive vehicle. Once the vehicle dies and becomes a // husk, only the cleanuptimemin and cleanuptimemax values apply. SetDvar( "scr_veh_cleanuptime_dmgfactor_min", "0.33" ); SetDvar( "scr_veh_cleanuptime_dmgfactor_max", "1.0" ); SetDvar( "scr_veh_cleanuptime_dmgfactor_deadtread", "0.25" ); // decrease the damage factor ( which scales wait time ) by this much for each tread that has been completely destroyed SetDvar( "scr_veh_cleanuptime_dmgfraction_curve_begin", "0.0" ); // this is the amount of damage at which the damage factor affecting wait time will be at max SetDvar( "scr_veh_cleanuptime_dmgfraction_curve_end", "1.0" ); // this is the amount of damage at which the damage factor affecting wait time will be at min SetDvar( "scr_veh_cleanupabandoned", "1" ); // Decide whether to cleanup abandoned vehicles SetDvar( "scr_veh_cleanupdrifted", "1" ); // Decide whether to cleanup drifted vehicles SetDvar( "scr_veh_cleanupmaxspeedmph", "1" ); // If it's going slower than this, then the vehicle can be cleaned up SetDvar( "scr_veh_cleanupmindistancefeet", "75" ); // If it's at least this far from its original position, then the vehicle can be cleaned up SetDvar( "scr_veh_waittillstoppedandmindist_maxtime", "10" ); SetDvar( "scr_veh_waittillstoppedandmindist_maxtimeenabledistfeet", "5" ); // Respawn time is the wait between cleaning up an old vehicle // and spawning a new replacement vehicle. // these are given in seconds SetDvar( "scr_veh_respawnafterhuskcleanup", "1" ); // if true, vehicle respawn kicks off after husk cleanup. otherwise, kicks off after live vehicle is killed SetDvar( "scr_veh_respawntimemin", "50" ); SetDvar( "scr_veh_respawntimemax", "90" ); SetDvar( "scr_veh_respawnwait_maxiterations", "30" ); SetDvar( "scr_veh_respawnwait_iterationwaitseconds", "1" ); SetDvar( "scr_veh_disablerespawn", "0" );//turn off the default respawning system, the game mode will need to take care of respawning us. SetDvar( "scr_veh_disableoverturndamage", "0" );// turn on damage when the vehicle is overturned by default // Explodes when killed or when dvar scr_veh_explode_on_cleanup is not zero SetDvar( "scr_veh_explosion_spawnfx", "1" ); // Whether or not to create an explosion FX when the vehicle is destroyed SetDvar( "scr_veh_explosion_doradiusdamage", "1" ); // whether or not to hurt nearby entities when the vehicle is destroyed SetDvar( "scr_veh_explosion_radius", "256" ); SetDvar( "scr_veh_explosion_mindamage", "20" ); SetDvar( "scr_veh_explosion_maxdamage", "200" ); SetDvar( "scr_veh_ondeath_createhusk", "1" ); // Whether or not to init a burnt-out husk for players to use as cover when the vehicle is destroyed SetDvar( "scr_veh_ondeath_usevehicleashusk", "1" ); // if true, husks will be the vehicle entities themselves. otherwise, husk will be a spawn("script_model") SetDvar( "scr_veh_explosion_husk_forcepointvariance", "30" ); // controls the "spin" when the vehicle husk is launched SetDvar( "scr_veh_explosion_husk_horzvelocityvariance", "25" ); SetDvar( "scr_veh_explosion_husk_vertvelocitymin", "100" ); SetDvar( "scr_veh_explosion_husk_vertvelocitymax", "200" ); SetDvar( "scr_veh_explode_on_cleanup", "1" ); ///< if not "exploding", then "disappearing". this only affect auto-cleanup case, not when vehicle is destroyed. destroyed vehicle always explodes SetDvar( "scr_veh_disappear_maxwaittime", "60" ); ///< # seconds at which test for "player too close" and "player can see" will timeout and the husk will be cleaned up anyway SetDvar( "scr_veh_disappear_maxpreventdistancefeet", "30" ); ///< If vehicle closer to the player than this, then it won't be cleaned up SetDvar( "scr_veh_disappear_maxpreventvisibilityfeet", "150" ); ///< Only apply visibility check if vehicle is closer to the player than this SetDvar( "scr_veh_health_tank", "1350" ); // default health level.vehicle_drivers_are_invulnerable = GetDvarint( "scr_veh_driversareinvulnerable" ); level.onEjectOccupants =&vehicle_eject_all_occupants;// Callback function to allow custom code when a vehicle is overturned and needs to eject occupants level.vehicleHealths[ "panzer4_mp" ] = 2600; level.vehicleHealths[ "t34_mp" ] = 2600; SetDvar( "scr_veh_health_jeep", "700" ); if ( init_vehicle_entities() ) { level.vehicle_explosion_effect = "_t6/vehicle/vexplosion/fx_vexplode_helicopter_exp_mp"; // kaboom! level.veh_husk_models = []; level.veh_husk_effects = []; if ( isdefined( level.use_new_veh_husks ) ) { level.veh_husk_models[ "t34_mp" ] = "veh_t34_destroyed_mp"; //level.veh_husk_models[ "panzer4_mp" ] = "veh_panzer4_destroyed_mp"; } if ( isdefined( level.onAddVehicleHusks) ) { [[level.onAddVehicleHusks]](); } //level._effect["tanksquish"] = "_t6/maps/see2/fx_body_blood_splat";//TODO T7 - contact FX team to get proper replacement } // for clientscripts chopper_player_get_on_gun = %int_huey_gunner_on; chopper_door_open = %v_huey_door_open; chopper_door_open_state = %v_huey_door_open_state; chopper_door_closed_state = %v_huey_door_close_state; killbrushes = GetEntArray( "water_killbrush", "targetname" ); foreach( brush in killbrushes ) { brush thread water_killbrush_think(); } return; } function water_killbrush_think() { for( ;; ) { self waittill( "trigger", entity ); if ( isdefined( entity ) ) { if ( isdefined( entity.targetname ) ) { if ( entity.targetname == "rcbomb" ) { entity notify( "rcbomb_shutdown" ); } else if ( entity.targetname == "talon" && !( isdefined( entity.dead ) && entity.dead ) ) { entity notify( "death" ); } } if ( isdefined( entity.heliType ) && entity.heliType == "qrdrone" ) { entity qrdrone::QRDrone_force_destroy(); } } } } function initialize_vehicle_damage_effects_for_level() { //###stefan $TODO it would be great if these came from a GDT somewhere instead of being hard-coded in scripts /* Vehicle damage effects are stored in level.vehicles_damage_states[] where the array key is the GDT vehicle-type name for the particular vehicle. For example, "sherman_mp" --> level.vehicles_damage_states["sherman_mp"] The default MP vehicle is "defaultvehicle_mp". This is used for vehicles who do not have a custom entry in the vehicles_damage_states[] array. Here we setup the vehicle damage effects response behaviors for all vehicles, using a data structure layout as follows: struct s_vehicle_damage_state { float health_percentage; // apply effect(s) from effect_array[] when vehicle health is <= this amount struct s_effect_array { id damage_effect; // this is the damage effect to apply string sound_effect; // this is the sound effect to play string vehicle_tag; // this is the tag on the vehicle where the effect should be applied } effect_array[]; }; So, when a vehicle's health percentage becomes <= s_vehicle_damage_effects.health_percentage, all the effects listed in s_vehicle_damage_effects.effect_array[] are applied as defined */ // damage indices k_mild_damage_index= 0; k_moderate_damage_index= 1; k_severe_damage_index= 2; k_total_damage_index= 3; // health_percentage constants k_mild_damage_health_percentage= 0.85; k_moderate_damage_health_percentage= 0.55; k_severe_damage_health_percentage= 0.35; k_total_damage_health_percentage= 0; level.k_mild_damage_health_percentage = k_mild_damage_health_percentage; level.k_moderate_damage_health_percentage = k_moderate_damage_health_percentage; level.k_severe_damage_health_percentage = k_severe_damage_health_percentage; level.k_total_damage_health_percentage = k_total_damage_health_percentage; level.vehicles_damage_states= []; level.vehicles_husk_effects = []; level.vehicles_damage_treadfx = []; // setup the default vehicle vehicle_name= get_default_vehicle_name(); { level.vehicles_damage_states[vehicle_name]= []; level.vehicles_damage_treadfx[vehicle_name] = []; // mild damage { level.vehicles_damage_states[vehicle_name][k_mild_damage_index]= SpawnStruct(); level.vehicles_damage_states[vehicle_name][k_mild_damage_index].health_percentage= k_mild_damage_health_percentage; level.vehicles_damage_states[vehicle_name][k_mild_damage_index].effect_array= []; // effect '0' - placed @ "tag_origin" level.vehicles_damage_states[vehicle_name][k_mild_damage_index].effect_array[0]= SpawnStruct(); level.vehicles_damage_states[vehicle_name][k_mild_damage_index].effect_array[0].damage_effect= "_t6/vehicle/vfire/fx_tank_sherman_smldr"; // smoldering (smoke puffs)//TODO T7 - contact FX team to get proper replacement level.vehicles_damage_states[vehicle_name][k_mild_damage_index].effect_array[0].sound_effect= undefined; level.vehicles_damage_states[vehicle_name][k_mild_damage_index].effect_array[0].vehicle_tag= "tag_origin"; } // moderate damage { level.vehicles_damage_states[vehicle_name][k_moderate_damage_index]= SpawnStruct(); level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].health_percentage= k_moderate_damage_health_percentage; level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].effect_array= []; // effect '0' - placed @ "tag_origin" level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].effect_array[0]= SpawnStruct(); level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].effect_array[0].damage_effect= "_t6/vehicle/vfire/fx_vfire_med_12"; // flames & more smoke//TODO T7 - contact FX team to get proper replacement level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].effect_array[0].sound_effect= undefined; level.vehicles_damage_states[vehicle_name][k_moderate_damage_index].effect_array[0].vehicle_tag= "tag_origin"; } // severe damage { level.vehicles_damage_states[vehicle_name][k_severe_damage_index]= SpawnStruct(); level.vehicles_damage_states[vehicle_name][k_severe_damage_index].health_percentage= k_severe_damage_health_percentage; level.vehicles_damage_states[vehicle_name][k_severe_damage_index].effect_array= []; // effect '0' - placed @ "tag_origin" level.vehicles_damage_states[vehicle_name][k_severe_damage_index].effect_array[0]= SpawnStruct(); level.vehicles_damage_states[vehicle_name][k_severe_damage_index].effect_array[0].damage_effect= "_t6/vehicle/vfire/fx_vfire_sherman"; // pillar of smoke//TODO T7 - contact FX team to get proper replacement level.vehicles_damage_states[vehicle_name][k_severe_damage_index].effect_array[0].sound_effect= undefined; level.vehicles_damage_states[vehicle_name][k_severe_damage_index].effect_array[0].vehicle_tag= "tag_origin"; } // total damage { level.vehicles_damage_states[vehicle_name][k_total_damage_index]= SpawnStruct(); level.vehicles_damage_states[vehicle_name][k_total_damage_index].health_percentage= k_total_damage_health_percentage; level.vehicles_damage_states[vehicle_name][k_total_damage_index].effect_array= []; // effect '0' - placed @ "tag_origin" level.vehicles_damage_states[vehicle_name][k_total_damage_index].effect_array[0]= SpawnStruct(); level.vehicles_damage_states[vehicle_name][k_total_damage_index].effect_array[0].damage_effect= "_t6/vehicle/vexplosion/fx_vexplode_helicopter_exp_mp"; level.vehicles_damage_states[vehicle_name][k_total_damage_index].effect_array[0].sound_effect= "vehicle_explo"; // kaboom! level.vehicles_damage_states[vehicle_name][k_total_damage_index].effect_array[0].vehicle_tag= "tag_origin"; } { default_husk_effects = SpawnStruct(); default_husk_effects.damage_effect = undefined; default_husk_effects.sound_effect = undefined; default_husk_effects.vehicle_tag = "tag_origin"; level.vehicles_husk_effects[ vehicle_name ] = default_husk_effects; } } // _t34::build_damage_states(); // _panzeriv::build_damage_states(); return; } //string function get_vehicle_name( vehicle) { name= ""; if (isdefined(vehicle)) { if (isdefined(vehicle.vehicletype)) { name= vehicle.vehicletype; } } return name; } //string function get_default_vehicle_name() { return "defaultvehicle_mp"; } //string function get_vehicle_name_key_for_damage_states( vehicle) { vehicle_name= get_vehicle_name(vehicle); if ( !isdefined(level.vehicles_damage_states) || !isdefined(level.vehicles_damage_states[vehicle_name])) { vehicle_name= get_default_vehicle_name(); } return vehicle_name; } //int function get_vehicle_damage_state_index_from_health_percentage( vehicle) { if ( !isdefined( level.vehicles_damage_states ) ) return -1; damage_state_index= -1; vehicle_name= get_vehicle_name_key_for_damage_states(); for (test_index= 0; test_index0) { previous_damage_state_index= get_vehicle_damage_state_index_from_health_percentage(vehicle); vehicle.current_health_percentage= vehicle.health/vehicle.initial_state.health; current_damage_state_index= get_vehicle_damage_state_index_from_health_percentage(vehicle); // if we have reached a new damage state, play associated effects if (previous_damage_state_index!=current_damage_state_index) { vehicle notify ( "damage_state_changed" ); if (previous_damage_state_index<0) { start_damage_state_index= 0; } else { start_damage_state_index= previous_damage_state_index+1; } play_damage_state_effects(vehicle, start_damage_state_index, current_damage_state_index); if ( vehicle.health <= 0 ) { vehicle kill_vehicle(attacker); } } } } function play_damage_state_effects( vehicle, start_damage_state_index, end_damage_state_index) { vehicle_name= get_vehicle_name_key_for_damage_states( vehicle ); // play effects for all damage states from start_damage_state_index --> end_damage_state_index for (damage_state_index= start_damage_state_index; damage_state_index<=end_damage_state_index; damage_state_index++) { for (effect_index= 0; effect_index 0 ) { // if the specified effect was loaded, play it on the associated vehicle tag if ( isdefined( effects.damage_effect ) ) { PlayFxOnTag( effects.damage_effect, self, effects.vehicle_tag ); } wait( waitTime ); } } function init_vehicle_entities() { vehicles = getentarray( "script_vehicle", "classname" ); array::thread_all( vehicles,&init_original_vehicle ); if ( isdefined( vehicles ) ) { return vehicles.size; } return 0; } function precache_vehicles() { // Last time I tested, this is actually not called from anywhere } function register_vehicle() { // Register vehicle husk if ( !isdefined( level.vehicles_list ) ) { level.vehicles_list = []; } level.vehicles_list[ level.vehicles_list.size ] = self; } // Before spawning a new vehicle, we need to bookkeep our list // of instantiated vehicles. If spawning the new vehicle would cause us to go // over the max vehicle limit, then we need to force-delete // the oldest (dead) ones to make room. function manage_vehicles() { if ( !isdefined( level.vehicles_list ) ) { return true; } else { MAX_VEHICLES = GetMaxVehicles(); { // Consolidate array - Husks might have been cleaned up in the interim newArray = []; for ( i = 0; i < level.vehicles_list.size; i++ ) { if ( isdefined( level.vehicles_list[ i ] ) ) { newArray[ newArray.size ] = level.vehicles_list[ i ]; } } level.vehicles_list = newArray; } // make sure there's room for one more vehiclesToDelete = ( level.vehicles_list.size + 1 ) - MAX_VEHICLES; if ( vehiclesToDelete > 0 ) { newArray = []; for ( i = 0; i < level.vehicles_list.size; i++ ) { vehicle = level.vehicles_list[ i ]; if ( vehiclesToDelete > 0 ) { // ".permanentlyRemoved" vehicles will never be really deleted if ( isdefined( vehicle.is_husk ) && !isdefined( vehicle.permanentlyRemoved ) ) { deleted = vehicle husk_do_cleanup(); if ( deleted ) { vehiclesToDelete--; continue; } } } newArray[ newArray.size ] = vehicle; } level.vehicles_list = newArray; } return level.vehicles_list.size < MAX_VEHICLES; } } /@ "Name: init_vehicle( )" "Summary: Initializes a vehicle entity when the game starts" "Module: Vehicle" "Example: vehicle init_vehicle();" "SPMP: multiplayer" @/ function init_vehicle() { self register_vehicle(); // setting the tank health here so it is universal // we should do the same for the other vehicles if ( isdefined( level.vehicleHealths ) && isdefined( level.vehicleHealths[ self.vehicletype ] ) ) { self.maxhealth = level.vehicleHealths[ self.vehicletype ]; } else { self.maxhealth = GetDvarint( "scr_veh_health_tank"); /# println( "No health specified for vehicle type "+self.vehicletype+"! Using default..." ); #/ } self.health = self.maxhealth; self vehicle_record_initial_values(); self init_vehicle_threads(); system::wait_till( "spawning" ); // add the influencer to block all teams // do not spawn all team influencer -- per design // self spawning::create_entity_masked_enemy_influencer( "vehicle", 0 ); } function initialize_vehicle_damage_state_data() { if (self.initial_state.health>0) { self.current_health_percentage= self.health/self.initial_state.health; self.previous_health_percentage= self.health/self.initial_state.health; } else { self.current_health_percentage= 1; self.previous_health_percentage= 1; } return; } function init_original_vehicle() { // this is a temporary hack trying to resolve the "!cent->pose.fx.effect" // crash bug. Basically I think the bug is caused by deleteing the original // tanks that were in the bsp self.original_vehicle = true; self init_vehicle(); } // debug code below is for a simple vehicle health bar function vehicle_wait_player_enter_t() { self endon( "transmute" ); self endon( "death" ); self endon( "delete" ); while( 1 ) { self waittill( "enter_vehicle", player ); player thread player_wait_exit_vehicle_t(); player player_update_vehicle_hud( true, self ); } } function player_wait_exit_vehicle_t() { // Don't endon "death". Player will receive // "exit_vehicle" message when killed in a vehicle self endon( "disconnect" ); self waittill( "exit_vehicle", vehicle ); self player_update_vehicle_hud( false, vehicle ); } function vehicle_wait_damage_t() { self endon( "transmute" ); self endon( "death" ); self endon( "delete" ); while( 1 ) { self waittill ( "damage" ); occupants = self GetVehOccupants(); if( isdefined( occupants ) ) { for( i = 0; i < occupants.size; i++ ) { occupants[i] player_update_vehicle_hud( true, self ); } } } } function player_update_vehicle_hud( show, vehicle ) { if( show ) { if ( !isdefined( self.vehicleHud ) ) { self.vehicleHud = hud::createBar( (1, 1, 1), 64, 16 ); self.vehicleHud hud::setPoint( "CENTER", "BOTTOM", 0, -40 ); self.vehicleHud.alpha = 0.75; } self.vehicleHud hud::updateBar( vehicle.health / vehicle.initial_state.health ); } else { if ( isdefined( self.vehicleHud ) ) { self.vehicleHud hud::destroyElem(); } } if ( GetDvarString( "scr_vehicle_healthnumbers" )!= "" ) { if ( GetDvarint( "scr_vehicle_healthnumbers" )!= 0 ) { if( show ) { if ( !isdefined( self.vehicleHudHealthNumbers ) ) { self.vehicleHudHealthNumbers = hud::createFontString( "default", 2.0 ); self.vehicleHudHealthNumbers hud::setParent( self.vehicleHud ); self.vehicleHudHealthNumbers hud::setPoint( "LEFT", "RIGHT", 8, 0 ); self.vehicleHudHealthNumbers.alpha = 0.75; self.vehicleHudHealthNumbers.hideWhenInMenu = false; self.vehicleHudHealthNumbers.archived = false; } self.vehicleHudHealthNumbers setValue( vehicle.health ); } else { if ( isdefined( self.vehicleHudHealthNumbers ) ) { self.vehicleHudHealthNumbers hud::destroyElem(); } } } } } function init_vehicle_threads() { self thread vehicle_abandoned_by_drift_t(); self thread vehicle_abandoned_by_occupants_t(); self thread vehicle_damage_t(); self thread vehicle_ghost_entering_occupants_t(); self thread vehicle_recycle_spawner_t(); self thread vehicle_disconnect_paths(); // enable debug vehicle health bar if( isdefined( level.enableVehicleHealthbar ) && level.enableVehicleHealthbar ) { self thread vehicle_wait_player_enter_t(); self thread vehicle_wait_damage_t(); } self thread vehicle_wait_tread_damage(); self thread vehicle_overturn_eject_occupants(); if (GetDvarint( "scr_veh_disableoverturndamage") == 0) { self thread vehicle_overturn_suicide(); } /# self thread cleanup_debug_print_t(); self thread cleanup_debug_print_clearmsg_t(); #/ } /@ "Name: build_template( , , )" "Summary: called in individual vehicle file - mandatory to call this in all vehicle files at the top!" "Module: vehicle_build( vehicle.gsc )" "CallOn: " "MandatoryArg: : vehicle type to set" "MandatoryArg: : model to set( this is usually generated by the level script )" "OptionalArg: : this overrides the type, used for copying a vehicle script" "Example: build_template( "bmp", model, type );" "SPMP: singleplayer" @/ function build_template( type, model, typeoverride ) { if( isdefined( typeoverride ) ) type = typeoverride; if( !isdefined( level.vehicle_death_fx ) ) level.vehicle_death_fx = []; if( !isdefined( level.vehicle_death_fx[ type ] ) ) level.vehicle_death_fx[ type ] = []; // can have overrides level.vehicle_compassicon[ type ] = false; level.vehicle_team[ type ] = "axis"; level.vehicle_life[ type ] = 999; level.vehicle_hasMainTurret[ model ] = false; level.vehicle_mainTurrets[ model ] = []; level.vtmodel = model; level.vttype = type; } /@ "Name: build_rumble( , , , , , )" "Summary: called in individual vehicle file - define amount of radius damage to be set on each vehicle" "Module: vehicle_build( vehicle.gsc )" "CallOn: " "MandatoryArg: : rumble asset" "MandatoryArg: : scale" "MandatoryArg: : duration" "MandatoryArg: : radius" "MandatoryArg: : time to wait between rumbles" "MandatoryArg: : random amount of time to add to basetime" "Example: build_rumble( "tank_rumble", 0.15, 4.5, 600, 1, 1 );" "SPMP: singleplayer" @/ function build_rumble( rumble, scale, duration, radius, basetime, randomaditionaltime ) { if( !isdefined( level.vehicle_rumble ) ) level.vehicle_rumble = []; struct = build_quake( scale, duration, radius, basetime, randomaditionaltime ); assert( isdefined( rumble ) ); struct.rumble = rumble; level.vehicle_rumble[ level.vttype ] = struct; } function build_quake( scale, duration, radius, basetime, randomaditionaltime ) { struct = spawnstruct(); struct.scale = scale; struct.duration = duration; struct.radius = radius; if( isdefined( basetime ) ) struct.basetime = basetime; if( isdefined( randomaditionaltime ) ) struct.randomaditionaltime = randomaditionaltime; return struct; } /@ "Name: build_exhaust( )" "Summary: called in individual vehicle file - assign an exhaust effect to this vehicle!" "Module: vehicle_build( vehicle.gsc )" "CallOn: " "MandatoryArg: : exhaust effect in string format" "Example: build_exhaust( "_t6/vehicle/exhaust/fx_exhaust_tank" );" "SPMP: singleplayer" @/ function build_exhaust( effect ) { level.vehicle_exhaust[ level.vtmodel ] = effect; } function cleanup_debug_print_t() { self endon( "transmute" ); self endon( "death" ); self endon( "delete" ); /# while ( 1 ) { if ( isdefined( self.debug_message ) && GetDvarint( "scr_veh_cleanupdebugprint" ) != 0 ) { Print3d( self.origin + ( 0, 0, 150 ), self.debug_message, ( 0, 1, 0 ), 1, 1, 1 ); } wait 0.01; } #/ } function cleanup_debug_print_clearmsg_t() { self endon( "transmute" ); self endon( "death" ); self endon( "delete" ); /# while ( 1 ) { self waittill( "enter_vehicle" ); self.debug_message = undefined; } #/ } function cleanup_debug_print( message ) { /# self.debug_message = message; #/ } // ===================================================================================== // Abandonment Code function vehicle_abandoned_by_drift_t() { self endon( "transmute" ); self endon( "death" ); self endon( "delete" ); self wait_then_cleanup_vehicle( "Drift Test", "scr_veh_cleanupdrifted" ); } function vehicle_abandoned_by_occupants_timeout_t() { self endon( "transmute" ); self endon( "death" ); self endon( "delete" ); self wait_then_cleanup_vehicle( "Abandon Test", "scr_veh_cleanupabandoned" ); } function wait_then_cleanup_vehicle( test_name, cleanup_dvar_name ) { self endon( "enter_vehicle" ); self wait_until_severely_damaged(); self do_alive_cleanup_wait( test_name ); self wait_for_vehicle_to_stop_outside_min_radius(); // unoccupied vehicle can be being pushed! self cleanup( test_name, cleanup_dvar_name,&vehicle_recycle ); } function wait_until_severely_damaged() { while ( 1 ) { health_percentage = self.health / self.initial_state.health; if ( isdefined( level.k_severe_damage_health_percentage ) ) // initially called before this is set up. { self cleanup_debug_print( "Damage Test: Still healthy - (" + health_percentage + " >= " + level.k_severe_damage_health_percentage + ") and working treads"); } else { self cleanup_debug_print( "Damage Test: Still healthy and working treads"); } self waittill( "damage" ); health_percentage = self.health / self.initial_state.health; if ( isdefined( level.k_severe_damage_health_percentage ) && health_percentage < level.k_severe_damage_health_percentage ) break; } } function get_random_cleanup_wait_time( state ) { varnamePrefix = "scr_veh_" + state + "_cleanuptime"; minTime = getdvarfloat( varnamePrefix + "min" ); maxTime = getdvarfloat( varnamePrefix + "max" ); if ( maxTime > minTime ) { return RandomFloatRange( minTime, maxTime ); } else { return maxTime; } } function do_alive_cleanup_wait( test_name ) { initialRandomWaitSeconds = get_random_cleanup_wait_time( "alive" ); secondsWaited = 0.0; seconds_per_iteration = 1.0; while ( true ) { curve_begin = GetDvarfloat( "scr_veh_cleanuptime_dmgfraction_curve_begin" ); curve_end = GetDvarfloat( "scr_veh_cleanuptime_dmgfraction_curve_end" ); factor_min = GetDvarfloat( "scr_veh_cleanuptime_dmgfactor_min" ); factor_max = GetDvarfloat( "scr_veh_cleanuptime_dmgfactor_max" ); treadDeadDamageFactor = GetDvarfloat( "scr_veh_cleanuptime_dmgfactor_deadtread" ); damageFraction = 0.0; if ( self is_vehicle() ) { damageFraction = ( self.initial_state.health - self.health ) / self.initial_state.health; } else // is husk { damageFraction = 1.0; } damageFactor = 0.0; if ( damageFraction <= curve_begin ) { damageFactor = factor_max; } else if ( damageFraction >= curve_end ) { damageFactor = factor_min; } else { dydx = ( factor_min - factor_max ) / ( curve_end - curve_begin ); damageFactor = factor_max + ( damageFraction - curve_begin ) * dydx; } totalSecsToWait = initialRandomWaitSeconds * damageFactor; if ( secondsWaited >= totalSecsToWait ) { break; } self cleanup_debug_print( test_name + ": Waiting " + ( totalSecsToWait - secondsWaited ) + "s" ); wait seconds_per_iteration; secondsWaited = secondsWaited + seconds_per_iteration; } } function do_dead_cleanup_wait( test_name ) { total_secs_to_wait = get_random_cleanup_wait_time( "dead" ); seconds_waited = 0.0; seconds_per_iteration = 1.0; while ( seconds_waited < total_secs_to_wait ) { self cleanup_debug_print( test_name + ": Waiting " + ( total_secs_to_wait - seconds_waited ) + "s" ); wait seconds_per_iteration; seconds_waited = seconds_waited + seconds_per_iteration; } } function cleanup( test_name, cleanup_dvar_name, cleanup_func ) { keep_waiting = true; while ( keep_waiting ) { cleanupEnabled = !isdefined( cleanup_dvar_name ) || getdvarint( cleanup_dvar_name ) != 0; if ( cleanupEnabled != 0 ) { self [[cleanup_func]](); break; } keep_waiting = false; /# // Only in debug mode, we will keep looping in case someone turns the // cleanup dvar back on. self cleanup_debug_print( "Cleanup disabled for " + test_name + " ( dvar = " + cleanup_dvar_name + " )" ); wait 5.0; keep_waiting = true; #/ } } function vehicle_wait_tread_damage() { self endon( "death" ); self endon( "delete" ); vehicle_name= get_vehicle_name(self); while ( 1 ) { self waittill ( "broken", brokenNotify ); if ( brokenNotify == "left_tread_destroyed" ) { if ( isdefined( level.vehicles_damage_treadfx[vehicle_name] ) && isdefined( level.vehicles_damage_treadfx[vehicle_name][0] ) ) { self thread play_vehicle_effects( level.vehicles_damage_treadfx[vehicle_name][0], true ); } } else if ( brokenNotify == "right_tread_destroyed" ) { if ( isdefined( level.vehicles_damage_treadfx[vehicle_name] ) && isdefined( level.vehicles_damage_treadfx[vehicle_name][1] ) ) { self thread play_vehicle_effects( level.vehicles_damage_treadfx[vehicle_name][1], true ); } } } } function wait_for_vehicle_to_stop_outside_min_radius() { maxWaitTime = GetDvarfloat( "scr_veh_waittillstoppedandmindist_maxtime" ); iterationWaitSeconds = 1.0; maxWaitTimeEnableDistInches = 12 * GetDvarfloat( "scr_veh_waittillstoppedandmindist_maxtimeenabledistfeet" ); initialOrigin = self.initial_state.origin; for ( totalSecondsWaited = 0.0; totalSecondsWaited < maxWaitTime; totalSecondsWaited += iterationWaitSeconds ) { // We don't want to disappear it if someone is // currently pushing it with another vehicle speedMPH = self GetSpeedMPH(); cutoffMPH = GetDvarfloat( "scr_veh_cleanupmaxspeedmph" ); if ( speedMPH > cutoffMPH ) { cleanup_debug_print( "(" + ( maxWaitTime - totalSecondsWaited ) + "s) Speed: " + speedMPH + ">" + cutoffMPH ); } else { break; /* initialOriginOnTerrain = ( initialOrigin[0], initialOrigin[1], 0 ); originOnTerrain = ( self.origin[0], self.origin[1], 0 ); moveDistInches = Distance( initialOriginOnTerrain, originOnTerrain ); cutoffInches = 12 * GetDvarint( "scr_veh_cleanupmindistancefeet" ); if ( moveDistInches < cutoffInches ) { cleanup_debug_print( "(" + ( maxWaitTime - totalSecondsWaited ) + "s) Dist: " + moveDistInches + "<" + cutoffInches ); if ( moveDistInches < maxWaitTimeEnableDistInches ) { totalSecondsWaited = 0.0; } } else { break; } */ } wait iterationWaitSeconds; } } function vehicle_abandoned_by_occupants_t() { self endon( "transmute" ); self endon( "death" ); self endon( "delete" ); while ( 1 ) { self waittill( "exit_vehicle" ); occupants = self GetVehOccupants(); if ( occupants.size == 0 ) { self play_start_stop_sound( "tank_shutdown_sfx" ); self thread vehicle_abandoned_by_occupants_timeout_t(); } } } function play_start_stop_sound( sound_alias, modulation ) { if ( isdefined( self.start_stop_sfxid ) ) { //stopSound( self.start_stop_sfxid ); } self.start_stop_sfxid = self playSound( sound_alias ); } // this function should be replaced by code in cg_player.cpp cg_player() // we should just not be rendering the player when in a non-visible seat function vehicle_ghost_entering_occupants_t() { self endon( "transmute" ); self endon( "death" ); self endon( "delete" ); if ( IsDefined( self.vehicleClass ) && "artillery" == self.vehicleClass ) { return; } //if ( self vehicle_is_tank() ) { while ( 1 ) { self waittill( "enter_vehicle", player, seat ); isDriver = seat == 0; if ( GetDvarint( "scr_veh_driversarehidden" ) != 0 && isDriver ) { player Ghost(); } { occupants = self GetVehOccupants(); if ( occupants.size == 1 ) { self play_start_stop_sound( "tank_startup_sfx" ); } } player thread player_change_seat_handler_t( self ); player thread player_leave_vehicle_cleanup_t( self ); } } } function player_is_occupant_invulnerable( sMeansOfDeath ) { if ( self IsRemoteControlling() ) return false; if (!isdefined(level.vehicle_drivers_are_invulnerable)) level.vehicle_drivers_are_invulnerable = false; invulnerable = ( level.vehicle_drivers_are_invulnerable && ( self player_is_driver() ) ); return invulnerable; } function player_is_driver() { if ( !isalive(self) ) return false; vehicle = self GetVehicleOccupied(); if ( isdefined( vehicle ) ) { if ( IsDefined( vehicle.vehicleClass ) && "artillery" == vehicle.vehicleClass ) { return false; } seat = vehicle GetOccupantSeat( self ); if ( isdefined(seat) && seat == 0 ) return true; } return false; } // this function should be replaced by code in cg_player.cpp cg_player() // we should just not be rendering the player when in a non-visible seat function player_change_seat_handler_t( vehicle ) { self endon( "disconnect" ); self endon( "exit_vehicle" ); while ( 1 ) { self waittill( "change_seat", vehicle, oldSeat, newSeat ); isDriver = newSeat == 0; if ( isDriver ) { if ( GetDvarint( "scr_veh_driversarehidden" ) != 0 ) { self Ghost(); } } else { self Show(); } } } // this function should be replaced by code in cg_player.cpp cg_player() // we should just not be rendering the player when in a non-visible seat function player_leave_vehicle_cleanup_t( vehicle ) { self endon( "disconnect" ); self waittill( "exit_vehicle" ); currentWeapon = self getCurrentWeapon(); if ( IsDefined( self.lastWeapon ) && self.lastWeapon != currentWeapon && self.lastWeapon != level.weaponNone ) { self switchToWeapon( self.lastWeapon ); } self Show(); } function vehicle_is_tank() { return self.vehicletype == "sherman_mp" || self.vehicletype == "panzer4_mp" || self.vehicletype == "type97_mp" || self.vehicletype == "t34_mp"; } // ===================================================================================== function vehicle_record_initial_values() { if ( !isdefined( self.initial_state ) ) { self.initial_state= SpawnStruct(); } if ( isdefined( self.origin ) ) { self.initial_state.origin= self.origin; } if ( isdefined( self.angles ) ) { self.initial_state.angles= self.angles; } if ( isdefined( self.health ) ) { self.initial_state.health= self.health; } self initialize_vehicle_damage_state_data(); return; } function vehicle_should_explode_on_cleanup() { return GetDvarint( "scr_veh_explode_on_cleanup" ) != 0; } function vehicle_recycle() { self wait_for_unnoticeable_cleanup_opportunity(); self.recycling = true; self suicide(); } function wait_for_vehicle_overturn() { self endon( "transmute" ); self endon( "death" ); self endon( "delete" ); worldup = anglestoup((0,90,0)); overturned = 0; while (!overturned) { if ( isdefined( self.angles ) ) { up = AnglesToUp( self.angles ); dot = vectordot(up, worldup); if (dot <= 0.0) overturned = 1; } if (!overturned) wait (1.0); } } function vehicle_overturn_eject_occupants() { self endon( "transmute" ); self endon( "death" ); self endon( "delete" ); for(;;) { self waittill( "veh_ejectoccupants" ); if ( isdefined( level.onEjectOccupants ) ) { [[level.onEjectOccupants]](); } wait .25; } } function vehicle_eject_all_occupants() { occupants = self GetVehOccupants(); if ( isdefined( occupants ) ) { for ( i = 0; i < occupants.size; i++ ) { if ( isdefined( occupants[i] ) ) { occupants[i] Unlink(); } } } } function vehicle_overturn_suicide() { self endon( "transmute" ); self endon( "death" ); self endon( "delete" ); self wait_for_vehicle_overturn(); seconds = RandomFloatRange( 5, 7 ); wait seconds; damageOrigin = self.origin + (0,0,25); self finishVehicleRadiusDamage(self, self, 32000, 32000, 32000, 0, "MOD_EXPLOSIVE", level.weaponNone, damageOrigin, 400, -1, (0,0,1), 0); } function suicide() { self kill_vehicle( self ); } function kill_vehicle( attacker ) { damageOrigin = self.origin + (0,0,1); self finishVehicleRadiusDamage(attacker, attacker, 32000, 32000, 10, 0, "MOD_EXPLOSIVE", level.weaponNone, damageOrigin, 400, -1, (0,0,1), 0); } function value_with_default( preferred_value, default_value ) { if ( isdefined( preferred_value ) ) { return preferred_value; } return default_value; } function vehicle_transmute( attacker ) { deathOrigin = self.origin; deathAngles = self.angles; vehicle_name = get_vehicle_name_key_for_damage_states( self ); respawn_parameters = SpawnStruct(); respawn_parameters.origin = self.initial_state.origin; respawn_parameters.angles = self.initial_state.angles; respawn_parameters.health = self.initial_state.health; respawn_parameters.targetname = value_with_default( self.targetname, "" ); respawn_parameters.vehicletype = value_with_default( self.vehicletype, "" ); respawn_parameters.destructibledef = self.destructibledef; // Vehicle may or may not be a destructible vehicle vehicleWasDestroyed = !isdefined( self.recycling ); if ( vehicleWasDestroyed || vehicle_should_explode_on_cleanup() ) { _spawn_explosion( deathOrigin ); if ( vehicleWasDestroyed && GetDvarint( "scr_veh_explosion_doradiusdamage" ) != 0 ) { // Vehicle is exploding, so damage nearby entities // Damage first, so doesn't affect other entities spawned // by this function explosionRadius = GetDvarint( "scr_veh_explosion_radius" ); explosionMinDamage = GetDvarint( "scr_veh_explosion_mindamage" ); explosionMaxDamage = GetDvarint( "scr_veh_explosion_maxdamage" ); self kill_vehicle(attacker); self RadiusDamage( deathOrigin, explosionRadius, explosionMaxDamage, explosionMinDamage, attacker, "MOD_EXPLOSIVE", GetWeapon( self.vehicletype + "_explosion" ) );////////////////XXXXXXXXXXXXXXXXX COLLATERAL DAMAGE } } self notify( "transmute" ); respawn_vehicle_now = true; if ( vehicleWasDestroyed && GetDvarint( "scr_veh_ondeath_createhusk" ) != 0 ) { // Spawn burned out husk for players to use as cover if ( GetDvarint( "scr_veh_ondeath_usevehicleashusk" ) != 0 ) { husk = self; self.is_husk = true; } else { husk = _spawn_husk( deathOrigin, deathAngles, self.vehmodel ); } husk _init_husk( vehicle_name, respawn_parameters ); if ( GetDvarint( "scr_veh_respawnafterhuskcleanup" ) != 0 ) { respawn_vehicle_now = false; } } if ( !isdefined( self.is_husk ) ) { self remove_vehicle_from_world(); } if ( GetDvarint( "scr_veh_disablerespawn" ) != 0 ) //The Vehicle Mayhem gamemode handles spawning the vehicles, so it does not need to respawn here { respawn_vehicle_now = false; } if ( respawn_vehicle_now ) { respawn_vehicle( respawn_parameters ); } } function respawn_vehicle( respawn_parameters ) { { minTime = GetDvarint( "scr_veh_respawntimemin" ); maxTime = GetDvarint( "scr_veh_respawntimemax" ); seconds = RandomFloatRange( minTime, maxTime ); wait seconds; } wait_until_vehicle_position_wont_telefrag( respawn_parameters.origin ); if ( !manage_vehicles() ) // make sure we don't hit max vehicle limit { /# iprintln("Vehicle can't respawn because MAX_VEHICLES has been reached and none of the vehicles could be cleaned up."); #/ } else { if ( isdefined( respawn_parameters.destructibledef ) ) // passing undefined argument doesn't make the server happy { vehicle = SpawnVehicle( respawn_parameters.vehicletype, respawn_parameters.origin, respawn_parameters.angles, respawn_parameters.targetname, respawn_parameters.destructibledef ); } else { vehicle = SpawnVehicle( respawn_parameters.vehicletype, respawn_parameters.origin, respawn_parameters.angles, respawn_parameters.targetname ); } vehicle.vehicletype = respawn_parameters.vehicletype; vehicle.destructibledef = respawn_parameters.destructibledef; vehicle.health = respawn_parameters.health; vehicle init_vehicle(); vehicle vehicle_telefrag_griefers_at_position( respawn_parameters.origin ); } } function remove_vehicle_from_world() { // this is a temporary hack trying to resolve the "!cent->pose.fx.effect" // crash bug. Basically I think the bug is caused by deleteing the original // tanks that were in the bsp self notify ( "removed" ); if ( isdefined( self.original_vehicle ) ) { if ( !isdefined( self.permanentlyRemoved ) ) { self.permanentlyRemoved = true; // Mark that it has been permanently removed from the world self thread hide_vehicle(); // threaded because it calls a wait() } return false; } else { self _delete_entity(); return true; } } function _delete_entity() { /# //iprintln("$e" + ( self GetEntNum() ) + " is deleting"); #/ self Delete(); } function hide_vehicle() { under_the_world = ( self.origin[0], self.origin[1], self.origin[2] - 10000 ); self.origin = under_the_world; wait 0.1; self Hide(); self notify( "hidden_permanently" ); } function wait_for_unnoticeable_cleanup_opportunity() { maxPreventDistanceFeet = GetDvarint( "scr_veh_disappear_maxpreventdistancefeet" ); maxPreventVisibilityFeet = GetDvarint( "scr_veh_disappear_maxpreventvisibilityfeet" ); maxPreventDistanceInchesSq = 144 * maxPreventDistanceFeet * maxPreventDistanceFeet; maxPreventVisibilityInchesSq = 144 * maxPreventVisibilityFeet * maxPreventVisibilityFeet; maxSecondsToWait = GetDvarfloat( "scr_veh_disappear_maxwaittime" ); iterationWaitSeconds = 1.0; for ( secondsWaited = 0.0; secondsWaited < maxSecondsToWait; secondsWaited += iterationWaitSeconds ) { players_s = util::get_all_alive_players_s(); okToCleanup = true; for ( j = 0; j < players_s.a.size && okToCleanup; j++ ) { player = players_s.a[ j ]; distInchesSq = DistanceSquared( self.origin, player.origin ); if ( distInchesSq < maxPreventDistanceInchesSq ) { self cleanup_debug_print( "(" + ( maxSecondsToWait - secondsWaited ) + "s) Player too close: " + distInchesSq + "<" + maxPreventDistanceInchesSq ); okToCleanup = false; } else if ( distInchesSq < maxPreventVisibilityInchesSq ) { vehicleVisibilityFromPlayer = self SightConeTrace( player.origin, player, AnglesToForward( player.angles ) ); if ( vehicleVisibilityFromPlayer > 0 ) { self cleanup_debug_print( "(" + ( maxSecondsToWait - secondsWaited ) + "s) Player can see" ); okToCleanup = false; } } } if ( okToCleanup ) { return; } wait iterationWaitSeconds; } } function wait_until_vehicle_position_wont_telefrag( position ) { maxIterations = GetDvarint( "scr_veh_respawnwait_maxiterations" ); iterationWaitSeconds = GetDvarint( "scr_veh_respawnwait_iterationwaitseconds" ); for ( i = 0; i < maxIterations; i++ ) { if ( !vehicle_position_will_telefrag( position ) ) { return; } wait iterationWaitSeconds; } } function vehicle_position_will_telefrag( position ) { players_s = util::get_all_alive_players_s(); for ( i = 0; i < players_s.a.size; i++ ) { if ( players_s.a[ i ] player_vehicle_position_will_telefrag( position ) ) { return true; } } return false; } function vehicle_telefrag_griefers_at_position( position ) { attacker = self; inflictor = self; players_s = util::get_all_alive_players_s(); for ( i = 0; i < players_s.a.size; i++ ) { player = players_s.a[ i ]; if ( player player_vehicle_position_will_telefrag( position ) ) { player DoDamage( 20000, player.origin + ( 0, 0, 1 ), attacker, inflictor, "none" ); } } } function player_vehicle_position_will_telefrag( position ) { distanceInches = 20 * 12; ///< 20 ft., in inches minDistInchesSq = distanceInches * distanceInches; distInchesSq = DistanceSquared( self.origin, position ); return distInchesSq < minDistInchesSq; } function vehicle_recycle_spawner_t() { self endon( "delete" ); self waittill( "death", attacker ); // "vehicle Delete()" sends death message too!!! if ( isdefined( self ) ) { self vehicle_transmute( attacker ); } } function vehicle_play_explosion_sound() { self playSound( "car_explo_large" ); } function vehicle_damage_t() { self endon( "delete" ); self endon( "removed" ); for( ;; ) { self waittill ( "damage", damage, attacker ); players = GetPlayers(); for ( i = 0 ; i < players.size ; i++ ) { if ( !isalive(players[i]) ) continue; vehicle = players[i] GetVehicleOccupied(); if ( isdefined( vehicle) && self == vehicle && players[i] player_is_driver() ) { if (damage>0) { // ^^^ earthquake() will generate an SRE if scale <= 0 earthquake( damage/400, 1.0, players[i].origin, 512, players[i] ); } if ( damage > 100.0 ) { /# println( "Playing heavy rumble." ); #/ players[i] PlayRumbleOnEntity( "tank_damage_heavy_mp" ); } else if ( damage > 10.0 ) { /# println( "Playing light rumble." ); #/ players[i] PlayRumbleOnEntity( "tank_damage_light_mp" ); } } } update_damage_effects(self, attacker); } } // ===================================================================================== // Burnt-Out Husk Code function _spawn_husk( origin, angles, modelname ) { husk = spawn( "script_model", origin ); husk.angles = angles; husk SetModel( modelname ); husk.health = 1; husk SetCanDamage( false ); ///< Does this really work? It doesn't for players, but might for other entities return husk; } function is_vehicle() { // Could check classname=="script_vehicle", but this is a little more general purpose, I think return isdefined( self.vehicletype ); } function swap_to_husk_model() { if ( isdefined( self.vehicletype ) ) { husk_model = level.veh_husk_models[ self.vehicletype ]; if ( isdefined( husk_model ) ) { self SetModel( husk_model ); } } } function _init_husk( vehicle_name, respawn_parameters ) { self swap_to_husk_model(); if ( isdefined( level.vehicles_husk_effects ) ) { effects = level.vehicles_husk_effects[ vehicle_name ]; self play_vehicle_effects( effects ); } self.respawn_parameters = respawn_parameters; forcePointVariance = GetDvarint( "scr_veh_explosion_husk_forcepointvariance" ); horzVelocityVariance = GetDvarint( "scr_veh_explosion_husk_horzvelocityvariance" ); vertVelocityMin = GetDvarint( "scr_veh_explosion_husk_vertvelocitymin" ); vertVelocityMax = GetDvarint( "scr_veh_explosion_husk_vertvelocitymax" ); forcePointX = RandomFloatRange( 0-forcePointVariance, forcePointVariance ); forcePointY = RandomFloatRange( 0-forcePointVariance, forcePointVariance ); forcePoint = ( forcePointX, forcePointY, 0 ); forcePoint += self.origin; initialVelocityX = RandomFloatRange( 0-horzVelocityVariance, horzVelocityVariance ); initialVelocityY = RandomFloatRange( 0-horzVelocityVariance, horzVelocityVariance ); initialVelocityZ = RandomFloatRange( vertVelocityMin, vertVelocityMax ); initialVelocity = ( initialVelocityX, initialVelocityY, initialVelocityZ ); if ( self is_vehicle() ) { self LaunchVehicle( initialVelocity, forcePoint ); } else { self PhysicsLaunch( forcePoint, initialVelocity ); } self thread husk_cleanup_t(); /# self thread cleanup_debug_print_t(); #/ } function husk_cleanup_t() { self endon( "death" ); // ent Delete() actually sends the "death" message!!! self endon( "delete" ); self endon( "hidden_permanently" ); respawn_parameters = self.respawn_parameters; self do_dead_cleanup_wait( "Husk Cleanup Test" ); self wait_for_unnoticeable_cleanup_opportunity(); self thread final_husk_cleanup_t( respawn_parameters ); // break off new thread to avoid end-ons } function final_husk_cleanup_t( respawn_parameters ) { self husk_do_cleanup(); // causes endons, which is why we broke this off into a new thread if ( GetDvarint( "scr_veh_respawnafterhuskcleanup" ) != 0 ) { if ( GetDvarint( "scr_veh_disablerespawn" ) == 0 ) //The Vehicle Mayhem gamemode handles spawning the vehicles, so it does not need to respawn here { respawn_vehicle( respawn_parameters ); } } } // Returns true only if the entity is actually deleted, rather than just hidden. function husk_do_cleanup() { // Don't ever let vehicles just blink out. Spawn a VFX // explosion that doesn't injur any surrounding entities, // just to mask the blink out, in case players are // looking in the direction of this vehicle husk. self _spawn_explosion( self.origin ); if ( self is_vehicle() ) { return self remove_vehicle_from_world(); } else { self _delete_entity(); return true; } } // ===================================================================================== // ===================================================================================== // Explosion Code function _spawn_explosion( origin ) { if ( GetDvarint( "scr_veh_explosion_spawnfx" ) == 0 ) { return; } if ( isdefined( level.vehicle_explosion_effect ) ) { forward = ( 0, 0, 1 ); rot = randomfloat( 360 ); up = ( cos( rot ), sin( rot ), 0 ); PlayFX( level.vehicle_explosion_effect, origin, forward, up ); } thread _play_sound_in_space( "vehicle_explo", origin ); } // NOTE: This function was copied from sab.gsc. Should be centralized somewhere... function _play_sound_in_space( soundEffectName, origin ) { org = Spawn( "script_origin", origin ); org.origin = origin; org PlaySoundWithNotify( soundEffectName, "sounddone" ); org waittill( "sounddone" ); org delete(); } // ===================================================================================== function vehicle_kill_disconnect_paths_forever() { self notify( "kill_disconnect_paths_forever" ); } function vehicle_disconnect_paths() { /* MPAI_PETER_TODO self endon( "death" ); self endon( "kill_disconnect_paths_forever" ); if ( isdefined( self.script_disconnectpaths ) && !self.script_disconnectpaths ) { self.dontDisconnectPaths = true;// lets other parts of the script know not to disconnect script return; } wait( randomfloat( 1 ) ); while( isdefined( self ) ) { if( self getspeed() < 1 ) { if ( !isdefined( self.dontDisconnectPaths ) ) self disconnectpaths(); self notify( "speed_zero_path_disconnect" ); while( self getspeed() < 1 ) wait .05; } self connectpaths(); wait 1; } */ } function follow_path( node ) { self endon("death"); assert( isdefined( node ), "vehicle_path() called without a path" ); self notify( "newpath" ); // dynamicpaths unique. node isn't defined by info vehicle node calls to this function if( isdefined( node ) ) { self.attachedpath = node; } pathstart = self.attachedpath; self.currentNode = self.attachedpath; if( !isdefined( pathstart ) ) { return; } self AttachPath( pathstart ); self StartPath(); self endon( "newpath" ); nextpoint = pathstart; while ( isdefined( nextpoint ) ) { self waittill( "reached_node", nextpoint ); self.currentNode = nextpoint; // the sweet stuff! Pathpoints handled in script as triggers! nextpoint notify( "trigger", self ); if ( isdefined( nextpoint.script_noteworthy ) ) { self notify( nextpoint.script_noteworthy ); self notify( "noteworthy", nextpoint.script_noteworthy, nextpoint ); } waittillframeend; } } function InitVehicleMap() { root = "devgui_cmd \"MP/Vehicles/"; AddDebugCommand( root + "Spawn siegebot\" \"set scr_spawnvehicle 1\"\n"); AddDebugCommand( root + "Spawn siegebot boss\" \"set scr_spawnvehicle 2\"\n"); AddDebugCommand( root + "Spawn quadtank\" \"set scr_spawnvehicle 3\"\n"); AddDebugCommand( root + "Spawn mechtank\" \"set scr_spawnvehicle 4\"\n"); thread VehicleMainThread(); } function VehicleMainThread() { if ( level.disableVehicleSpawners === true ) return; //"siegebot"; //"siegebot_boss"; //"quadtank"; //"mechtank"; //spawn_nodes = getentarray( "veh_spawn_point", "targetname" ); spawn_nodes = struct::get_array( "veh_spawn_point", "targetname" ); veh_spawner_id = 0; // a unique identifier for gameplay driven fx for( i = 0; i < spawn_nodes.size; i++ ) { spawn_node = spawn_nodes[i]; veh_name = spawn_node.script_noteworthy; time_interval = int(spawn_node.script_parameters); if( !isdefined( veh_name ) ) continue; veh_spawner_id++; thread VehicleSpawnThread( veh_spawner_id, veh_name, spawn_node.origin, spawn_node.angles, time_interval ); if ( isdefined( level.vehicle_spawner_init ) ) level [[ level.vehicle_spawner_init ]] ( veh_spawner_id, veh_name, spawn_node.origin, spawn_node.angles ); {wait(.05);}; } if ( isdefined( level.vehicle_spawners_init_finished ) ) level thread [[ level.vehicle_spawners_init_finished ]](); } function VehicleSpawnThread( veh_spawner_id, veh_name, origin, angles, time_interval ) { level endon( "game_ended" ); veh_spawner = GetEnt( veh_name + "_spawner", "targetname" ); kill_trigger = spawn( "trigger_radius", origin, 0, 60, 180 ); /# level thread test_vehicle_spawn_fx( veh_name, origin, angles ); original_time_interval = time_interval; #/ while( 1 ) { vehicle = veh_spawner SpawnFromSpawner( veh_name, true, true, true ); if( !isdefined( vehicle ) ) { wait RandomFloatRange( 1.0, 2.0 ); continue; } if( isdefined( vehicle.archetype ) ) vehicle ASMRequestSubstate( "locomotion@movement" ); {wait(.05);}; vehicle.origin = origin; vehicle.angles = angles; vehicle.veh_spawner_id = veh_spawner_id; vehicle thread VehicleTeamThread(); /# level thread debug_vehicle_destroy_think( vehicle ); #/ vehicle waittill( "death" ); vehicle vehicle_death::DeleteWhenSafe( 0.25 ); if ( isdefined( level.vehicle_destroyed ) ) level thread [[ level.vehicle_destroyed ]]( veh_spawner_id ); /# time_interval = original_time_interval; if ( GetDvarFloat( "scr_vehicle_spawn_time", 0.0 ) != 0.0 ) { time_interval = GetDvarFloat( "scr_vehicle_spawn_time", 0.0 ); if ( time_interval < 5.1 ) time_interval = 5.1; } #/ if( isdefined( time_interval ) ) { level thread PerformVehiclePreSpawn( veh_spawner_id, veh_name, origin, angles, time_interval, kill_trigger ); wait time_interval; } } } function PerformVehiclePreSpawn( veh_spawner_id, veh_name, origin, angles, spawn_delay, kill_trigger ) { fx_prespawn_time = 5.0; fx_spawn_delay = spawn_delay - fx_prespawn_time; wait fx_spawn_delay; if ( isdefined( level.vehicle_about_to_spawn ) ) { level thread [[ level.vehicle_about_to_spawn ]]( veh_spawner_id, veh_name, origin, angles, fx_prespawn_time ); } kill_overlap_time = 0.1; wait_before_kill = fx_prespawn_time - kill_overlap_time; wait wait_before_kill; kill_duration_ms = kill_overlap_time * 2 * 1000; level thread kill_any_touching( kill_trigger, kill_duration_ms ); wait kill_overlap_time; } function kill_any_touching( kill_trigger, kill_duration_ms ) { kill_expire_time_ms = GetTime() + kill_duration_ms; kill_weapon = GetWeapon( "hero_minigun" ); // we like this for the gib settings while ( GetTime() <= kill_expire_time_ms ) { foreach( player in level.players ) { if ( !isdefined( player ) ) continue; if ( player IsTouching( kill_trigger ) ) { if ( player IsInVehicle() ) { vehicle = player GetVehicleOccupied(); if ( isdefined( vehicle ) && ( vehicle.is_oob_kill_target === true ) ) // intentionally piggy-backed on is_oob_kill_target { destroy_vehicle( vehicle ); continue; } } player DoDamage( player.health + 1, player.origin, kill_trigger, kill_trigger, "none", "MOD_SUICIDE", 0, kill_weapon ); } } potential_victims = GetAIArray(); if ( isdefined( potential_victims ) ) { foreach( entity in potential_victims ) { if ( !isdefined( entity ) ) continue; if ( !entity IsTouching( kill_trigger ) ) continue; if ( isdefined( entity.health ) && entity.health <= 0 ) continue; if ( IsVehicle( entity ) ) destroy_vehicle( entity ); } } {wait(.05);}; } } function destroy_vehicle( vehicle ) { // need to do this to destroy vehicles vehicle DoDamage( vehicle.health + 10000, vehicle.origin, undefined, undefined, "none", "MOD_TRIGGER_HURT" ); // must be MOD_TRIGGER_HURT } /# function test_vehicle_spawn_fx( veh_name, origin, angles ) { fx_prespawn_time = 5.0; while( 1 ) { if ( GetDvarInt( "scr_vehicle_spawn_fx_debug", 0 ) == 0 ) { wait 1.0; } else { if ( isdefined( level.vehicle_about_to_spawn ) ) { level thread [[ level.vehicle_about_to_spawn ]]( veh_name, origin, angles, fx_prespawn_time ); } wait 6.0; } } } #/ /# function debug_vehicle_destroy_think( vehicle ) { vehicle endon( "death" ); SetDvar( "scr_kill_vehicle", 0 ); while( 1 ) { if ( GetDvarInt( "scr_kill_vehicle" ) != 0 ) destroy_vehicle( vehicle ); wait 1.0; } } #/ function VehicleTeamThread() { vehicle = self; vehicle endon( "death" ); vehicle MakeVehicleUsable(); if( Target_isTarget( vehicle ) ) Target_Remove( vehicle ); vehicle.noJumping = true; vehicle.forceDamageFeedback = true; vehicle.vehkilloccupantsondeath = true; vehicle DisableAimAssist(); while( 1 ) { vehicle setteam( "neutral" ); vehicle.ignoreme = true; //vehicle SetHighDetail( false ); vehicle clientfield::set( "toggle_lights", 1 ); if( Target_isTarget( vehicle ) ) Target_Remove( vehicle ); vehicle waittill( "enter_vehicle", player ); player ClearAndCachePerks(); vehicle setteam( player.team ); vehicle.ignoreme = false; //vehicle SetHighDetail( true ); vehicle clientfield::set( "toggle_lights", 0 ); vehicle spawning::create_entity_enemy_influencer( "small_vehicle", player.team ); player spawning::enable_influencers( false ); if( !Target_isTarget( vehicle ) ) { if( isdefined( vehicle.targetOffset ) ) Target_Set( vehicle, vehicle.targetOffset ); else Target_Set( vehicle, ( 0, 0, 0 ) ); } vehicle thread WatchPlayerExitRequestThread( player ); vehicle waittill( "exit_vehicle", player ); if( isdefined( player ) ) { player SetCachedPerks(); player spawning::enable_influencers( true ); } vehicle spawning::remove_influencers(); } } function WatchPlayerExitRequestThread( player ) { level endon( "game_ended" ); player endon ( "death" ); player endon( "disconnect" ); vehicle = self; vehicle endon( "death" ); wait 1.5; while( true ) { timeUsed = 0; while( player UseButtonPressed() ) { timeUsed += 0.05; if( timeUsed > 0.25 ) { player unlink(); return; } {wait(.05);}; } {wait(.05);}; } } // We will cache of the perks on the player. If the player dies, cache is lost but respawned function ClearAndCachePerks() { self.perks_before_vehicle = self GetPerks(); self ClearPerks(); } function SetCachedPerks() { assert( IsDefined( self.perks_before_vehicle ) ); foreach( perk in self.perks_before_vehicle ) { self SetPerk( perk ); } }