1756 lines
48 KiB
Plaintext
1756 lines
48 KiB
Plaintext
#using scripts\codescripts\struct;
|
|
|
|
#using scripts\shared\clientfield_shared;
|
|
#using scripts\shared\gameskill_shared;
|
|
#using scripts\shared\math_shared;
|
|
#using scripts\shared\statemachine_shared;
|
|
#using scripts\shared\system_shared;
|
|
#using scripts\shared\util_shared;
|
|
#using scripts\shared\turret_shared;
|
|
#using scripts\shared\flag_shared;
|
|
#using scripts\shared\damagefeedback_shared;
|
|
#using scripts\shared\laststand_shared;
|
|
#using scripts\shared\gameobjects_shared;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#using scripts\shared\ai\systems\blackboard;
|
|
#using scripts\shared\ai\blackboard_vehicle;
|
|
|
|
|
|
#using scripts\shared\vehicle_shared;
|
|
#using scripts\shared\vehicle_ai_shared;
|
|
#using scripts\shared\vehicle_death_shared;
|
|
|
|
#using scripts\mp\killstreaks\_killstreaks;
|
|
#using scripts\mp\killstreaks\_killstreak_bundles;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Number of times that a player can destroy the trophy weakspot before it is permanently destroyed
|
|
|
|
//Used to limit the number of spikes that can hit a QT before trophy system gets re-enabled
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//an actor target must be at least this range from the QT for the QT to use the javelin attack
|
|
|
|
|
|
|
|
|
|
#precache( "string", "tag_target_lower" );
|
|
|
|
|
|
|
|
#namespace quadtank;
|
|
|
|
function autoexec __init__sytem__() { system::register("quadtank",&__init__,undefined,undefined); }
|
|
|
|
#using_animtree( "generic" );
|
|
|
|
function __init__()
|
|
{
|
|
vehicle::add_main_callback( "quadtank", &quadtank_initialize );
|
|
|
|
clientfield::register( "toplayer", "player_shock_fx", 1, 1, "int" );
|
|
clientfield::register( "vehicle", "quadtank_trophy_state", 1, 1, "int" );
|
|
}
|
|
|
|
function quadtank_initialize()
|
|
{
|
|
self useanimtree( #animtree );
|
|
|
|
self EnableAimAssist();
|
|
self SetNearGoalNotifyDist( 50 );
|
|
|
|
// AI SPECIFIC INITIALIZATION
|
|
blackboard::CreateBlackBoardForEntity( self );
|
|
self Blackboard::RegisterVehicleBlackBoardAttributes();
|
|
|
|
self.turret_state = 1;
|
|
|
|
self.fovcosine = 0; // +/-90 degrees = 180 fov, err 0 actually means 360 degree view
|
|
self.fovcosinebusy = 0;
|
|
self.maxsightdistsqrd = ( (10000) * (10000) );
|
|
|
|
self.weakpointobjective = 0;
|
|
self.combatactive = true; //used for weakpoint marker to make sure that objective is not added if tank is off
|
|
self.damage_during_trophy_down = 0;
|
|
self.spike_hits_during_trophy_down = 0;
|
|
self.trophy_disables = 0;
|
|
self.allow_movement = true;
|
|
|
|
assert( isdefined( self.scriptbundlesettings ) );
|
|
|
|
self.settings = struct::get_script_bundle( "vehiclecustomsettings", self.scriptbundlesettings );
|
|
|
|
self.variant = "cannon";
|
|
|
|
if( IsSubStr( self.vehicleType, "mlrs" ) )
|
|
{
|
|
self.variant = "rocketpod";
|
|
}
|
|
|
|
self.goalRadius = 9999999;
|
|
self.goalHeight = 512;
|
|
self SetGoal( self.origin, false, self.goalRadius, self.goalHeight );
|
|
|
|
self SetSpeed( self.settings.defaultMoveSpeed, 10, 10 );
|
|
self SetMinDesiredTurnYaw( 45 );
|
|
self show_weak_spots( false );
|
|
|
|
turret::_init_turret( 1 );
|
|
turret::_init_turret( 2 );
|
|
|
|
turret::set_best_target_func( &_get_best_target_quadtank_side_turret, 1 );
|
|
turret::set_best_target_func( &_get_best_target_quadtank_side_turret, 2 );
|
|
|
|
self quadtank_update_difficulty();
|
|
|
|
self quadtank_side_turrets_forward();
|
|
self.overrideVehicleDamage = &QuadtankCallback_VehicleDamage;
|
|
|
|
//disable some cybercom abilities
|
|
if( IsDefined( level.vehicle_initializer_cb ) )
|
|
{
|
|
[[level.vehicle_initializer_cb]]( self );
|
|
}
|
|
|
|
self.ignoreFireFly = true;
|
|
self.ignoreDecoy = true;
|
|
self vehicle_ai::InitThreatBias();
|
|
|
|
self.disableElectroDamage = true;
|
|
self.disableBurnDamage = true;
|
|
|
|
self thread vehicle_ai::target_hijackers();
|
|
|
|
self HidePart( "tag_defense_active" );
|
|
|
|
|
|
self quadtank_enabletrophy();
|
|
self quadtank_disabletrophy();
|
|
|
|
killstreak_bundles::register_killstreak_bundle( "quadtank" );
|
|
self.maxhealth = killstreak_bundles::get_max_health( "quadtank" );
|
|
self.heatlh = self.maxhealth;
|
|
|
|
self thread monitor_enter_vehicle();
|
|
}
|
|
|
|
function quadtank_update_difficulty()
|
|
{
|
|
// testing out changing the turret parameters based solely upon the number of players, since damage
|
|
// is alread scaled based upon the difficulty of the individual player
|
|
// so, saving out current method until change has been tested
|
|
//
|
|
// value = gameskill::get_general_difficulty_level();
|
|
//
|
|
// scale_up = mapfloat( 0, 7, 0.8, 2.0, value );
|
|
// scale_down = mapfloat( 0, 7, 1.0, 0.5, value );
|
|
|
|
if( isDefined( level.players) )
|
|
{
|
|
value = level.players.size;
|
|
}
|
|
else
|
|
{
|
|
value = 1;
|
|
}
|
|
|
|
|
|
scale_up = mapfloat( 1, 4, 1, 1.5, value );
|
|
scale_down = mapfloat( 1, 4, 1.0, 0.75, value );
|
|
|
|
turret::set_burst_parameters( 1.5, 2.5 * scale_up, 0.25 * scale_down, 0.75 * scale_down, 1 );
|
|
turret::set_burst_parameters( 1.5, 2.5 * scale_up, 0.25 * scale_down, 0.75 * scale_down, 2 );
|
|
|
|
self.difficulty_scale_up = scale_up;
|
|
self.difficulty_scale_down = scale_down;
|
|
}
|
|
|
|
function defaultRole()
|
|
{
|
|
self.state_machine = self vehicle_ai::init_state_machine_for_role( "default" );
|
|
|
|
self vehicle_ai::get_state_callbacks( "pain" ).update_func = &pain_update;
|
|
self vehicle_ai::get_state_callbacks( "emped" ).update_func = &quadtank_emped;
|
|
|
|
self vehicle_ai::get_state_callbacks( "off" ).enter_func = &state_off_enter;
|
|
self vehicle_ai::get_state_callbacks( "off" ).exit_func = &state_off_exit;
|
|
|
|
self vehicle_ai::get_state_callbacks( "scripted" ).update_func = &state_scripted_update;
|
|
self vehicle_ai::get_state_callbacks( "driving" ).update_func = &state_driving_update;
|
|
|
|
self vehicle_ai::get_state_callbacks( "combat" ).update_func = &state_combat_update;
|
|
self vehicle_ai::get_state_callbacks( "combat" ).exit_func = &state_combat_exit;
|
|
|
|
self vehicle_ai::get_state_callbacks( "death" ).update_func = &quadtank_death;
|
|
|
|
self vehicle_ai::call_custom_add_state_callbacks();
|
|
|
|
self vehicle_ai::StartInitialState();
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: off
|
|
// ----------------------------------------------
|
|
function quadtank_off()
|
|
{
|
|
self vehicle_ai::set_state( "off" );
|
|
self.combatactive = false;
|
|
}
|
|
|
|
function quadtank_on()
|
|
{
|
|
self vehicle_ai::set_state( "combat" );
|
|
self.combatactive = true;
|
|
}
|
|
|
|
function state_off_enter( params )
|
|
{
|
|
self playsound( "veh_quadtank_power_down" );
|
|
|
|
self LaserOff();
|
|
self ClearTargetEntity();
|
|
self CancelAIMove();
|
|
self ClearVehGoalPos();
|
|
|
|
vehicle_ai::TurnOffAllLightsAndLaser();
|
|
vehicle_ai::TurnOffAllAmbientAnims();
|
|
self vehicle::toggle_tread_fx( 0 );
|
|
self vehicle::toggle_sounds( 0 );
|
|
self vehicle::toggle_exhaust_fx( 0 );
|
|
|
|
angles = self GetTagAngles( "tag_flash" );
|
|
target_vec = self.origin + AnglesToForward( ( 0, angles[1], 0 ) ) * 1000;
|
|
target_vec = target_vec + ( 0, 0, -500 );
|
|
self SetTargetOrigin( target_vec );
|
|
self set_side_turrets_enabled( false );
|
|
self thread quadtank_disabletrophy();
|
|
|
|
if( !isdefined( self.emped ) )
|
|
{
|
|
self DisableAimAssist();
|
|
}
|
|
}
|
|
|
|
function state_off_exit( params )
|
|
{
|
|
self vehicle::lights_on();
|
|
self vehicle::toggle_tread_fx( 1 );
|
|
self vehicle::toggle_sounds( 1 );
|
|
self thread bootup();
|
|
self vehicle::toggle_exhaust_fx( 1 );
|
|
self EnableAimAssist();
|
|
}
|
|
|
|
function bootup()
|
|
{
|
|
self endon("death");
|
|
self playsound( "veh_quadtank_power_up" );
|
|
self vehicle_ai::blink_lights_for_time( 1.5 );
|
|
|
|
angles = self GetTagAngles( "tag_flash" );
|
|
target_vec = self.origin + AnglesToForward( ( 0, angles[1], 0 ) ) * 1000;
|
|
self.turretRotScale = 0.3;
|
|
|
|
driver = self GetSeatOccupant( 0 );
|
|
if( !isdefined(driver) )
|
|
{
|
|
self SetTargetOrigin( target_vec );
|
|
}
|
|
wait 1;
|
|
|
|
self.turretRotScale = 1 * self.difficulty_scale_up;
|
|
}
|
|
// State: off -----------------------------------
|
|
|
|
// ----------------------------------------------
|
|
// State: pain
|
|
// ----------------------------------------------
|
|
function pain_update( params )
|
|
{
|
|
self endon( "change_state" );
|
|
self endon( "death" );
|
|
|
|
isTrophyDownPain = params.notify_param[0];
|
|
|
|
if( isTrophyDownPain === true )
|
|
{
|
|
// trophy system must be going down now
|
|
asmState = "trophy_disabled@stationary";
|
|
}
|
|
else
|
|
{
|
|
// can only take pain when trophy is down
|
|
asmState = "pain@stationary";
|
|
}
|
|
self ASMRequestSubstate( asmState );
|
|
playsoundatposition ("prj_quad_impact", self.origin);
|
|
|
|
self CancelAIMove();
|
|
self ClearVehGoalPos();
|
|
self ClearTurretTarget();
|
|
self SetBrake( 1 );
|
|
|
|
self vehicle_ai::waittill_asm_complete( asmState, 6 );
|
|
|
|
self SetBrake( 0 );
|
|
|
|
self ASMRequestSubstate( "locomotion@movement" );
|
|
|
|
driver = self GetSeatOccupant( 0 );
|
|
if( !isdefined( driver ) )
|
|
{
|
|
self vehicle_ai::set_state( "combat" );
|
|
}
|
|
else
|
|
{
|
|
self vehicle_ai::set_state( "driving" );
|
|
}
|
|
}
|
|
// State: pain ----------------------------------
|
|
|
|
// ----------------------------------------------
|
|
// State: scripted
|
|
// ----------------------------------------------
|
|
function state_scripted_update( params )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
|
|
self set_side_turrets_enabled( false );
|
|
self LaserOff();
|
|
self ClearTargetEntity();
|
|
self CancelAIMove();
|
|
self ClearVehGoalPos();
|
|
|
|
self vehicle::toggle_ambient_anim_group( 2, true );
|
|
}
|
|
// State: scripted ------------------------------
|
|
|
|
// ----------------------------------------------
|
|
// State: driving
|
|
// ----------------------------------------------
|
|
function state_driving_update( params )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
|
|
self set_side_turrets_enabled( false );
|
|
self LaserOff();
|
|
self ClearTargetEntity();
|
|
self CancelAIMove();
|
|
self ClearVehGoalPos();
|
|
|
|
self vehicle::toggle_ambient_anim_group( 2, true );
|
|
|
|
driver = self GetSeatOccupant( 0 );
|
|
if( isdefined(driver) )
|
|
{
|
|
self.turretRotScale = 1;
|
|
self DisableAimAssist();
|
|
self thread quadtank_set_team( driver.team );
|
|
self SetBrake( 0 );
|
|
self ASMRequestSubstate( "locomotion@movement" );
|
|
self thread quadtank_player_fireupdate();
|
|
self thread footstep_handler();
|
|
|
|
self.trophy_disables = 1 - 1;
|
|
self thread quadtank_disabletrophy();
|
|
}
|
|
}
|
|
|
|
function quadtank_exit_vehicle()
|
|
{
|
|
self SetGoal( self.origin );
|
|
}
|
|
// State: driving -------------------------------
|
|
|
|
// ----------------------------------------------
|
|
// State: combat
|
|
// ----------------------------------------------
|
|
function state_combat_update( params )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
|
|
if ( isalive( self ) )
|
|
{
|
|
}
|
|
|
|
if ( isalive( self ) && !trophy_disabled() )
|
|
{
|
|
self thread quadtank_enabletrophy();
|
|
}
|
|
|
|
if ( self.allow_movement )
|
|
{
|
|
self thread quadtank_movementupdate();
|
|
}
|
|
else
|
|
{
|
|
self SetBrake( 1 );
|
|
}
|
|
|
|
switch ( self.variant )
|
|
{
|
|
case "cannon":
|
|
vehicle_ai::Cooldown( "main_cannon", 4 ); // don't shoot cannon immediately
|
|
self thread quadtank_weapon_think_cannon();
|
|
break;
|
|
case "rocketpod":
|
|
self thread Attack_Thread_rocket();
|
|
break;
|
|
}
|
|
}
|
|
|
|
function state_combat_exit( params )
|
|
{
|
|
self notify( "end_attack_thread" );
|
|
self notify( "end_movement_thread" );
|
|
self ClearTurretTarget();
|
|
self ClearLookAtEnt();
|
|
}
|
|
// State: combat --------------------------------
|
|
|
|
// ----------------------------------------------
|
|
// State: death
|
|
// ----------------------------------------------
|
|
function quadtank_death( params )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "nodeath_thread" );
|
|
|
|
//self set_trophy_state( false );
|
|
self quadtank_weakpoint_display( false );
|
|
self remove_repulsor();
|
|
self HidePart( "tag_lidar_null", "", true );
|
|
self vehicle::set_damage_fx_level( 0 );
|
|
|
|
// Need to prep the death model
|
|
StreamerModelHint( self.deathmodel, 6 );
|
|
|
|
if ( !isdefined( self.custom_death_sequence ) )
|
|
{
|
|
playsoundatposition ("prj_quad_impact", self.origin);
|
|
self playsound( "veh_quadtank_power_down" );
|
|
self playsound("veh_quadtank_sparks");
|
|
self ASMRequestSubstate( "death@stationary" );
|
|
self waittill( "explosion_c" );
|
|
}
|
|
else
|
|
{
|
|
self [[self.custom_death_sequence]]();
|
|
}
|
|
|
|
if( isdefined( level.disable_thermal ) )
|
|
{
|
|
[[level.disable_thermal]]();
|
|
}
|
|
|
|
if( isdefined( self.stun_fx ) )
|
|
{
|
|
self.stun_fx delete();
|
|
}
|
|
|
|
BadPlace_Box( "", 0, self.origin, 90, "neutral" );
|
|
self vehicle_death::set_death_model( self.deathmodel, self.modelswapdelay );
|
|
self vehicle_death::death_radius_damage();
|
|
|
|
vehicle_ai::waittill_asm_complete( "death@stationary", 5 );
|
|
|
|
self thread vehicle_death::CleanUp();
|
|
vehicle_death::FreeWhenSafe();
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// State: emped
|
|
// ----------------------------------------------
|
|
function quadtank_emped( params )
|
|
{
|
|
self endon ("death");
|
|
self endon( "change_state" );
|
|
self endon( "emped" );
|
|
|
|
if( isdefined( self.emped ) )
|
|
{
|
|
// already emped, just return for now.
|
|
return;
|
|
}
|
|
|
|
self.emped = true;
|
|
PlaySoundAtPosition( "veh_quadtankemp_down", self.origin );
|
|
self.turretRotScale = 0.2;
|
|
if( !isdefined( self.stun_fx) )
|
|
{
|
|
self.stun_fx = Spawn( "script_model", self.origin );
|
|
self.stun_fx SetModel( "tag_origin" );
|
|
self.stun_fx LinkTo( self, "tag_turret", (0,0,0), (0,0,0) );
|
|
//PlayFXOnTag( level._effect[ "quadtank_stun" ], self.stun_fx, "tag_origin" );
|
|
}
|
|
|
|
time = params.notify_param[0];
|
|
assert( isdefined( time ) );
|
|
vehicle_ai::Cooldown( "emped_timer", time );
|
|
|
|
while( !vehicle_ai::IsCooldownReady( "emped_timer" ) )
|
|
{
|
|
timeLeft = max( vehicle_ai::GetCooldownLeft( "emped_timer" ), 0.5 );
|
|
wait timeLeft;
|
|
}
|
|
|
|
self.stun_fx delete();
|
|
self.emped = undefined;
|
|
self playsound ("veh_boot_quadtank");
|
|
|
|
self vehicle_ai::evaluate_connections();
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// trophy system
|
|
// ----------------------------------------------
|
|
function trophy_disabled()
|
|
{
|
|
if( self.trophy_down === true )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( trophy_destroyed() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function trophy_destroyed()
|
|
{
|
|
if ( self.trophy_disables >= 1 )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function quadtank_disabletrophy()
|
|
{
|
|
self endon( "death" );
|
|
self notify( "stop_disabletrophy" );
|
|
self endon( "stop_disabletrophy" );
|
|
self notify( "stop_enabletrophy" );
|
|
|
|
//set_trophy_state( false );
|
|
|
|
if( trophy_disabled() )
|
|
return;
|
|
|
|
self.trophy_down = true;
|
|
|
|
driver = self GetSeatOccupant( 0 );
|
|
curr_state = self vehicle_ai::get_current_state();
|
|
next_state = self vehicle_ai::get_next_state();
|
|
if( !isdefined( driver ) && isdefined( curr_state ) && ( curr_state != "off" ) && isdefined( next_state ) && ( next_state != "off" ) )
|
|
{
|
|
self notify( "pain", true ); // Play a trophy system down animation using the pain state
|
|
}
|
|
|
|
//Target_Set( self, ( 0, 0, 60 ) );7
|
|
self.targetOffset = ( 0, 0, 60 );
|
|
|
|
self HidePart( "tag_defense_active" );
|
|
//self HidePart( "tag_target_upper" );
|
|
|
|
self.attackerAccuracy = 0.5;
|
|
self.damage_during_trophy_down = 0;
|
|
self.spike_hits_during_trophy_down = 0;
|
|
self.trophy_disables += 1;
|
|
|
|
self quadtank_weakpoint_display( false );
|
|
self remove_repulsor();
|
|
|
|
driver = self GetSeatOccupant( 0 );
|
|
|
|
self set_side_turrets_enabled( false );
|
|
|
|
if( IsDefined( level.vehicle_defense_cb ) )
|
|
{
|
|
[[level.vehicle_defense_cb]]( self, false );
|
|
}
|
|
|
|
if( trophy_destroyed() )
|
|
{
|
|
self notify("trophy_system_destroyed");
|
|
level notify("trophy_system_destroyed",self);
|
|
self playsound ("wpn_trophy_disable");
|
|
PlayFXOnTag( self.settings.trophydetonationfx, self, "tag_target_lower" );
|
|
self HidePart( "tag_lidar_null", "", true );
|
|
return;
|
|
}
|
|
|
|
self notify("trophy_system_disabled");
|
|
level notify("trophy_system_disabled",self);
|
|
self playsound ("wpn_trophy_disable");
|
|
|
|
self vehicle_ai::Cooldown( "trophy_down", self.settings.trophySystemDownTime );
|
|
while( !self vehicle_ai::IsCooldownReady("trophy_down") || self vehicle_ai::get_current_state() === "off" )
|
|
{
|
|
if ( vehicle_ai::GetCooldownLeft( "trophy_down" ) < 0.5 * self.settings.trophySystemDownTime && ( self.damage_during_trophy_down >= self.settings.trophysystemdisablethreshold ||
|
|
self.spike_hits_during_trophy_down >= 5 ) )
|
|
{
|
|
self vehicle_ai::ClearCooldown( "trophy_down" );
|
|
}
|
|
|
|
wait 1;
|
|
}
|
|
|
|
// player's trophy don't get back up
|
|
driver = self GetSeatOccupant( 0 );
|
|
if( isdefined( driver ) )
|
|
{
|
|
self.trophy_disables = 1;
|
|
}
|
|
|
|
if( !trophy_destroyed() )
|
|
{
|
|
self thread quadtank_enabletrophy();
|
|
}
|
|
}
|
|
|
|
function quadtank_enabletrophy()
|
|
{
|
|
self endon( "death" );
|
|
self notify( "stop_enabletrophy" );
|
|
self endon( "stop_enabletrophy" );
|
|
|
|
//set_trophy_state( true );
|
|
time = (isdefined(self.settings.trophywarmup)?self.settings.trophywarmup:0.1);
|
|
wait time;
|
|
|
|
driver = self GetSeatOccupant( 0 );
|
|
|
|
self.trophy_down = false;
|
|
self.attackerAccuracy = 1;
|
|
self ShowPart( "tag_defense_active" );
|
|
//self ShowPart( "tag_target_upper" );
|
|
|
|
self quadtank_projectile_watcher();
|
|
self thread quadtank_automelee_update();
|
|
|
|
if( !isdefined( driver ) )
|
|
{
|
|
self quadtank_weakpoint_display( true );
|
|
}
|
|
else
|
|
{
|
|
self quadtank_weakpoint_display( false );
|
|
}
|
|
|
|
if ( Target_IsTarget( self ) )
|
|
{
|
|
//Target_Remove( self );
|
|
}
|
|
|
|
if( !isdefined( driver ) )
|
|
{
|
|
self set_side_turrets_enabled( true );
|
|
}
|
|
self.trophy_system_health = self.settings.trophySystemHealth;
|
|
|
|
if( isDefined( level.players) && level.players.size > 0 )
|
|
{
|
|
num_players_trophy_health_modifier = 0.75;
|
|
|
|
if( level.players.size == 2)
|
|
{
|
|
num_players_trophy_health_modifier = 1;
|
|
}
|
|
if( level.players.size == 3)
|
|
{
|
|
num_players_trophy_health_modifier = 1.25;
|
|
}
|
|
if( level.players.size >= 4)
|
|
{
|
|
num_players_trophy_health_modifier = 1.5;
|
|
}
|
|
self.trophy_system_health = self.trophy_system_health * num_players_trophy_health_modifier;
|
|
}
|
|
|
|
if( IsDefined( level.vehicle_defense_cb ) )
|
|
{
|
|
[[level.vehicle_defense_cb]]( self, true );
|
|
}
|
|
|
|
self notify("trophy_system_enabled");
|
|
level notify("trophy_system_enabled",self);
|
|
}
|
|
|
|
// trophy system --------------------------------
|
|
|
|
function quadtank_side_turrets_forward()
|
|
{
|
|
self SetTurretTargetRelativeAngles( (10, -90, 0), 1 );
|
|
self SetTurretTargetRelativeAngles( (10, 90, 0), 2 );
|
|
self.turretRotScale = 1 * self.difficulty_scale_up;
|
|
}
|
|
|
|
// rotates the turret around until he can see his enemy
|
|
function quadtank_turret_scan( scan_forever )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
|
|
self.turretRotScale = 0.3;
|
|
|
|
while( scan_forever || ( !isdefined( self.enemy ) || !(self VehCanSee( self.enemy )) ) )
|
|
{
|
|
if( self.turretontarget && self.turret_state != 0 )
|
|
{
|
|
self.turret_state++;
|
|
if( self.turret_state >= 5 )
|
|
self.turret_state = 1;
|
|
}
|
|
|
|
switch( self.turret_state )
|
|
{
|
|
// reserved for taking damage and looking responsive
|
|
case 0:
|
|
if( isdefined( self.enemy ) )
|
|
{
|
|
self SetLookAtEnt( self.enemy );
|
|
target_vec = self.enemy.origin + ( 0, 0, 40 );
|
|
self SetTargetOrigin( target_vec );
|
|
wait 1.0;
|
|
self ClearLookAtEnt();
|
|
self.turret_state++;
|
|
} // else fall through to FORWARD
|
|
|
|
case 1:
|
|
target_vec = self.origin + AnglesToForward( ( 0, self.angles[1], 0 ) ) * 1000;
|
|
break;
|
|
|
|
case 2:
|
|
target_vec = self.origin + AnglesToForward( ( 0, self.angles[1] + 30, 0 ) ) * 1000;
|
|
break;
|
|
|
|
case 3:
|
|
target_vec = self.origin + AnglesToForward( ( 0, self.angles[1], 0 ) ) * 1000;
|
|
break;
|
|
|
|
case 4:
|
|
target_vec = self.origin + AnglesToForward( ( 0, self.angles[1] - 30, 0 ) ) * 1000;
|
|
break;
|
|
}
|
|
|
|
target_vec = target_vec + ( 0, 0, 40 );
|
|
self SetTargetOrigin( target_vec );
|
|
|
|
wait 0.2;
|
|
}
|
|
}
|
|
|
|
function set_side_turrets_enabled( on )
|
|
{
|
|
if( on )
|
|
{
|
|
turret::enable( 1, false );
|
|
turret::enable( 2, false );
|
|
}
|
|
else
|
|
{
|
|
turret::disable( 1 );
|
|
turret::disable( 2 );
|
|
}
|
|
}
|
|
|
|
function show_weak_spots( show ) // vents on the sides that are exposed when firing the main gun
|
|
{
|
|
if( show )
|
|
{
|
|
self vehicle::toggle_exhaust_fx( 1 );
|
|
}
|
|
else
|
|
{
|
|
self vehicle::toggle_exhaust_fx( 0 );
|
|
}
|
|
}
|
|
|
|
function set_detonation_time( target )
|
|
{
|
|
self endon( "change_state" );
|
|
|
|
self playsound("veh_quadtank_cannon_charge");
|
|
|
|
self waittill( "weapon_fired", proj );
|
|
|
|
self thread railgun_sound(proj);
|
|
|
|
if( isdefined( target ) && isdefined( proj ) )
|
|
{
|
|
vel = proj GetVelocity();
|
|
|
|
proj_speed = length( vel );
|
|
|
|
dist = Distance( proj.origin, target.origin ) + RandomFloatRange( 0, 40 );
|
|
|
|
time_to_enemy = dist / proj_speed;
|
|
|
|
proj ResetMissileDetonationTime( time_to_enemy );
|
|
}
|
|
}
|
|
|
|
function quadtank_weapon_think_cannon()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
|
|
cant_see_enemy_count = 0;
|
|
|
|
self set_side_turrets_enabled( true );
|
|
self SetOnTargetAngle( 10 ); // self.turretontarget will be true when the turret is aimed within this rage
|
|
|
|
self.getreadytofire = undefined;
|
|
|
|
while ( 1 )
|
|
{
|
|
{
|
|
if ( isdefined( self.enemy ) && self VehCanSee( self.enemy ) )
|
|
{
|
|
self SetTurretTargetEnt( self.enemy );
|
|
self SetLookAtEnt( self.enemy );
|
|
}
|
|
|
|
wait 0.2;
|
|
continue;
|
|
}
|
|
|
|
if ( isdefined( self.enemy ) && self VehCanSee( self.enemy ) )
|
|
{
|
|
self.turretRotScale = 1 * self.difficulty_scale_up;
|
|
self SetTurretTargetEnt( self.enemy );
|
|
self SetLookAtEnt( self.enemy );
|
|
|
|
if( cant_see_enemy_count >= 2 )
|
|
{
|
|
wait 0.1; // let the self.turretontarget have time to update so we don't shoot in a bad direction
|
|
|
|
// found enemy, react by changing goal positions
|
|
self CancelAIMove();
|
|
self ClearVehGoalPos();
|
|
self notify( "near_goal" );
|
|
}
|
|
cant_see_enemy_count = 0;
|
|
fired = false;
|
|
|
|
if ( isdefined( self.enemy ) && self VehCanSee( self.enemy ) )
|
|
{
|
|
if( DistanceSquared( self.origin, self.enemy.origin ) > 270 * 270 && self.turretontarget )
|
|
{
|
|
v_my_forward = Anglestoforward( self.angles );
|
|
v_to_enemy = self.enemy.origin - self.origin;
|
|
v_to_enemy = VectorNormalize( v_to_enemy );
|
|
dot = VectorDot( v_to_enemy, v_my_forward );
|
|
|
|
if( dot > 0.707 ) // body is facing within 45' of enemy
|
|
{
|
|
self ASMRequestSubstate( "fire@stationary" );
|
|
self SetTurretTargetEnt( self.enemy );
|
|
self thread set_detonation_time( self.enemy );
|
|
|
|
if( isDefined( level.players) && level.players.size < 3)
|
|
{
|
|
self set_side_turrets_enabled( false );
|
|
}
|
|
|
|
self show_weak_spots( true );
|
|
self.getreadytofire = true;
|
|
fired = true;
|
|
|
|
self CancelAIMove();
|
|
self ClearVehGoalPos();
|
|
self notify( "near_goal" );
|
|
|
|
self.turretRotScale = 0.7;
|
|
|
|
wait 1;
|
|
|
|
level notify( "sndStopCountdown" );
|
|
|
|
self vehicle_ai::waittill_asm_complete( "fire@stationary", 6 );
|
|
|
|
self set_side_turrets_enabled( true );
|
|
|
|
self.turretRotScale = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
self.getreadytofire = undefined;
|
|
|
|
if ( isdefined( self.enemy ) )
|
|
{
|
|
self SetTurretTargetEnt( self.enemy );
|
|
self SetLookAtEnt( self.enemy );
|
|
}
|
|
|
|
if( fired )
|
|
{
|
|
self show_weak_spots( false );
|
|
|
|
vehicle_ai::Cooldown( "main_cannon", RandomFloatRange( 5, 7.5 ) );
|
|
}
|
|
else
|
|
{
|
|
wait 0.25;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cant_see_enemy_count++;
|
|
|
|
wait 0.5;
|
|
|
|
if( cant_see_enemy_count > 40 )
|
|
{
|
|
self quadtank_turret_scan( false );
|
|
}
|
|
else if( cant_see_enemy_count > 30 )
|
|
{
|
|
self ClearLookAtEnt();
|
|
self ClearTargetEntity();
|
|
}
|
|
else
|
|
{
|
|
if( isdefined( self.enemy ) )
|
|
{
|
|
self SetTurretTargetEnt( self.enemy );
|
|
self ClearLookAtEnt();
|
|
}
|
|
else
|
|
{
|
|
self ClearLookAtEnt();
|
|
self quadtank_turret_scan( false );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function Attack_Thread_rocket()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "end_attack_thread" );
|
|
|
|
self vehicle::toggle_ambient_anim_group( 2, false ); // close the weapon doors
|
|
|
|
while( true )
|
|
{
|
|
useJavelin = false;
|
|
|
|
if ( isdefined( self.enemy ) )
|
|
{
|
|
self SetTurretTargetEnt( self.enemy );
|
|
self SetLookAtEnt( self.enemy );
|
|
}
|
|
|
|
if( isdefined( self.enemy) && vehicle_ai::IsCooldownReady( "javelin_rocket_launcher", 0.5 ) )
|
|
{
|
|
if( isVehicle( self.enemy ) || Distance2DSquared( self.origin, self.enemy.origin) >= ( (800) * (800) ) )
|
|
{
|
|
useJavelin = !self vehseenrecently( self.enemy, 3 ) || ( RandomInt( 100 ) < 3 );
|
|
}
|
|
}
|
|
|
|
if ( isdefined( self.enemy ) && vehicle_ai::IsCooldownReady( "rocket_launcher", 0.5 ) )
|
|
{
|
|
if( isDefined( level.players) && level.players.size < 3)
|
|
{
|
|
self set_side_turrets_enabled( false );
|
|
}
|
|
self ClearVehGoalPos();
|
|
self notify( "near_goal" );
|
|
self show_weak_spots( true );
|
|
self vehicle::toggle_ambient_anim_group( 2, true );
|
|
|
|
if( !useJavelin )
|
|
{
|
|
self SetVehWeapon( GetWeapon( "quadtank_main_turret_rocketpods_straight" ) );
|
|
offset = ( 0, 0, -50 );
|
|
if ( isPlayer( self.enemy ) )
|
|
{
|
|
origin = self.enemy.origin;
|
|
eye = self.enemy GetEye();
|
|
offset = ( 0, 0, origin[2] - eye[2] - 5 );
|
|
}
|
|
vehicle_ai::SetTurretTarget( self.enemy, 0, offset );
|
|
}
|
|
else
|
|
{
|
|
self playsound ("veh_quadtank_mlrs_plant_start");
|
|
|
|
self SetVehWeapon( GetWeapon( "quadtank_main_turret_rocketpods_javelin" ) );
|
|
|
|
vehicle_ai::SetTurretTarget( self.enemy, 0, (0,0,300) );
|
|
}
|
|
|
|
wait 1;
|
|
msg = self util::waittill_any_timeout( 2, "turret_on_target", "end_attack_thread" );
|
|
|
|
if ( isdefined( self.enemy ) && Distance2DSquared( self.origin, self.enemy.origin ) > ( (350) * (350) ) )
|
|
{
|
|
fired = false;
|
|
for( i = 0; i < 4 && isdefined( self.enemy ); i++ )
|
|
{
|
|
if( useJavelin )
|
|
{
|
|
if ( isPlayer( self.enemy ) )
|
|
{
|
|
self thread vehicle_ai::Javelin_LoseTargetAtRightTime( self.enemy );
|
|
}
|
|
self thread javeline_incoming(GetWeapon( "quadtank_main_turret_rocketpods_javelin" ));
|
|
}
|
|
self FireWeapon( 0, self.enemy );
|
|
|
|
fired = true;
|
|
wait 0.8;
|
|
}
|
|
|
|
if ( fired )
|
|
{
|
|
vehicle_ai::Cooldown( "rocket_launcher", randomFloatRange( 8, 10 ) );
|
|
|
|
if( useJavelin )
|
|
{
|
|
vehicle_ai::Cooldown( "javelin_rocket_launcher", 20 );
|
|
}
|
|
}
|
|
}
|
|
|
|
self set_side_turrets_enabled( true );
|
|
self vehicle::toggle_ambient_anim_group( 2, false );
|
|
}
|
|
wait 1;
|
|
}
|
|
}
|
|
|
|
// self == player
|
|
function trigger_player_shock_fx()
|
|
{
|
|
if ( !isdefined( self._player_shock_fx_quadtank_melee ) )
|
|
{
|
|
self._player_shock_fx_quadtank_melee = 0;
|
|
}
|
|
|
|
self._player_shock_fx_quadtank_melee = !self._player_shock_fx_quadtank_melee;
|
|
self clientfield::set_to_player( "player_shock_fx", self._player_shock_fx_quadtank_melee );
|
|
}
|
|
|
|
function path_update_interrupt()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
self endon( "near_goal" );
|
|
self endon( "reached_end_node" );
|
|
|
|
wait 1;
|
|
|
|
cantSeeEnemyCount = 0;
|
|
|
|
while( 1 )
|
|
{
|
|
if( isdefined( self.current_pathto_pos ) )
|
|
{
|
|
if( isdefined( self.enemy ) )
|
|
{
|
|
if( distance2dSquared( self.enemy.origin, self.current_pathto_pos ) < 250 * 250 )
|
|
{
|
|
self.move_now = true;
|
|
self notify( "near_goal" );
|
|
}
|
|
|
|
if( !self VehCanSee( self.enemy ) )
|
|
{
|
|
if( !self vehicle_ai::CanSeeEnemyFromPosition( self.current_pathto_pos, self.enemy, 80 ) )
|
|
{
|
|
cantSeeEnemyCount++;
|
|
if( cantSeeEnemyCount > 5 )
|
|
{
|
|
self.move_now = true;
|
|
self notify( "near_goal" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( distance2dSquared( self.current_pathto_pos, self.goalpos ) > self.goalradius * self.goalradius )
|
|
{
|
|
wait 1;
|
|
|
|
self.move_now = true;
|
|
self notify( "near_goal" );
|
|
}
|
|
}
|
|
|
|
wait 0.3;
|
|
}
|
|
}
|
|
|
|
function Movement_Thread_Wander()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
self notify( "end_movement_thread" );
|
|
self endon( "end_movement_thread" );
|
|
|
|
if( self.goalforced )
|
|
{
|
|
return self.goalpos;
|
|
}
|
|
|
|
minSearchRadius = 0;
|
|
maxSearchRadius = 2000;
|
|
halfHeight = 300;
|
|
innerSpacing = 90;
|
|
outerSpacing = innerSpacing * 2;
|
|
maxGoalTimeout = 15;
|
|
|
|
self ASMRequestSubstate( "locomotion@movement" );
|
|
|
|
wait 0.5;
|
|
self SetBrake( 0 );
|
|
|
|
while ( true )
|
|
{
|
|
self SetSpeed( self.settings.defaultMoveSpeed, 5, 5 );
|
|
|
|
PixBeginEvent( "_quadtank::Movement_Thread_Wander" );
|
|
queryResult = PositionQuery_Source_Navigation( self.origin, minSearchRadius, maxSearchRadius, halfHeight, innerSpacing, self, outerSpacing );
|
|
PixEndEvent();
|
|
|
|
// filter
|
|
PositionQuery_Filter_DistanceToGoal( queryResult, self );
|
|
vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult );
|
|
vehicle_ai::PositionQuery_Filter_Random( queryResult, 200, 250 );
|
|
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
if( distance2dSquared( self.origin, point.origin ) < 170 * 170 )
|
|
{
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "tooCloseToSelf" ] = -100; #/ point.score += -100;;
|
|
}
|
|
}
|
|
self vehicle_ai::PositionQuery_DebugScores( queryResult );
|
|
|
|
vehicle_ai::PositionQuery_PostProcess_SortScore( queryResult );
|
|
|
|
foundpath = false;
|
|
goalPos = self.origin;
|
|
count = queryResult.data.size;
|
|
if( count > 3 )
|
|
count = 3;
|
|
|
|
for ( i = 0; i < count && !foundpath; i++ )
|
|
{
|
|
goalPos = queryResult.data[i].origin;
|
|
foundpath = self SetVehGoalPos( goalPos, false, true );
|
|
}
|
|
|
|
if( foundpath )
|
|
{
|
|
|
|
self.current_pathto_pos = goalpos;
|
|
self thread path_update_interrupt();
|
|
self ASMRequestSubstate( "locomotion@movement" );
|
|
|
|
msg = self util::waittill_any_timeout( maxGoalTimeout, "near_goal", "force_goal", "reached_end_node", "goal" );
|
|
self CancelAIMove();
|
|
self ClearVehGoalPos();
|
|
|
|
if( isdefined( self.move_now ) )
|
|
{
|
|
self.move_now = undefined;
|
|
|
|
wait 0.1;
|
|
}
|
|
else
|
|
{
|
|
wait 0.5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self.current_pathto_pos = undefined;
|
|
|
|
goalYaw = self GetGoalYaw();
|
|
|
|
wait 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
function quadtank_movementupdate()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
|
|
//if( distance2dSquared( self.origin, self.goalpos ) > 20 * 20 )
|
|
// self SetVehGoalPos( self.goalpos, true, 2 );
|
|
|
|
self ASMRequestSubstate( "locomotion@movement" );
|
|
wait 0.5;
|
|
|
|
self SetBrake( 0 );
|
|
|
|
while ( self.allow_movement )
|
|
{
|
|
if ( self.getreadytofire !== true )
|
|
{
|
|
goalpos = vehicle_ai::FindNewPosition( 80 );
|
|
|
|
if ( isdefined( goalpos ) && ( Distance2DSquared( goalpos, self.origin ) > ( (50) * (50) ) || Abs( goalpos[2] - self.origin[2] ) > self.height ) )
|
|
{
|
|
self SetSpeed( self.settings.defaultMoveSpeed, 5, 5 );
|
|
self SetVehGoalPos( goalpos, false, true );
|
|
self.current_pathto_pos = goalpos;
|
|
self thread path_update_interrupt();
|
|
self ASMRequestSubstate( "locomotion@movement" );
|
|
result = self util::waittill_any_return( "near_goal", "reached_end_node", "force_goal" );
|
|
}
|
|
else
|
|
{
|
|
self notify( "goal" );
|
|
}
|
|
|
|
self CancelAIMove();
|
|
self ClearVehGoalPos();
|
|
|
|
if ( isdefined( self.move_now ) )
|
|
{
|
|
self.move_now = undefined;
|
|
|
|
wait 0.1;
|
|
}
|
|
else
|
|
{
|
|
wait 0.5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while ( isdefined( self.getreadytofire ) )
|
|
{
|
|
wait 0.2;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
function quadtank_player_fireupdate()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "exit_vehicle" );
|
|
|
|
weapon = self SeatGetWeapon( 1 );
|
|
fireTime = weapon.fireTime;
|
|
|
|
while( 1 )
|
|
{
|
|
self SetGunnerTargetVec( self GetGunnerTargetVec( 0 ), 1 );
|
|
if( self IsGunnerFiring( 0 ) )
|
|
{
|
|
self FireWeapon( 2 );
|
|
}
|
|
wait fireTime;
|
|
}
|
|
}
|
|
|
|
function do_melee( shouldDoDamage, enemy )
|
|
{
|
|
if( !isAlive( enemy ) || distanceSquared( enemy.origin, self.origin ) > ( (270) * (270) ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( vehicle_ai::EntityIsArchetype( enemy, "quadtank" ) || vehicle_ai::EntityIsArchetype( enemy, "raps" ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( isPlayer( enemy ) && enemy laststand::player_is_in_laststand() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
self notify ( "play_meleefx" );
|
|
|
|
if ( shouldDoDamage )
|
|
{
|
|
// don't damage player, but crush player vehicle
|
|
players = GetPlayers();
|
|
foreach( player in players )
|
|
{
|
|
player._takedamage_old = player.takedamage;
|
|
player.takedamage = false;
|
|
}
|
|
|
|
RadiusDamage( self.origin + (0,0,40), 270, 400, 400, self );
|
|
|
|
foreach( player in players )
|
|
{
|
|
player.takedamage = player._takedamage_old;
|
|
player._takedamage_old = undefined;
|
|
}
|
|
}
|
|
|
|
if ( isdefined( enemy ) && isPlayer( enemy ) )
|
|
{
|
|
direction = ( ( enemy.origin - self.origin )[0], ( enemy.origin - self.origin )[1], 0 );
|
|
if ( Abs( direction[0] ) < 0.01 && Abs( direction[1] ) < 0.01 )
|
|
{
|
|
direction = ( RandomFloatRange( 1, 2 ), RandomFloatRange( 1, 2 ), 0 );
|
|
}
|
|
direction = VectorNormalize( direction );
|
|
strength = 1000;
|
|
enemy SetVelocity( enemy GetVelocity() + direction * strength );
|
|
enemy trigger_player_shock_fx();
|
|
enemy DoDamage( 15, self.origin, self );
|
|
}
|
|
|
|
self playsound( "veh_quadtank_emp" );
|
|
|
|
return true;
|
|
}
|
|
|
|
function quadtank_automelee_update()
|
|
{
|
|
self endon( "death" );
|
|
|
|
assert( isdefined( self.team ) );
|
|
|
|
while( !trophy_disabled() )
|
|
{
|
|
enemies = self GetEnemies();
|
|
|
|
meleed = false;
|
|
|
|
foreach( enemy in enemies )
|
|
{
|
|
if( enemy IsNoTarget() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
meleed = meleed || self do_melee( !meleed, enemy );
|
|
if ( meleed )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
wait 0.3;
|
|
}
|
|
}
|
|
|
|
|
|
function quadtank_destroyturret( index )
|
|
{
|
|
turret::disable( index );
|
|
|
|
if( index == 1 )
|
|
{
|
|
self HidePart( "tag_gunner_barrel1" );
|
|
self HidePart( "tag_gunner_turret1" );
|
|
}
|
|
else if( index == 2 )
|
|
{
|
|
self HidePart( "tag_gunner_barrel2" );
|
|
self HidePart( "tag_gunner_turret2" );
|
|
}
|
|
}
|
|
|
|
|
|
function monitor_enter_vehicle()
|
|
{
|
|
self endon( "death" );
|
|
|
|
while( 1 )
|
|
{
|
|
self waittill( "enter_vehicle", player );
|
|
|
|
if ( isdefined( player ) && isPlayer( player ) )
|
|
{
|
|
player vehicle::update_damage_as_occupant( self.maxhealth - self.health, self.maxhealth );
|
|
}
|
|
}
|
|
}
|
|
|
|
function QuadtankCallback_VehicleDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal )
|
|
{
|
|
if ( isdefined( eAttacker ) && ( eAttacker == self || isplayer( eAttacker ) && eAttacker.usingvehicle && eAttacker.viewlockedentity === self ) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ( sMeansOfDeath === "MOD_MELEE" || sMeansOfDeath === "MOD_MELEE_WEAPON_BUTT" || sMeansOfDeath === "MOD_MELEE_ASSASSINATE" || sMeansOfDeath === "MOD_ELECTROCUTED" || sMeansOfDeath === "MOD_CRUSH" || weapon.isEmp )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
iDamage = self killstreaks::OnDamagePerWeapon( "quadtank", eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, self.maxhealth, undefined, self.maxhealth * 0.4, undefined, 0, undefined, true, 1.0 );
|
|
|
|
|
|
driver = self GetSeatOccupant( 0 );
|
|
if ( isPlayer( driver ) )
|
|
{
|
|
driver vehicle::update_damage_as_occupant( self.maxhealth - ( self.health - iDamage ), self.maxhealth );
|
|
}
|
|
|
|
return iDamage;
|
|
}
|
|
|
|
function quadtank_set_team( team )
|
|
{
|
|
self.team = team;
|
|
|
|
if( !self vehicle_ai::is_instate( "off" ) )
|
|
{
|
|
self vehicle_ai::blink_lights_for_time( 0.5 );
|
|
}
|
|
}
|
|
|
|
function remove_repulsor()
|
|
{
|
|
if( isdefined( self.missile_repulsor ) )
|
|
{
|
|
missile_deleteattractor( self.missile_repulsor );
|
|
self.missile_repulsor = undefined;
|
|
}
|
|
self notify( "end_repulsor_fx" );
|
|
}
|
|
|
|
function repulsor_fx()
|
|
{
|
|
self notify( "end_repulsor_fx" );
|
|
self endon( "end_repulsor_fx" );
|
|
self endon( "death" );
|
|
self endon( "change_state" );
|
|
|
|
while( 1 )
|
|
{
|
|
self util::waittill_any( "projectile_applyattractor", "play_meleefx" );
|
|
if ( vehicle_ai::IsCooldownReady("repulsorfx_interval") )
|
|
{
|
|
PlayFxOnTag( self.settings.trophyrepulsefx, self, "tag_body" );
|
|
|
|
self vehicle::impact_fx( self.settings.trophyrepulsefx_ground );
|
|
|
|
vehicle_ai::Cooldown( "repulsorfx_interval", 0.5 );
|
|
|
|
self PlaySound( "wpn_quadtank_shield_impact" );
|
|
}
|
|
}
|
|
}
|
|
|
|
function quadtank_projectile_watcher()
|
|
{
|
|
if( !isdefined( self.missile_repulsor ) )
|
|
{
|
|
self.missile_repulsor = missile_createrepulsorent( self, 40000, self.settings.trophysystemrange, true );
|
|
}
|
|
self thread repulsor_fx();
|
|
}
|
|
|
|
function turn_off_laser_after( time )
|
|
{
|
|
self notify( "turn_off_laser_thread" );
|
|
self endon( "turn_off_laser_thread" );
|
|
self endon( "death" );
|
|
|
|
wait time;
|
|
|
|
self LaserOff();
|
|
}
|
|
|
|
|
|
|
|
//self = turret/vehicle
|
|
function side_turret_is_target_in_view_score( v_target, n_index )
|
|
{
|
|
s_turret = turret::_get_turret_data( n_index );
|
|
|
|
v_pivot_pos = self GetTagOrigin( s_turret.str_tag_pivot );
|
|
v_angles_to_target = VectorToAngles( v_target - v_pivot_pos );
|
|
|
|
n_rest_angle_pitch = s_turret.n_rest_angle_pitch + self.angles[0];
|
|
n_rest_angle_yaw = s_turret.n_rest_angle_yaw + self.angles[1];
|
|
|
|
n_ang_pitch = AngleClamp180( v_angles_to_target[0] - n_rest_angle_pitch );
|
|
n_ang_yaw = AngleClamp180( v_angles_to_target[1] - n_rest_angle_yaw );
|
|
|
|
b_out_of_range = false;
|
|
|
|
if ( n_ang_pitch > 0 )
|
|
{
|
|
if ( n_ang_pitch > s_turret.bottomarc )
|
|
{
|
|
b_out_of_range = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( Abs( n_ang_pitch ) > s_turret.toparc )
|
|
{
|
|
b_out_of_range = true;
|
|
}
|
|
}
|
|
|
|
if ( n_ang_yaw > 0 )
|
|
{
|
|
if ( n_ang_yaw > s_turret.leftarc )
|
|
{
|
|
b_out_of_range = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( Abs( n_ang_yaw ) > s_turret.rightarc )
|
|
{
|
|
b_out_of_range = true;
|
|
}
|
|
}
|
|
|
|
if( b_out_of_range )
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
return ( Abs( n_ang_yaw ) / 90 * 800 );
|
|
}
|
|
|
|
function _get_best_target_quadtank_side_turret( a_potential_targets, n_index )
|
|
{
|
|
takeEasyOnOneTarget = MapFloat( 0, 4, 800, 0, level.gameskill );
|
|
|
|
if ( n_index === 1 )
|
|
{
|
|
other_turret_target = turret::get_target( 2 );
|
|
}
|
|
else if ( n_index === 2 )
|
|
{
|
|
other_turret_target = turret::get_target( 1 );
|
|
}
|
|
|
|
e_best_target = undefined;
|
|
f_best_score = 100000; // lower is better
|
|
|
|
s_turret = turret::_get_turret_data( n_index );
|
|
|
|
foreach( e_target in a_potential_targets )
|
|
{
|
|
f_score = Distance( self.origin, e_target.origin );
|
|
|
|
b_current_target = turret::is_target( e_target, n_index );
|
|
|
|
if( b_current_target )
|
|
{
|
|
f_score -= 100;
|
|
}
|
|
|
|
if( e_target === self.enemy )
|
|
{
|
|
f_score += 300;
|
|
}
|
|
|
|
if ( e_target === other_turret_target )
|
|
{
|
|
f_score += ( 100 + takeEasyOnOneTarget );
|
|
}
|
|
|
|
if( IsSentient( e_target ) && e_target AttackedRecently( self, 2 ) )
|
|
{
|
|
f_score -= 200;
|
|
}
|
|
|
|
if ( isAlive( self.lockon_owner ) && e_target === self.lockon_owner )
|
|
{
|
|
f_score -= 1000;
|
|
}
|
|
|
|
v_offset = turret::_get_default_target_offset( e_target, n_index );
|
|
|
|
view_score = side_turret_is_target_in_view_score( e_target.origin + v_offset, n_index );
|
|
|
|
if( view_score != 0.0 )
|
|
{
|
|
f_score += view_score;
|
|
|
|
b_trace_passed = turret::trace_test( e_target, v_offset, n_index );
|
|
|
|
if ( b_current_target && !b_trace_passed && !isdefined( s_turret.n_time_lose_sight ) )
|
|
{
|
|
s_turret.n_time_lose_sight = GetTime();
|
|
}
|
|
|
|
if( b_trace_passed )
|
|
{
|
|
f_score -= 500;
|
|
}
|
|
}
|
|
else if ( b_current_target )
|
|
{
|
|
s_turret.b_target_out_of_range = true;
|
|
f_score += 5000;
|
|
}
|
|
|
|
if( f_score < f_best_score )
|
|
{
|
|
f_best_score = f_score;
|
|
e_best_target = e_target;
|
|
}
|
|
}
|
|
|
|
return e_best_target;
|
|
}
|
|
|
|
function quadtank_weakpoint_display( state )
|
|
{
|
|
if( self.displayweakpoint !== state )
|
|
{
|
|
self.displayweakpoint = state;
|
|
|
|
if( !self.displayweakpoint && self.weakpointobjective === 1 )
|
|
{
|
|
self.weakpointobjective = 0;
|
|
}
|
|
|
|
player = level.players[0];
|
|
if( self.displayweakpoint && self.combatactive && self.weakpointobjective !== 1 && ( !isdefined( player ) || player.team !== self.team ) )
|
|
{
|
|
self.weakpointobjective = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
function footstep_handler()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "exit_vehicle" );
|
|
|
|
while( 1 )
|
|
{
|
|
note = self util::waittill_any_return( "footstep_front_left", "footstep_front_right", "footstep_rear_left", "footstep_rear_right" );
|
|
|
|
switch( note )
|
|
{
|
|
case "footstep_front_left":
|
|
{
|
|
bone = "tag_foot_fx_left_front";
|
|
break;
|
|
}
|
|
case "footstep_front_right":
|
|
{
|
|
bone = "tag_foot_fx_right_front";
|
|
break;
|
|
}
|
|
case "footstep_rear_left":
|
|
{
|
|
bone = "tag_foot_fx_left_back";
|
|
break;
|
|
}
|
|
case "footstep_rear_right":
|
|
{
|
|
bone = "tag_foot_fx_right_back";
|
|
break;
|
|
}
|
|
}
|
|
|
|
position = self GetTagOrigin( bone ) + (0,0,15);
|
|
|
|
self RadiusDamage( position, 60, 50, 50, self, "MOD_CRUSH" );
|
|
}
|
|
}
|
|
|
|
function javeline_incoming(projectile)
|
|
{
|
|
self endon( "entityshutdown" );
|
|
self endon ("death");
|
|
|
|
self waittill( "weapon_fired", projectile );
|
|
|
|
distance = 1400;
|
|
alias = "prj_quadtank_javelin_incoming";
|
|
|
|
wait(5);
|
|
|
|
if(!isdefined( projectile ) )
|
|
return;
|
|
|
|
while(isdefined(projectile) && isdefined( projectile.origin ))
|
|
{
|
|
if ( isdefined( self.enemy ) && isdefined( self.enemy.origin ))
|
|
{
|
|
projectileDistance = DistanceSquared( projectile.origin, self.enemy.origin);
|
|
|
|
if( projectileDistance <= distance * distance )
|
|
{
|
|
projectile playsound (alias);
|
|
return;
|
|
}
|
|
}
|
|
|
|
wait (.2);
|
|
}
|
|
}
|
|
|
|
function railgun_sound(projectile)
|
|
{
|
|
self endon( "entityshutdown" );
|
|
self endon ("death");
|
|
|
|
self waittill( "weapon_fired", projectile );
|
|
|
|
distance = 900;
|
|
alais = "wpn_quadtank_railgun_fire_rocket_flux";
|
|
players = level.players;
|
|
|
|
while(isdefined(projectile) && isdefined( projectile.origin ))
|
|
{
|
|
if ( isdefined( players[0] ) && isdefined( players[0].origin ))
|
|
{
|
|
projectileDistance = DistanceSquared( projectile.origin, players[0].origin);
|
|
|
|
if( projectileDistance <= distance * distance )
|
|
{
|
|
projectile playsound (alais);
|
|
return;
|
|
}
|
|
}
|
|
|
|
wait (.2);
|
|
}
|
|
|
|
}
|
|
|
|
|