540 lines
24 KiB
Plaintext
540 lines
24 KiB
Plaintext
#using scripts\codescripts\struct;
|
|
|
|
#using scripts\shared\array_shared;
|
|
#using scripts\shared\callbacks_shared;
|
|
#using scripts\shared\clientfield_shared;
|
|
#using scripts\shared\killstreaks_shared;
|
|
#using scripts\shared\math_shared;
|
|
#using scripts\shared\statemachine_shared;
|
|
#using scripts\shared\system_shared;
|
|
#using scripts\shared\turret_shared;
|
|
#using scripts\shared\util_shared;
|
|
#using scripts\shared\vehicle_ai_shared;
|
|
#using scripts\shared\vehicle_death_shared;
|
|
#using scripts\shared\vehicle_shared;
|
|
|
|
#using scripts\mp\_util;
|
|
#using scripts\mp\gametypes\_shellshock;
|
|
#using scripts\mp\killstreaks\_killstreakrules;
|
|
#using scripts\mp\killstreaks\_airsupport;
|
|
#using scripts\mp\killstreaks\_killstreaks;
|
|
#using scripts\mp\killstreaks\_remote_weapons;
|
|
#using scripts\mp\teams\_teams;
|
|
#using scripts\shared\weapons\_heatseekingmissile;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#precache( "fx", "explosions/fx_vexp_wasp_gibb_death" );
|
|
|
|
#namespace flak_drone;
|
|
|
|
|
|
|
|
|
|
function init()
|
|
{
|
|
clientfield::register( "vehicle", "flak_drone_camo", 1, 3, "int" );
|
|
|
|
vehicle::add_main_callback( "veh_flak_drone_mp", &InitFlakDrone );
|
|
}
|
|
|
|
function InitFlakDrone()
|
|
{
|
|
self.health = self.healthdefault;
|
|
self vehicle::friendly_fire_shield();
|
|
self EnableAimAssist();
|
|
self SetNearGoalNotifyDist( ( 40 ) );
|
|
self SetHoverParams( ( 50.0 ), ( 75.0 ), ( 100.0 ) );
|
|
self SetVehicleAvoidance( true );
|
|
self.fovcosine = 0; // +/-90 degrees = 180
|
|
self.fovcosinebusy = 0; //+/- 55 degrees = 110 fov
|
|
self.vehAirCraftCollisionEnabled = true;
|
|
self.goalRadius = 999999;
|
|
self.goalHeight = 999999;
|
|
self SetGoal( self.origin, false, self.goalRadius, self.goalHeight );
|
|
self thread vehicle_ai::nudge_collision();
|
|
|
|
self.overrideVehicleDamage = &FlakDroneDamageOverride;
|
|
|
|
self vehicle_ai::init_state_machine_for_role( "default" );
|
|
self vehicle_ai::get_state_callbacks( "combat" ).enter_func = &state_combat_enter;
|
|
self vehicle_ai::get_state_callbacks( "combat" ).update_func = &state_combat_update;
|
|
self vehicle_ai::get_state_callbacks( "off" ).enter_func = &state_off_enter;
|
|
self vehicle_ai::get_state_callbacks( "off" ).update_func = &state_off_update;
|
|
self vehicle_ai::get_state_callbacks( "death" ).update_func = &state_death_update;
|
|
|
|
self vehicle_ai::StartInitialState( "off" );
|
|
}
|
|
|
|
function state_off_enter( params )
|
|
{
|
|
}
|
|
|
|
function state_off_update( params )
|
|
{
|
|
self endon( "change_state" );
|
|
self endon( "death" );
|
|
|
|
while( !isdefined( self.parent ) )
|
|
{
|
|
wait( 0.1 ); //Wait for parent to be setup
|
|
}
|
|
|
|
self.parent endon( "death" );
|
|
|
|
while( true )
|
|
{
|
|
self SetSpeed( ( 400 ) );
|
|
|
|
if( ( isdefined( self.inpain ) && self.inpain ) )
|
|
{
|
|
wait( ( 0.1 ) );
|
|
}
|
|
|
|
self ClearLookAtEnt();
|
|
|
|
self.current_pathto_pos = undefined;
|
|
|
|
queryOrigin = self.parent.origin + ( 0, 0, ( -75 ) );
|
|
queryResult = PositionQuery_Source_Navigation( queryOrigin,
|
|
( 25 ),
|
|
( 75 ),
|
|
( 40 ),
|
|
( 40 ),
|
|
self );
|
|
|
|
if( isdefined( queryResult ) )
|
|
{
|
|
PositionQuery_Filter_DistanceToGoal( queryResult, self );
|
|
vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult );
|
|
|
|
best_point = undefined;
|
|
best_score = -999999;
|
|
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
randomScore = randomFloatRange( 0, 100 );
|
|
distToOriginScore = point.distToOrigin2D * 0.2;
|
|
|
|
point.score += randomScore + distToOriginScore;
|
|
/# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distToOrigin" ] = distToOriginScore; #/ point.score += distToOriginScore;;
|
|
|
|
if ( point.score > best_score )
|
|
{
|
|
best_score = point.score;
|
|
best_point = point;
|
|
}
|
|
}
|
|
|
|
self vehicle_ai::PositionQuery_DebugScores( queryResult );
|
|
|
|
if( isdefined( best_point ) )
|
|
{
|
|
self.current_pathto_pos = best_point.origin;
|
|
}
|
|
}
|
|
|
|
if( IsDefined( self.current_pathto_pos ) )
|
|
{
|
|
self UpdateFlakDroneSpeed();
|
|
if( self SetVehGoalPos( self.current_pathto_pos, true, false ) )
|
|
{
|
|
self playsound ("veh_wasp_vox");
|
|
}
|
|
else
|
|
{
|
|
self SetSpeed( ( 400 ) * 3 );
|
|
self.current_pathto_pos = self GetClosestPointOnNavVolume( self.origin, 999999 );
|
|
self SetVehGoalPos( self.current_pathto_pos, true, false );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( isDefined( self.parent.heliGoalPos ) )
|
|
{
|
|
self.current_pathto_pos = self.parent.heliGoalPos;
|
|
}
|
|
else
|
|
{
|
|
self.current_pathto_pos = queryOrigin;
|
|
}
|
|
self UpdateFlakDroneSpeed();
|
|
self SetVehGoalPos( self.current_pathto_pos, true, false );
|
|
}
|
|
|
|
wait RandomFloatRange( ( .1 ), ( .2 ) );
|
|
}
|
|
}
|
|
|
|
function UpdateFlakDroneSpeed() // self == flak drone
|
|
{
|
|
desiredSpeed = ( 400 );
|
|
|
|
if ( isdefined( self.parent ) )
|
|
{
|
|
parentSpeed = self.parent GetSpeed();
|
|
desiredSpeed = parentSpeed * 0.9; // lag a little back
|
|
if ( Distance2DSquared( self.parent.origin, self.origin ) > ( (36) * (36) ) ) // match parent speed if too far from parent
|
|
{
|
|
if ( isdefined( self.current_pathto_pos ) )
|
|
{
|
|
flakDroneDistanceToGoalSquared = Distance2DSquared( self.origin, self.current_pathto_pos );
|
|
parentDistanceToGoalSquared = Distance2DSquared( self.parent.origin, self.current_pathto_pos );
|
|
if ( flakDroneDistanceToGoalSquared > parentDistanceToGoalSquared )
|
|
{
|
|
desiredSpeed = parentSpeed * 1.3;
|
|
}
|
|
else
|
|
{
|
|
desiredSpeed = parentSpeed * 0.8;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self SetSpeed( max( desiredSpeed, 10 ) );
|
|
}
|
|
|
|
function state_combat_enter( params )
|
|
{
|
|
}
|
|
|
|
function state_combat_update( params )
|
|
{
|
|
drone = self;
|
|
drone endon( "change_state" );
|
|
drone endon( "death" );
|
|
|
|
drone thread SpawnFlakRocket( drone.incoming_missile, drone.origin, drone.parent );
|
|
drone Ghost();
|
|
}
|
|
|
|
function SpawnFlakRocket( missile, spawnPos, parent )
|
|
{
|
|
drone = self;
|
|
|
|
missile endon("death");
|
|
missile Missile_SetTarget( parent );
|
|
|
|
rocket = MagicBullet( GetWeapon( "flak_drone_rocket" ), spawnPos, missile.origin, parent, missile );
|
|
rocket.team = parent.team;
|
|
rocket setTeam( parent.team );
|
|
rocket clientfield::set( "enemyvehicle", 1 );
|
|
rocket Missile_SetTarget( missile );
|
|
missile thread CleanupAfterMissileDeath( rocket, drone ); // sometimes missile gets destroyed by the rocket
|
|
|
|
curDist = Distance( missile.origin, rocket.origin ); // note: algorithm requires distance (not distance squared)
|
|
|
|
tooCloseToPredictedParent = false;
|
|
|
|
/#
|
|
debug_draw = GetDvarInt( "scr_flak_drone_debug_trails", 0 );
|
|
debug_duration = GetDvarInt( "scr_flak_drone_debug_trails_duration", 20 * 20 ); // duration in number of server frames
|
|
#/
|
|
|
|
while( true )
|
|
{
|
|
{wait(.05);};
|
|
|
|
prevDist = curDist;
|
|
|
|
if ( isdefined( rocket ) )
|
|
{
|
|
curDist = Distance( missile.origin, rocket.origin ); // can't use DistanceSquared() here
|
|
|
|
distDelta = prevDist - curDist;
|
|
|
|
predictedDist = curDist - distDelta;
|
|
}
|
|
|
|
/#
|
|
if ( debug_draw && isdefined( missile ) )
|
|
util::debug_sphere( missile.origin, 6, ( 0.9, 0, 0 ), 0.9, debug_duration ); // small red sphere for missile trail
|
|
|
|
if ( debug_draw && isdefined( rocket ) )
|
|
util::debug_sphere( rocket.origin, 6, ( 0, 0, 0.9 ), 0.9, debug_duration ); // small blue spheres for flak drone trail (as rocket)
|
|
#/
|
|
|
|
if ( isdefined( parent ) )
|
|
{
|
|
parentVelocity = parent GetVelocity();
|
|
parentPredictedLocation = parent.origin + ( parentVelocity * 0.05 );
|
|
missileVelocity = missile GetVelocity();
|
|
missilePredictedLocation = missile.origin + ( missileVelocity * 0.05 );
|
|
if ( DistanceSquared( parentPredictedLocation, missilePredictedLocation ) < ( (1000) * (1000) )
|
|
|| DistanceSquared( parent.origin, missilePredictedLocation ) < ( (1000) * (1000) ) )
|
|
{
|
|
tooCloseToPredictedParent = true;
|
|
}
|
|
}
|
|
|
|
if( ( predictedDist < 0 ) || ( curDist > prevDist ) || tooCloseToPredictedParent || !isdefined( rocket ) )
|
|
{
|
|
/#
|
|
if ( debug_draw && isdefined( parent ) )
|
|
{
|
|
if ( tooCloseToPredictedParent && ! ( ( predictedDist < 0 ) || ( curDist > prevDist ) ) )
|
|
{
|
|
util::debug_sphere( parent.origin, 18, ( 0.9, 0, 0.9 ), 0.9, debug_duration ); // large purple sphere means too close to parent
|
|
}
|
|
else
|
|
{
|
|
util::debug_sphere( parent.origin, 18, ( 0, 0.9, 0 ), 0.9, debug_duration ); // large green sphere means intercepted
|
|
}
|
|
}
|
|
#/
|
|
|
|
if ( isdefined( rocket ) )
|
|
{
|
|
rocket detonate();
|
|
}
|
|
missile thread heatseekingmissile::_missileDetonate( missile.target_attacker, missile.target_weapon, missile.target_weapon.explosionradius, 10, 20 );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function CleanupAfterMissileDeath( rocket, flak_drone )
|
|
{
|
|
missile = self;
|
|
missile waittill( "death" );
|
|
|
|
wait 0.5; // make sure explosions fire off before deleting
|
|
|
|
if ( isdefined( rocket ) )
|
|
{
|
|
rocket delete();
|
|
}
|
|
|
|
if ( isdefined( flak_drone ) )
|
|
{
|
|
flak_drone delete();
|
|
}
|
|
}
|
|
|
|
function state_death_update( params )
|
|
{
|
|
self endon( "death" );
|
|
doGibbedDeath = false;
|
|
|
|
if( isdefined( self.death_info ) )
|
|
{
|
|
if( isdefined( self.death_info.weapon ) )
|
|
{
|
|
if( self.death_info.weapon.dogibbing || self.death_info.weapon.doannihilate )
|
|
{
|
|
doGibbedDeath = true;
|
|
}
|
|
}
|
|
if( isdefined( self.death_info.meansOfDeath ) )
|
|
{
|
|
meansOfDeath = self.death_info.meansOfDeath;
|
|
if( meansOfDeath == "MOD_EXPLOSIVE" || meansOfDeath == "MOD_GRENADE_SPLASH" || meansOfDeath == "MOD_PROJECTILE_SPLASH" || meansOfDeath == "MOD_PROJECTILE" )
|
|
{
|
|
doGibbedDeath = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( doGibbedDeath )
|
|
{
|
|
self playsound ("veh_wasp_gibbed");
|
|
PlayFxOnTag( "explosions/fx_vexp_wasp_gibb_death", self, "tag_origin" );
|
|
self Ghost();
|
|
self NotSolid();
|
|
|
|
wait( 5 );
|
|
if( isdefined( self ) )
|
|
{
|
|
self Delete();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self vehicle_death::flipping_shooting_death();
|
|
}
|
|
}
|
|
|
|
function drone_pain_for_time( time, stablizeParam, restoreLookPoint )
|
|
{
|
|
self endon( "death" );
|
|
|
|
self.painStartTime = GetTime();
|
|
|
|
if ( !( isdefined( self.inpain ) && self.inpain ) )
|
|
{
|
|
self.inpain = true;
|
|
|
|
while ( GetTime() < self.painStartTime + time * 1000 )
|
|
{
|
|
self SetVehVelocity( self.velocity * stablizeParam );
|
|
self SetAngularVelocity( self GetAngularVelocity() * stablizeParam );
|
|
wait 0.1;
|
|
}
|
|
|
|
if ( isdefined( restoreLookPoint ) )
|
|
{
|
|
restoreLookEnt = Spawn( "script_model", restoreLookPoint );
|
|
restoreLookEnt SetModel( "tag_origin" );
|
|
|
|
self ClearLookAtEnt();
|
|
self SetLookAtEnt( restoreLookEnt );
|
|
self setTurretTargetEnt( restoreLookEnt );
|
|
wait 1.5;
|
|
|
|
self ClearLookAtEnt();
|
|
self ClearTurretTarget();
|
|
restoreLookEnt delete();
|
|
}
|
|
|
|
self.inpain = false;
|
|
}
|
|
}
|
|
|
|
function drone_pain( eAttacker, damageType, hitPoint, hitDirection, hitLocationInfo, partName )
|
|
{
|
|
if ( !( isdefined( self.inpain ) && self.inpain ) )
|
|
{
|
|
yaw_vel = math::randomSign() * RandomFloatRange( 280, 320 );
|
|
|
|
ang_vel = self GetAngularVelocity();
|
|
ang_vel += ( RandomFloatRange( -120, -100 ), yaw_vel, RandomFloatRange( -200, 200 ) );
|
|
self SetAngularVelocity( ang_vel );
|
|
|
|
self thread drone_pain_for_time( 0.8, 0.7 );
|
|
}
|
|
}
|
|
|
|
function FlakDroneDamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal )
|
|
{
|
|
if( sMeansOfDeath == "MOD_TRIGGER_HURT" )
|
|
return 0;
|
|
|
|
if( isdefined( eAttacker ) && isdefined( eAttacker.team ) && eAttacker.team != self.team )
|
|
{
|
|
drone_pain( eAttacker, sMeansOfDeath, vPoint, vDir, sHitLoc, partName );
|
|
}
|
|
|
|
return iDamage;
|
|
}
|
|
|
|
function Spawn( parent, onDeathCallback )
|
|
{
|
|
if( !IsNavVolumeLoaded() )
|
|
{
|
|
/# IPrintLnBold( "Error: NavVolume Not Loaded" ); #/
|
|
return undefined;
|
|
}
|
|
|
|
spawnPoint = parent.origin + ( 0, 0, -50 );
|
|
|
|
drone = SpawnVehicle( "veh_flak_drone_mp", spawnPoint, parent.angles, "dynamic_spawn_ai" );
|
|
drone.death_callback = onDeathCallback;
|
|
drone configureTeam( parent, false );
|
|
drone thread WatchGameEvents();
|
|
drone thread WatchDeath();
|
|
drone thread WatchParentDeath();
|
|
drone thread WatchParentMissiles();
|
|
return drone;
|
|
}
|
|
|
|
function configureTeam( parent, isHacked )
|
|
{
|
|
drone = self;
|
|
drone.team = parent.team;
|
|
drone SetTeam( parent.team );
|
|
if ( isHacked )
|
|
{
|
|
drone clientfield::set( "enemyvehicle", 2 );
|
|
}
|
|
else
|
|
{
|
|
drone clientfield::set( "enemyvehicle", 1 );
|
|
}
|
|
drone.parent = parent;
|
|
|
|
}
|
|
|
|
function WatchGameEvents()
|
|
{
|
|
drone = self;
|
|
drone endon( "death" );
|
|
|
|
drone.parent.owner util::waittill_any( "game_ended", "emp_jammed", "disconnect", "joined_team" );
|
|
drone Shutdown( true );
|
|
}
|
|
|
|
function WatchDeath()
|
|
{
|
|
drone = self;
|
|
drone.parent endon( "death" );
|
|
|
|
drone waittill( "death" );
|
|
drone Shutdown( true );
|
|
}
|
|
|
|
function WatchParentDeath()
|
|
{
|
|
drone = self;
|
|
drone endon( "death" );
|
|
|
|
drone.parent waittill( "death" );
|
|
drone Shutdown( true );
|
|
}
|
|
|
|
function WatchParentMissiles()
|
|
{
|
|
drone = self;
|
|
drone endon( "death" );
|
|
drone.parent endon( "death" );
|
|
|
|
drone.parent waittill( "stinger_fired_at_me", missile, weapon, attacker );
|
|
|
|
drone.incoming_missile = missile;
|
|
drone.incoming_missile.target_weapon = weapon;
|
|
drone.incoming_missile.target_attacker = attacker;
|
|
drone vehicle_ai::set_state( "combat" );
|
|
}
|
|
|
|
function SetCamoState( state )
|
|
{
|
|
self clientfield::set( "flak_drone_camo", state );
|
|
}
|
|
|
|
function Shutdown( explode )
|
|
{
|
|
drone = self;
|
|
|
|
if( isdefined( drone.death_callback ) )
|
|
{
|
|
drone.parent thread [[ drone.death_callback ]]();
|
|
}
|
|
|
|
if( isdefined( drone ) && !isdefined( drone.parent ) )
|
|
{
|
|
drone Ghost();
|
|
drone NotSolid();
|
|
wait( 5 );
|
|
if( isdefined( drone ) )
|
|
drone Delete();
|
|
}
|
|
|
|
if( isdefined( drone ) )
|
|
{
|
|
if( explode )
|
|
{
|
|
drone DoDamage( drone.health + 1000, drone.origin, drone, drone, "none", "MOD_EXPLOSIVE" );
|
|
}
|
|
else
|
|
{
|
|
drone Delete();
|
|
}
|
|
}
|
|
}
|