1101 lines
26 KiB
Plaintext
1101 lines
26 KiB
Plaintext
#include common_scripts\utility;
|
|
#include maps\mp\agents\_scriptedAgents;
|
|
#include maps\mp\agents\_agent_utility;
|
|
#include maps\mp\gametypes\_damage;
|
|
|
|
main()
|
|
{
|
|
self setupAlienState();
|
|
|
|
self thread think();
|
|
self thread watchOwnerDamage();
|
|
self thread watchOwnerDeath();
|
|
self thread watchOwnerTeamChange();
|
|
self thread WaitForBadPath();
|
|
self thread WaitForPathSet();
|
|
|
|
/#
|
|
self thread debug_dog();
|
|
#/
|
|
}
|
|
|
|
setupAlienState()
|
|
{
|
|
self.bLockGoalPos = false;
|
|
self.ownerRadiusSq = 144 * 144;
|
|
self.meleeRadiusSq = 90*90; //128 * 128;
|
|
self.attackOffset = 25 + self.radius;
|
|
self.attackRadiusSq = 450 * 450;
|
|
self.warningRadiusSq = 550 * 550;
|
|
self.warningZHeight = 128 * 0.75; //min floor to ceiling in a map is 128 units
|
|
self.attackZHeight = 54; //how high we'll allow the alien to jump to attack its target.
|
|
self.attackZHeightDown = -64; //how low we'll allow the alien to jump to attack its target.
|
|
self.ownerDamagedRadiusSq = 1500 * 1500; //if the owner takes damage and the attacker is within this radius
|
|
self.dogDamagedRadiusSq = 6500 * 6500; //if the alien takes damage and the attacker is within this radius. Dog = 1000 * 1000
|
|
self.keepPursuingTargetRadiusSq = 6000 * 6000; //stop pursuing the target after the target gets this far away from my owner, unless he's my favorite enemy.
|
|
self.preferredOffsetFromOwner = 76;
|
|
self.minOffsetFromOwner = 50; //need to keep this above owner.radius + self.radius.
|
|
self.forceAttack = true; //if we want to send after a target. Dog = false
|
|
self.ignoreCloseFoliage = true;
|
|
self.moveMode = "run";
|
|
self.enableExtendedKill = true;
|
|
self.attackState = "idle"; // self.aiState comes from code, so we need something to tell us what attack state we're in
|
|
self.moveState = "idle";
|
|
self.bHasBadPath = false;
|
|
self.timeOfLastDamage = 0;
|
|
|
|
|
|
// self.PathNodeArrayIndex = 0;
|
|
// Send the alien to the closest node on the pathnodearray
|
|
self.Alien_GoalPos = get_closest( self.origin , self.PathNodeArray );
|
|
self.allowCrouch = true;
|
|
|
|
self ScrAgentSetGoalRadius(24); // alien will get real twitchy about stopping if this falls below 16.
|
|
}
|
|
|
|
init()
|
|
{
|
|
//IPrintLnBold ("running INIT - settig up function calls");
|
|
self.animCBs = SpawnStruct();
|
|
self.animCBs.OnEnter = [];
|
|
self.animCBs.OnEnter[ "idle" ] = maps\mp\mp_dome_ns_alien_idle::main;
|
|
self.animCBs.OnEnter[ "move" ] = maps\mp\mp_dome_ns_alien_move::main;
|
|
self.animCBs.OnEnter[ "traverse" ] = maps\mp\mp_dome_ns_alien_traverse::main;
|
|
|
|
self.animCBs.OnExit = [];
|
|
self.animCBs.OnExit[ "idle" ] = maps\mp\mp_dome_ns_alien_idle::end_script;
|
|
self.animCBs.OnExit[ "move" ] = maps\mp\mp_dome_ns_alien_move::end_script;
|
|
self.animCBs.OnExit[ "traverse" ] = maps\mp\mp_dome_ns_alien_traverse::end_script;
|
|
|
|
self Suicide();
|
|
|
|
self.watchAttackStateFunc = ::watchAttackState;
|
|
|
|
self.aiState = "idle";
|
|
self.moveMode = "fastwalk";
|
|
|
|
self.radius = 15;
|
|
self.height = 40;
|
|
}
|
|
|
|
onEnterAnimState( prevState, nextState )
|
|
{
|
|
self notify( "killanimscript" );
|
|
|
|
if ( !IsDefined( self.animCBs.OnEnter[ nextState ] ) )
|
|
return;
|
|
|
|
if ( prevState == nextState && ( nextState != "alien_traverse" ) )
|
|
return;
|
|
|
|
if ( IsDefined( self.animCBs.OnExit[ prevState ] ) )
|
|
self [[ self.animCBs.OnExit[ prevState ] ]] ();
|
|
|
|
ExitAIState( self.aiState );
|
|
|
|
self.aiState = nextState;
|
|
|
|
EnterAIState( nextState );
|
|
|
|
self [[ self.animCBs.OnEnter[ nextState ] ]]();
|
|
}
|
|
|
|
think()
|
|
{
|
|
//IPrintLnBold ("start thinking");
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
if ( IsDefined( self.owner ) )
|
|
{
|
|
self endon( "owner_disconnect" );
|
|
self thread destroyOnOwnerDisconnect( self.owner );
|
|
}
|
|
|
|
self thread [[self.watchAttackStateFunc]]();
|
|
self thread MonitorFlash();
|
|
|
|
while ( true )
|
|
{
|
|
/#
|
|
if ( self ProcessDebugMode() )
|
|
continue;
|
|
#/
|
|
|
|
if ( !self.stateLocked && self readyToMeleeTarget() )
|
|
{
|
|
self mp_dome_ns_alien_explode ( self.curMeleeTarget );
|
|
return;
|
|
}
|
|
|
|
switch ( self.aiState )
|
|
{
|
|
case "idle":
|
|
self updateIdle();
|
|
break;
|
|
case "move":
|
|
self updateMove();
|
|
break;
|
|
case "melee":
|
|
self updateMelee();
|
|
break;
|
|
}
|
|
wait( 0.05 );
|
|
}
|
|
}
|
|
|
|
// self = the alien seeker
|
|
mp_dome_ns_alien_explode( explosion_target, maxDamage, blastRadius, attacker )
|
|
{
|
|
|
|
//self set_alien_emissive( 0.2, 1.0 );
|
|
self EmissiveBlend( 0.2, 1.0 );
|
|
playfx (level._effect[ "vfx_alien_minion_explode_dome" ], self.origin);
|
|
phyExpMagnitude = 2;
|
|
minDamage = 1;
|
|
if ( !isDefined ( maxDamage ))
|
|
maxDamage = 400;
|
|
if ( !isDefined ( blastRadius ))
|
|
blastRadius = 380;
|
|
|
|
// The seeker was killed by an attacker, so the attacker owns the explosions
|
|
if ( isDefined ( attacker ) )
|
|
{
|
|
attacker radiusDamage(self.origin, blastRadius, maxDamage, minDamage, attacker, "MOD_EXPLOSIVE", "killstreak_level_event_mp");
|
|
}
|
|
// The seeker blew itself up and the owner exists, so the owner owns the explosions
|
|
else if(IsDefined (self.owner))
|
|
{
|
|
self radiusDamage(self.origin, blastRadius, maxDamage, minDamage, self.owner, "MOD_EXPLOSIVE", "killstreak_level_event_mp");
|
|
}
|
|
// The seeker blew itself up and the owner does not exist, so nobody owns the explosion
|
|
else
|
|
{
|
|
self radiusDamage(self.origin, blastRadius, maxDamage, minDamage, undefined, "MOD_EXPLOSIVE", "killstreak_level_event_mp");
|
|
}
|
|
|
|
self playsound ("alien_minion_explode");
|
|
physicsExplosionSphere( self.origin, blastRadius, blastRadius/2, phyExpMagnitude );
|
|
self maps\mp\gametypes\_shellshock::barrel_earthQuake();
|
|
|
|
// If the alien was killed by someone and didn't just blow themselves up
|
|
// if ( !isDefined ( attacker ))
|
|
// {
|
|
self notify( "killanimscript" );
|
|
self SetAnimState( "explode", 0, 1 );
|
|
//}
|
|
|
|
wait GetAnimLength( self GetAnimEntry( "explode", 0 ) );
|
|
if ( IsDefined ( self ))
|
|
self suicide();
|
|
}
|
|
|
|
DidPastPursuitFail( enemy )
|
|
{
|
|
assert( IsDefined( enemy ) );
|
|
|
|
if ( IsDefined( self.curMeleeTarget ) && enemy != self.curMeleeTarget )
|
|
return false;
|
|
|
|
if ( !IsDefined( self.lastPursuitFailedPos ) || !IsDefined( self.lastPursuitFailedMyPos ) )
|
|
return false;
|
|
|
|
if ( Distance2DSquared( enemy.origin, self.lastPursuitFailedPos ) > 4 )
|
|
return false;
|
|
|
|
if ( self.bLastPursuitFailedPosBad )
|
|
return true;
|
|
|
|
if ( DistanceSquared( self.origin, self.lastPursuitFailedMyPos ) > 64 * 64 && GetTime() - self.lastPursuitFailedTime > 2000 )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
DidPastMeleeFail()
|
|
{
|
|
assert( IsDefined( self.curMeleeTarget ) );
|
|
|
|
if ( IsDefined( self.lastMeleeFailedPos ) && IsDefined( self.lastMeleeFailedMyPos )
|
|
&& Distance2DSquared( self.curMeleeTarget.origin, self.lastMeleeFailedPos ) < 4
|
|
&& DistanceSquared( self.origin, self.lastMeleeFailedMyPos ) < 50 * 50 )
|
|
return true;
|
|
|
|
if ( self WantToAttackTargetButCant( false ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
enterAIState( state )
|
|
{
|
|
self ExitAIState( self.aiState );
|
|
self.aiState = state;
|
|
|
|
switch ( state )
|
|
{
|
|
case "idle":
|
|
self.moveState = "idle";
|
|
self.bHasBadPath = false;
|
|
break;
|
|
case "move":
|
|
self.moveState = "follow";
|
|
break;
|
|
case "melee":
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
ExitAIState( state )
|
|
{
|
|
switch ( state )
|
|
{
|
|
case "move":
|
|
self.ownerPrevPos = undefined;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
updateIdle()
|
|
{
|
|
self UpdateMoveState();
|
|
}
|
|
|
|
updateMove()
|
|
{
|
|
self UpdateMoveState();
|
|
}
|
|
|
|
updateMelee()
|
|
{
|
|
self ScrAgentSetGoalPos( self.origin );
|
|
}
|
|
|
|
UpdateMoveState()
|
|
{
|
|
//IPrintLnBold ("updating move state");
|
|
if ( self.bLockGoalPos )
|
|
return;
|
|
|
|
self.prevMoveState = self.moveState;
|
|
|
|
attackPoint = undefined;
|
|
bRefreshGoal = false;
|
|
bWantedPursuitButFollowInstead = false;
|
|
|
|
cBadPathTimeOut = 500;
|
|
if ( self.bHasBadPath && GetTime() - self.lastBadPathTime < cBadPathTimeOut )
|
|
{
|
|
self.moveState = "follow";
|
|
bRefreshGoal = true;
|
|
}
|
|
else
|
|
{
|
|
self.moveState = self GetMoveState();
|
|
}
|
|
|
|
if ( self.moveState == "pursuit" )
|
|
{
|
|
attackPoint = self GetAttackPoint( self.enemy );
|
|
bLastBadMeleeTarget = false;
|
|
if ( IsDefined( self.lastBadPathTime ) && ( GetTime() - self.lastBadPathTime < 3000 ) )
|
|
{
|
|
if ( Distance2DSquared( attackPoint, self.lastBadPathGoal ) < 16 )
|
|
bLastBadMeleeTarget = true;
|
|
else if ( IsDefined( self.lastBadPathMoveState ) && self.lastBadPathMoveState == "pursuit" && Distance2DSquared( self.lastBadPathUltimateGoal, self.enemy.origin ) < 16 )
|
|
bLastBadMeleeTarget = true;
|
|
}
|
|
if ( bLastBadMeleeTarget )
|
|
{
|
|
self.moveState = "follow";
|
|
bWantedPursuitButFollowInstead = true;
|
|
}
|
|
else if ( self wantToAttackTargetButCant( true ) )
|
|
{
|
|
self.moveState = "follow";
|
|
bWantedPursuitButFollowInstead = true;
|
|
}
|
|
else if ( self DidPastPursuitFail( self.enemy ) )
|
|
{
|
|
self.moveState = "follow";
|
|
bWantedPursuitButFollowInstead = true;
|
|
}
|
|
}
|
|
|
|
self SetPastPursuitFailed( bWantedPursuitButFollowInstead );
|
|
|
|
if ( self.moveState == "follow" )
|
|
{
|
|
self.curMeleeTarget = undefined;
|
|
self.moveMode = self GetFollowMoveMode( self.moveMode );
|
|
self.bArrivalsEnabled = true;
|
|
|
|
myPos = self GetPathGoalPos();
|
|
if ( !IsDefined( myPos ) )
|
|
myPos = self.origin;
|
|
|
|
if ( GetTime() - self.timeOfLastDamage < 5000 )
|
|
bRefreshGoal = true;
|
|
|
|
distFromGoalPos = Distance2DSquared( self.origin, self.Alien_GoalPos.origin );
|
|
|
|
if ( ( distFromGoalPos < 800 ) )
|
|
{
|
|
self PickNewLocation();
|
|
}
|
|
self ScrAgentSetGoalPos( self.Alien_GoalPos.origin );
|
|
|
|
if ( bRefreshGoal == true )
|
|
{
|
|
self ScrAgentSetGoalPos( self.origin );
|
|
}
|
|
|
|
}
|
|
else if ( self.moveState == "pursuit" )
|
|
{
|
|
self.curMeleeTarget = self.enemy;
|
|
self.moveMode = "sprint";
|
|
self.bArrivalsEnabled = false;
|
|
|
|
assert( IsDefined( attackPoint ) );
|
|
self ScrAgentSetGoalPos( attackPoint );
|
|
}
|
|
}
|
|
|
|
PickNewLocation()
|
|
{
|
|
// The new goal is the one targeted by the current goal
|
|
self.Alien_GoalPos = GetStruct ( self.Alien_GoalPos.target, "targetname" );
|
|
}
|
|
|
|
GetMoveState( prevState )
|
|
{
|
|
if ( IsDefined( self.enemy ) )
|
|
{
|
|
// To ensure aliens don't gather around a dead remote uav user
|
|
if ( !maps\mp\_utility::IsReallyAlive( self.enemy ) )
|
|
return "follow";
|
|
|
|
if ( IsDefined( self.favoriteEnemy ) && self.enemy == self.favoriteEnemy )
|
|
return "pursuit";
|
|
|
|
if ( abs( self.origin[2] - self.enemy.origin[2] ) < self.warningZHeight && Distance2DSquared( self.enemy.origin, self.origin ) < self.attackRadiusSq )
|
|
return "pursuit";
|
|
|
|
if ( IsDefined( self.curMeleeTarget ) && self.curMeleeTarget == self.enemy )
|
|
{
|
|
if ( Distance2DSquared( self.curMeleeTarget.origin, self.origin ) < self.keepPursuingTargetRadiusSq )
|
|
return "pursuit";
|
|
}
|
|
}
|
|
|
|
return "follow";
|
|
}
|
|
|
|
SetPastPursuitFailed( bWantedPursuitButFollowInstead )
|
|
{
|
|
if ( bWantedPursuitButFollowInstead )
|
|
{
|
|
if ( !IsDefined( self.lastPursuitFailedPos ) )
|
|
{
|
|
self.lastPursuitFailedPos = self.enemy.origin;
|
|
self.lastPursuitFailedMyPos = self.origin;
|
|
groundPos = self DropPosToGround( self.enemy.origin );
|
|
self.bLastPursuitFailedPosBad = !IsDefined( groundPos );
|
|
self.lastPursuitFailedTime = GetTime();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self.lastPursuitFailedPos = undefined;
|
|
self.lastPursuitFailedMyPos = undefined;
|
|
self.bLastPursuitFailedPosBad = undefined;
|
|
self.lastPursuitFailedTime = undefined;
|
|
}
|
|
}
|
|
|
|
WaitForBadPath()
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
while ( true )
|
|
{
|
|
self waittill( "bad_path", badGoalPos );
|
|
self.bHasBadPath = true;
|
|
self.lastBadPathTime = GetTime();
|
|
self.lastBadPathGoal = badGoalPos;
|
|
self.lastBadPathMoveState = self.moveState;
|
|
if ( self.moveState == "follow" && IsDefined( self.owner ) )
|
|
self.lastBadPathUltimateGoal = self.owner.origin;
|
|
else if ( self.moveState == "pursuit" && IsDefined( self.enemy ) )
|
|
self.lastBadPathUltimateGoal = self.enemy.origin;
|
|
}
|
|
}
|
|
|
|
WaitForPathSet()
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
while ( true )
|
|
{
|
|
self waittill( "path_set" );
|
|
self.bHasBadPath = false;
|
|
}
|
|
}
|
|
|
|
GetFollowMoveMode( currentMoveMode )
|
|
{
|
|
cRunToFastWalkDistSq = 350 * 350;
|
|
cFastWalkToRunDistSq = 400 * 400;
|
|
|
|
pathGoalPos = self GetPathGoalPos();
|
|
if ( IsDefined( pathGoalPos ) )
|
|
{
|
|
distSq = DistanceSquared( pathGoalPos, self.origin );
|
|
if ( currentMoveMode == "run" || currentMoveMode == "sprint" )
|
|
{
|
|
if ( distSq < cRunToFastWalkDistSq )
|
|
return "fastwalk";
|
|
else if ( currentMoveMode == "sprint" )
|
|
return "run";
|
|
}
|
|
else if ( currentMoveMode == "fastwalk" )
|
|
{
|
|
if ( distSq > cFastWalkToRunDistSq )
|
|
return "run";
|
|
}
|
|
}
|
|
|
|
return currentMoveMode;
|
|
}
|
|
|
|
|
|
IsWithinAttackHeight( targetPos )
|
|
{
|
|
zDiff = targetPos[2] - self.origin[2];
|
|
|
|
return zDiff <= self.attackZHeight && zDiff >= self.attackZHeightDown;
|
|
}
|
|
|
|
WantToAttackTargetButCant( bCheckSight )
|
|
{
|
|
if ( !IsDefined( self.curMeleeTarget ) )
|
|
return false;
|
|
|
|
return !self IsWithinAttackHeight( self.curMeleeTarget.origin )
|
|
&& Distance2DSquared( self.origin, self.curMeleeTarget.origin ) < self.meleeRadiusSq * 0.75 * 0.75
|
|
&& ( !bCheckSight || self AgentCanSeeSentient( self.curMeleeTarget ) );
|
|
}
|
|
|
|
readyToMeleeTarget()
|
|
{
|
|
if ( !IsDefined( self.curMeleeTarget ) )
|
|
return false;
|
|
|
|
if ( !maps\mp\_utility::IsReallyAlive( self.curMeleeTarget ) )
|
|
return false;
|
|
|
|
if ( self.aiState == "traverse" )
|
|
return false;
|
|
|
|
if ( Distance2DSquared( self.origin, self.curMeleeTarget.origin ) > self.meleeRadiusSq )
|
|
return false;
|
|
|
|
if ( !self IsWithinAttackHeight( self.curMeleeTarget.origin ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
wantsToGrowlAtTarget()
|
|
{
|
|
if ( !IsDefined( self.enemy ) )
|
|
return false;
|
|
|
|
// first make sure the enemy is within the same height as the alien, or i can see them (which means their not on a different 'floor')
|
|
if( abs( self.origin[ 2 ] - self.enemy.origin[ 2 ] ) <= self.warningZHeight || self AgentCanSeeSentient( self.enemy ) )
|
|
{
|
|
// now see if the enemy is within the warning radius
|
|
distSq = Distance2DSquared( self.origin, self.enemy.origin );
|
|
if ( distSq < self.warningRadiusSq )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getAttackPoint( enemy )
|
|
{
|
|
meToTarget = enemy.origin - self.origin;
|
|
meToTarget = VectorNormalize( meToTarget );
|
|
|
|
pathGoalPos = self GetPathGoalPos();
|
|
closeEnough = self.attackOffset + 4;
|
|
if ( IsDefined( pathGoalPos ) && Distance2DSquared( pathGoalPos, enemy.origin ) < closeEnough * closeEnough && self CanMovePointToPoint( enemy.origin, pathGoalPos ) )
|
|
return pathGoalPos;
|
|
|
|
attackPoint = enemy.origin - meToTarget * self.attackOffset;
|
|
attackPoint = self DropPosToGround( attackPoint );
|
|
|
|
if ( !IsDefined( attackPoint ) )
|
|
return enemy.origin;
|
|
|
|
if ( !self CanMovePointToPoint( enemy.origin, attackPoint ) )
|
|
{
|
|
enemyFacing = AnglesToForward( enemy.angles );
|
|
attackPoint = enemy.origin + enemyFacing * self.attackOffset;
|
|
|
|
if ( !self CanMovePointToPoint( enemy.origin, attackPoint ) )
|
|
return enemy.origin;
|
|
}
|
|
|
|
return attackPoint;
|
|
}
|
|
|
|
// > 0 right
|
|
cross2D( a, b )
|
|
{
|
|
return a[0] * b[1] - b[0] * a[1];
|
|
}
|
|
|
|
|
|
destroyOnOwnerDisconnect( owner )
|
|
{
|
|
self endon( "death" );
|
|
owner waittill_any( "disconnect", "joined_team", "joined_spectators" );
|
|
|
|
self notify( "owner_disconnect" );
|
|
|
|
if ( maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone() )
|
|
wait 0.05;
|
|
|
|
self notify( "killanimscript" );
|
|
if ( IsDefined( self.animCBs.OnExit[ self.aiState ] ) )
|
|
self [[ self.animCBs.OnExit[ self.aiState ] ]] ();
|
|
self mp_dome_ns_alien_explode( undefined, 1, 0 );
|
|
}
|
|
|
|
watchAttackState() // self == alien
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
while ( true )
|
|
{
|
|
if ( self.aiState == "melee" )
|
|
{
|
|
if ( self.attackState != "melee" )
|
|
{
|
|
self.attackState = "melee";
|
|
self SetSoundState( undefined );
|
|
}
|
|
}
|
|
else if ( self.moveState == "pursuit" ) //( self wantsToAttackTarget() )
|
|
{
|
|
if ( self.attackState != "attacking" )
|
|
{
|
|
self.attackState = "attacking";
|
|
self SetSoundState( "bark", "attacking" );
|
|
}
|
|
}
|
|
else //if( !self wantsToAttackTarget() )
|
|
{
|
|
if ( self.attackState != "warning" )
|
|
{
|
|
if ( self wantsToGrowlAtTarget() )
|
|
{
|
|
self.attackState = "warning";
|
|
self SetSoundState( "growl", "warning" );
|
|
}
|
|
else
|
|
{
|
|
self.attackState = self.aiState;
|
|
self SetSoundState( "pant" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !self wantsToGrowlAtTarget() )
|
|
{
|
|
self.attackState = self.aiState;
|
|
self SetSoundState( "pant" );
|
|
}
|
|
}
|
|
}
|
|
|
|
wait( 0.05 );
|
|
}
|
|
}
|
|
|
|
SetSoundState( state, attackState )
|
|
{
|
|
if ( !IsDefined( state ) )
|
|
{
|
|
self notify( "end_dog_sound" );
|
|
self.soundState = undefined;
|
|
return;
|
|
}
|
|
|
|
if ( !IsDefined( self.soundState ) || self.soundState != state )
|
|
{
|
|
self notify( "end_dog_sound" );
|
|
self.soundState = state;
|
|
|
|
if ( state == "bark" )
|
|
{
|
|
self thread playBark( attackState );
|
|
}
|
|
else if ( state == "growl" )
|
|
{
|
|
self thread playGrowl( attackState );
|
|
}
|
|
else if ( state == "pant" )
|
|
{
|
|
self thread playPanting();
|
|
}
|
|
else
|
|
{
|
|
assertmsg( "unknown sound state " + state );
|
|
}
|
|
}
|
|
}
|
|
|
|
playBark( state ) // self == alien
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
self endon( "end_dog_sound" );
|
|
|
|
if( !isDefined( self.barking_sound ) )
|
|
{
|
|
self PlaySoundOnMovingEnt( "alien_minion_attack" );
|
|
self.barking_sound = true;
|
|
self thread watchBarking();
|
|
}
|
|
}
|
|
|
|
watchBarking() // self == alien
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
self endon( "end_dog_sound" );
|
|
|
|
wait( RandomIntRange( 5, 10 ) );
|
|
self.barking_sound = undefined;
|
|
}
|
|
|
|
playGrowl( state ) // self == alien
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
self endon( "end_dog_sound" );
|
|
|
|
if ( IsDefined( self.lastGrowlPlayedTime ) && GetTime() - self.lastGrowlPlayedTime < 3000 )
|
|
wait( 3 );
|
|
|
|
// while the alien is in this state randomly play growl
|
|
while ( true )
|
|
{
|
|
self.lastGrowlPlayedTime = GetTime();
|
|
self PlaySoundOnMovingEnt( "alien_minion_attack" );
|
|
|
|
wait( RandomIntRange( 3, 6 ) );
|
|
}
|
|
}
|
|
|
|
playPanting( state )
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
self endon( "end_dog_sound" );
|
|
|
|
if ( IsDefined( self.lastPantPlayedTime ) && GetTime() - self.lastPantPlayedTime < 3000 )
|
|
wait( 3 );
|
|
|
|
self.lastPantPlayedTime = GetTime();
|
|
|
|
while ( true )
|
|
{
|
|
// idle can handle panting on its own. just check periodically.
|
|
if ( self.aiState == "idle" )
|
|
{
|
|
wait ( 3 );
|
|
continue;
|
|
}
|
|
|
|
self.lastPantPlayedTime = GetTime();
|
|
if ( self.moveMode == "run" || self.moveMode == "sprint" )
|
|
self PlaySoundOnMovingEnt( "alien_minion_idle" );
|
|
else
|
|
self PlaySoundOnMovingEnt( "alien_minion_idle" );
|
|
|
|
wait( RandomIntRange( 6, 8 ) );
|
|
}
|
|
}
|
|
|
|
watchOwnerDamage() // self == alien
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
while( true )
|
|
{
|
|
if( !IsDefined( self.owner ) )
|
|
return;
|
|
|
|
self.owner waittill( "damage", damage, attacker );
|
|
|
|
if ( IsPlayer( attacker ) && attacker != self.owner )
|
|
{
|
|
// is the alien already attacking?
|
|
if ( self.attackState == "attacking" )
|
|
continue;
|
|
|
|
// is the alien close to the owner?
|
|
if ( DistanceSquared( self.owner.origin, self.origin ) > self.ownerDamagedRadiusSq )
|
|
continue;
|
|
|
|
// is the attacker within the owner damaged range?
|
|
if ( DistanceSquared( self.owner.origin, attacker.origin ) > self.ownerDamagedRadiusSq )
|
|
continue;
|
|
|
|
self.favoriteEnemy = attacker;
|
|
self.forceAttack = true;
|
|
self thread watchFavoriteEnemyDeath();
|
|
}
|
|
}
|
|
}
|
|
|
|
watchOwnerDeath() // self == alien
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
while( true )
|
|
{
|
|
if( !IsDefined( self.owner ) )
|
|
return;
|
|
|
|
self.owner waittill( "death" );
|
|
|
|
switch( level.gameType )
|
|
{
|
|
case "sd":
|
|
// the alien needs to die when the owner dies because killstreaks go away when the owner dies in sd
|
|
killDog();
|
|
break;
|
|
case "sr":
|
|
// the alien needs to die when the owner is eliminated because killstreaks go away when the owner dies in sr
|
|
result = level waittill_any_return( "sr_player_eliminated", "sr_player_respawned" );
|
|
if( IsDefined( result ) && result == "sr_player_eliminated" )
|
|
killDog();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
watchOwnerTeamChange() // self == alien
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
while( true )
|
|
{
|
|
if( !IsDefined( self.owner ) )
|
|
return;
|
|
|
|
result = self.owner waittill_any_return_no_endon_death( "joined_team", "joined_spectators" );
|
|
|
|
if( IsDefined( result ) && ( result == "joined_team" || result == "joined_spectators" ) )
|
|
killDog();
|
|
}
|
|
}
|
|
|
|
|
|
watchFavoriteEnemyDeath() // self == alien
|
|
{
|
|
self notify( "watchFavoriteEnemyDeath" );
|
|
self endon( "watchFavoriteEnemyDeath" );
|
|
|
|
self endon( "death" );
|
|
|
|
self.favoriteEnemy waittill_any_timeout( 5.0, "death", "disconnect" );
|
|
|
|
self.favoriteEnemy = undefined;
|
|
self.forceAttack = false;
|
|
}
|
|
|
|
OnDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset )
|
|
{
|
|
self.timeOfLastDamage = GetTime();
|
|
if ( IsDefined( self.owner ) )
|
|
self.damagedOwnerToMe = VectorNormalize( self.origin - self.owner.origin );
|
|
|
|
if ( self ShouldPlayHitReaction( iDamage, sWeapon, sMeansOfDeath ) )
|
|
{
|
|
switch ( self.aiState )
|
|
{
|
|
case "idle":
|
|
self thread maps\mp\mp_dome_ns_alien_idle::OnDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset );
|
|
break;
|
|
case "move":
|
|
self thread maps\mp\mp_dome_ns_alien_move::OnDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ShouldPlayHitReaction( iDamage, sWeapon, sMeansOfDeath )
|
|
{
|
|
// being specific on certain damage to do the damage reaction
|
|
if ( IsDefined( sWeapon ) && WeaponClass( sWeapon ) == "sniper" )
|
|
return true;
|
|
if ( IsDefined( sMeansOfDeath ) && IsExplosiveDamageMOD( sMeansOfDeath ) && iDamage >= 10 )
|
|
return true;
|
|
if ( IsDefined( sMeansOfDeath ) && sMeansOfDeath == "MOD_MELEE" )
|
|
return true;
|
|
if ( IsDefined( sWeapon ) && sWeapon == "concussion_grenade_mp" )
|
|
return true;
|
|
// flash grenade is monitored using MonitorFlash()
|
|
|
|
return false;
|
|
}
|
|
|
|
MonitorFlash()
|
|
{
|
|
self endon( "death" );
|
|
|
|
while ( true )
|
|
{
|
|
self waittill( "flashbang", origin, percent_distance, percent_angle, attacker, teamName, extraDuration );
|
|
|
|
if ( IsDefined( attacker ) && attacker == self.owner )
|
|
continue;
|
|
|
|
switch ( self.aiState )
|
|
{
|
|
case "idle":
|
|
self maps\mp\mp_dome_ns_alien_idle::onFlashbanged();
|
|
break;
|
|
case "move":
|
|
self maps\mp\mp_dome_ns_alien_move::onFlashbanged();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
get_closest( origin, points, maxDist )
|
|
{
|
|
Assert( points.size );
|
|
|
|
closestPoint = points[ 0 ];
|
|
dist = Distance( origin, closestPoint.origin );
|
|
|
|
for ( index = 0; index < points.size; index++ )
|
|
{
|
|
testDist = Distance( origin, points[ index ].origin );
|
|
if ( testDist >= dist )
|
|
continue;
|
|
|
|
dist = testDist;
|
|
closestPoint = points[ index ];
|
|
}
|
|
|
|
if ( !isDefined( maxDist ) || dist <= maxDist )
|
|
return closestPoint;
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/#
|
|
debug_dog() // self == alien
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
while( true )
|
|
{
|
|
if( GetDvarInt( "scr_debugdog" ) > 0 )
|
|
{
|
|
start = self.origin;
|
|
end = self.origin;
|
|
if( IsDefined( self.enemy ) )
|
|
end = self.enemy.origin;
|
|
color = [ 1, 1, 1 ];
|
|
|
|
switch( self.attackState )
|
|
{
|
|
case "idle":
|
|
color = [ 1, 1, 1 ];
|
|
break;
|
|
case "move":
|
|
color = [ 0, 1, 0 ];
|
|
break;
|
|
case "traverse":
|
|
color = [ 0.5, 0.5, 0.5 ];
|
|
break;
|
|
case "melee":
|
|
case "attacking":
|
|
color = [ 1, 0, 0 ];
|
|
break;
|
|
case "warning":
|
|
color = [ 0.8, 0.8, 0 ];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Print3d( self.origin + ( 0, 0, 10 ), self.attackState, ( color[0], color[1], color[2] ) );
|
|
Line( start, end, ( color[0], color[1], color[2] ) );
|
|
}
|
|
|
|
wait( 0.05 );
|
|
}
|
|
}
|
|
#/
|
|
|
|
/#
|
|
ProcessDebugMode()
|
|
{
|
|
if ( getdvarint( "scr_alienDebugMode" ) == 1 )
|
|
{
|
|
if ( !IsDefined( self.bDebugMode ) || !self.bDebugMode )
|
|
self thread DoDebugMode();
|
|
self.bDebugMode = true;
|
|
wait( 0.05 );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if ( IsDefined( self.bDebugMode ) && self.bDebugMode )
|
|
self EndDebugMode();
|
|
self.bDebugMode = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
DoDebugPrint3D( str, color )
|
|
{
|
|
self notify( "enddebugprint3D" );
|
|
level endon( "game_ended" );
|
|
self endon( "death" );
|
|
self endon( "enddebugprint3D" );
|
|
|
|
nTimes = 0;
|
|
while ( nTimes < 60 )
|
|
{
|
|
printPos = self.origin + (0,0,48);
|
|
print3D( printPos, str, color, 1, 1 );
|
|
nTimes++;
|
|
wait( 0.05 );
|
|
}
|
|
}
|
|
|
|
DebugDrawHitPos( pos, normal, color )
|
|
{
|
|
self notify( "enddebugdrawhitpos" );
|
|
level endon( "game_ended" );
|
|
self endon( "death" );
|
|
self endon( "enddebugdrawhitpos" );
|
|
|
|
lineEnd = pos + normal * 100;
|
|
|
|
while ( true )
|
|
{
|
|
Line( pos, lineEnd, color, 1, true, 1 );
|
|
wait( 0.05 );
|
|
}
|
|
}
|
|
|
|
DoDebugMode()
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
self endon( "enddebugmode" );
|
|
|
|
if ( IsDefined( self.owner ) && IsPlayer( self.owner ) )
|
|
player = self.owner;
|
|
else
|
|
player = level.players[0];
|
|
|
|
if ( IsAI( player ) )
|
|
return;
|
|
|
|
red = (255,0,0);
|
|
green = (0,255,0);
|
|
|
|
while ( true )
|
|
{
|
|
player NotifyOnPlayerCommand( "debug_setgoal", "+usereload" );
|
|
player waittill( "debug_setgoal" );
|
|
|
|
viewAngles = player GetPlayerAngles();
|
|
viewEye = player GetEye();
|
|
|
|
playerForward = AnglesToForward( viewAngles );
|
|
traceEndPos = viewEye + playerForward * 2048;
|
|
|
|
trace = self AIPhysicsTrace( viewEye, traceEndPos, 1, 2, true, true );
|
|
|
|
if ( trace[ "fraction" ] < 1 )
|
|
{
|
|
hitNormal = trace[ "normal" ];
|
|
hitPos = trace[ "position" ];
|
|
drawColor = red;
|
|
if ( hitNormal[2] > 0.707 )
|
|
{
|
|
goalPos = self DropPosToGround( hitPos );
|
|
if ( IsDefined( goalPos ) )
|
|
{
|
|
closestNode = GetClosestNodeInSight( goalPos );
|
|
if ( IsDefined( closestNode ) )
|
|
{
|
|
self ScrAgentSetGoalPos( goalPos );
|
|
drawColor = green;
|
|
self notify("enddebugprint3D");
|
|
hitPos = goalPos;
|
|
}
|
|
else
|
|
{
|
|
thread DoDebugPrint3D( "unable to find closest node to pos. (cannot path.)", red );
|
|
self ScrAgentSetGoalPos( self.origin );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
thread DoDebugPrint3D( "alien cannot stand there.", red );
|
|
self ScrAgentSetGoalPos( self.origin );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
thread DoDebugPrint3D( "hit pos too steep.", red );
|
|
self ScrAgentSetGoalPos( self.origin );
|
|
}
|
|
|
|
thread DebugDrawHitPos( hitPos, hitNormal, drawColor );
|
|
}
|
|
else
|
|
{
|
|
thread DoDebugPrint3D( "trace did not hit anything.", red );
|
|
self ScrAgentSetGoalPos( self.origin );
|
|
self notify( "enddebugdrawhitpos" );
|
|
}
|
|
}
|
|
}
|
|
|
|
EndDebugMode()
|
|
{
|
|
self notify("enddebugprint3D");
|
|
self notify("enddebugdrawhitpos");
|
|
self notify("enddebugmode");
|
|
}
|
|
#/
|