boiii-scripts/mp/vehicles/_quadtank.gsc
2023-04-13 17:30:38 +02:00

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);
}
}