623 lines
15 KiB
Plaintext
623 lines
15 KiB
Plaintext
#include maps\mp\_utility;
|
|
#include common_scripts\utility;
|
|
#include maps\mp\agents\_scriptedAgents;
|
|
#include maps\mp\agents\_agent_utility;
|
|
#include maps\mp\gametypes\_damage;
|
|
|
|
//===========================================
|
|
// constants
|
|
//===========================================
|
|
CONST_KILLSTREAK_NAME = "mine_level_killstreak";
|
|
CONST_WOLF_HEALTH = 200; // default dogs are 250
|
|
|
|
//===========================================
|
|
// init
|
|
//===========================================
|
|
init()
|
|
{
|
|
level.killStreakFuncs[CONST_KILLSTREAK_NAME] = ::tryUseWolfpack;
|
|
|
|
level.killstreakWeildWeapons[ "killstreak_wolfpack_mp" ] = CONST_KILLSTREAK_NAME;
|
|
}
|
|
|
|
|
|
//===========================================
|
|
// setup_callbacks
|
|
//===========================================
|
|
setup_callbacks()
|
|
{
|
|
level.agent_funcs["wolf"] = level.agent_funcs["dog"];
|
|
|
|
level.agent_funcs["wolf"]["spawn"] = ::spawn_dog;
|
|
level.agent_funcs["wolf"]["on_killed"] = ::on_agent_dog_killed;
|
|
level.agent_funcs["wolf"]["on_damaged"] = maps\mp\agents\_agents::on_agent_generic_damaged;
|
|
level.agent_funcs["wolf"]["on_damaged_finished"] = maps\mp\killstreaks\_dog_killstreak::on_damaged_finished;
|
|
// !!! CHANGE THIS
|
|
level.agent_funcs["wolf"]["think"] = ::think_init;
|
|
|
|
}
|
|
|
|
|
|
//===========================================
|
|
// tryUseDog
|
|
//===========================================
|
|
tryUseWolfpack( lifeId, streakName )
|
|
{
|
|
setup_callbacks();
|
|
|
|
return useDog();
|
|
}
|
|
|
|
|
|
//===========================================
|
|
// useDog
|
|
//===========================================
|
|
CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER = 2;
|
|
useDog()
|
|
{
|
|
/*
|
|
// limit the number of active "dog" agents allowed per player
|
|
if( isDefined(self.hasDog) && self.hasDog )
|
|
{
|
|
dog_type = self GetCommonPlayerDataReservedInt( "mp_dog_type" );
|
|
if( dog_type == 1 )
|
|
self iPrintLnBold( &"KILLSTREAKS_ALREADY_HAVE_WOLF" );
|
|
else
|
|
self iPrintLnBold( &"KILLSTREAKS_ALREADY_HAVE_DOG" );
|
|
return false;
|
|
}
|
|
|
|
// limit the number of active "dog" agents allowed per game
|
|
if( getNumActiveAgents( "dog" ) >= CONST_MAX_ACTIVE_KILLSTREAK_DOGS_PER_GAME )
|
|
{
|
|
self iPrintLnBold( &"KILLSTREAKS_TOO_MANY_DOGS" );
|
|
return false;
|
|
}
|
|
*/
|
|
|
|
// limit the number of active agents allowed per player
|
|
if( getNumOwnedActiveAgents( self ) >= CONST_MAX_ACTIVE_KILLSTREAK_AGENTS_PER_PLAYER )
|
|
{
|
|
self iPrintLnBold( &"KILLSTREAKS_AGENT_MAX" );
|
|
return false;
|
|
}
|
|
|
|
// TODO: we should probably do a queue system for these, so the player can call it but it'll go into a queue for when an agent dies to open up a spot
|
|
// limit the number of active agents allowed per player
|
|
maxagents = GetMaxAgents();
|
|
if( getNumActiveAgents() >= maxagents )
|
|
{
|
|
self iPrintLnBold( &"KILLSTREAKS_UNAVAILABLE" );
|
|
return false;
|
|
}
|
|
|
|
// make sure the player is still alive before the agent trys to spawn on the player
|
|
if( !isReallyAlive( self ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
result = self spawnWolf( 1 );
|
|
if ( result )
|
|
{
|
|
self PlaySound( "mp_mine_wolf_spawn" );
|
|
|
|
self thread spawnWolfPack();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
spawnWolf( id )
|
|
{
|
|
// try to spawn the agent on a path node near the player
|
|
// nearestPathNode = self getValidSpawnPathNodeNearPlayer( true );
|
|
// if( !IsDefined(nearestPathNode) )
|
|
// {
|
|
// return false;
|
|
// }
|
|
|
|
// find an available agent
|
|
agent = maps\mp\agents\_agent_common::connectNewAgent( "wolf" , self.team );
|
|
if( !IsDefined( agent ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// set the agent to the player's team
|
|
agent set_agent_team( self.team, self );
|
|
|
|
// !!! CHANGE THIS
|
|
structName = "wolf_spawn_0" + id;
|
|
wolfStruct = getstruct( structName, "targetname" );
|
|
|
|
spawnOrigin = wolfStruct.origin;
|
|
spawnAngles = self.angles;
|
|
agent.wolfId = id;
|
|
|
|
// pick a path
|
|
agent.pathNodeArray = getstructarray( "wolf_path_0" + id, "script_noteworthy" );
|
|
|
|
agent thread [[ agent agentFunc("spawn") ]]( spawnOrigin, spawnAngles, self );
|
|
|
|
agent _setNameplateMaterial( "player_name_bg_green_dog", "player_name_bg_red_dog" );
|
|
|
|
return true;
|
|
}
|
|
|
|
CONST_NUM_WOLVES = 3;
|
|
CONST_NUM_TOTAL_WOLVES = 6;
|
|
spawnWolfPack()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "disconnect" );
|
|
level endon( "game_ended" );
|
|
|
|
// spawn the initial set of solves
|
|
for ( i = 2; i <= CONST_NUM_WOLVES; i++ )
|
|
{
|
|
wait( 0.75 );
|
|
|
|
spawnWolf( i );
|
|
}
|
|
|
|
numWolvesLeft = CONST_NUM_TOTAL_WOLVES - CONST_NUM_WOLVES;
|
|
|
|
while ( true )
|
|
{
|
|
level waittill( "wolf_killed", id );
|
|
|
|
if ( numWolvesLeft > 0 )
|
|
{
|
|
wait ( 0.75 );
|
|
spawnWolf( id );
|
|
|
|
numWolvesLeft--;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//=======================================================
|
|
// on_agent_dog_killed
|
|
//=======================================================
|
|
on_agent_dog_killed( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration )
|
|
{
|
|
self.isActive = false;
|
|
self.hasDied = false;
|
|
|
|
//agent dogs in safeguard do not have an owner.
|
|
if( IsDefined( self.owner ) )
|
|
self.owner.hasDog = false;
|
|
|
|
eAttacker.lastKillDogTime = GetTime();
|
|
|
|
if ( IsDefined( self.animCBs.OnExit[ self.aiState ] ) )
|
|
self [[ self.animCBs.OnExit[ self.aiState ] ]] ();
|
|
|
|
// award XP for killing agents
|
|
if( isPlayer( eAttacker ) && IsDefined(self.owner) && (eAttacker != self.owner) )
|
|
{
|
|
self maps\mp\gametypes\_damage::onKillstreakKilled( eAttacker, sWeapon, sMeansOfDeath, iDamage, "destroyed_ks_wolf" );
|
|
}
|
|
|
|
|
|
self SetAnimState( "death" );
|
|
animEntry = self GetAnimEntry();
|
|
animLength = GetAnimLength( animEntry );
|
|
|
|
deathAnimDuration = int( animLength * 1000 ); // duration in milliseconds
|
|
|
|
self.body = self CloneAgent( deathAnimDuration );
|
|
|
|
self PlaySound( "anml_wolf_shot_death" );
|
|
|
|
level notify( "wolf_killed", self.wolfId );
|
|
|
|
self maps\mp\agents\_agent_utility::deactivateAgent();
|
|
|
|
self notify( "killanimscript" );
|
|
|
|
// notify death so that we can spawn a new agent
|
|
}
|
|
|
|
spawn_dog( optional_spawnOrigin, optional_spawnAngles, optional_owner ) // self == agent
|
|
{
|
|
dog_type = 1; // 0 == dog, 1 == wolf
|
|
|
|
dog_model = "mp_fullbody_wolf_c";
|
|
wolf_type = 1;
|
|
if ( self.wolfId == 1 )
|
|
{
|
|
dog_model = "mp_fullbody_wolf_b";
|
|
wolf_type = 0;
|
|
}
|
|
|
|
if( IsHairRunning() )
|
|
dog_model = dog_model + "_fur";
|
|
|
|
self SetModel( dog_model );
|
|
|
|
self.species = "dog";
|
|
|
|
self.OnEnterAnimState = maps\mp\agents\dog\_dog_think::OnEnterAnimState;
|
|
|
|
// allow killstreaks to pass in specific spawn locations
|
|
if( IsDefined(optional_spawnOrigin) && IsDefined(optional_spawnAngles) )
|
|
{
|
|
spawnOrigin = optional_spawnOrigin;
|
|
spawnAngles = optional_spawnAngles;
|
|
}
|
|
else
|
|
{
|
|
spawnPoint = self [[level.getSpawnPoint]]();
|
|
spawnOrigin = spawnpoint.origin;
|
|
spawnAngles = spawnpoint.angles;
|
|
}
|
|
self activateAgent();
|
|
self.spawnTime = GetTime();
|
|
self.lastSpawnTime = GetTime();
|
|
|
|
self.bIsWolf = true;
|
|
animclass = "wolf_animclass";
|
|
|
|
// !!! CHANGE THIS
|
|
self maps\mp\agents\dog\_dog_think::init();
|
|
self.watchAttackStateFunc = ::watchAttackState; // overwrite the dog's version of this to play custom sounds
|
|
|
|
// called from code when an agent is done initializing after AddAgent is called
|
|
// this should set up any state specific to this agent and game
|
|
self SpawnAgent( spawnOrigin, spawnAngles, animclass, 15, 40, optional_owner );
|
|
level notify( "spawned_agent", self );
|
|
|
|
self maps\mp\agents\_agent_common::set_agent_health( CONST_WOLF_HEALTH );
|
|
|
|
// must set the team after SpawnAgent to fix a bug with weapon crosshairs and nametags
|
|
if( IsDefined(optional_owner) )
|
|
{
|
|
self set_agent_team( optional_owner.team, optional_owner );
|
|
}
|
|
|
|
self SetThreatBiasGroup( "Dogs" );
|
|
|
|
self TakeAllWeapons();
|
|
|
|
// hide the dog, let the whistle happen, then show the dog
|
|
if( IsDefined(self.owner) )
|
|
{
|
|
self Hide();
|
|
wait( 1.0 ); // not sure what to endon for this
|
|
|
|
// The dog could have died during the 1 second wait (for example if he spawned in a kill trigger), so if that happened,
|
|
// don't start thinking since it will cause SREs due to him missing a self.agent_type
|
|
if ( !IsAlive(self) )
|
|
return;
|
|
|
|
self Show();
|
|
}
|
|
|
|
self thread [[ self agentFunc("think") ]]();
|
|
|
|
wait( 0.1 );
|
|
|
|
if ( IsHairRunning() )
|
|
{
|
|
furFX = level.wolfFurFX[ wolf_type ];
|
|
|
|
assert( IsDefined( furFX ) );
|
|
PlayFXOnTag( furFX, self, "tag_origin" );
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------
|
|
// Adapted from dog_think and mp_dome_ns_alien_think
|
|
think_init()
|
|
{
|
|
self maps\mp\agents\dog\_dog_think::setupDogState();
|
|
self.wolfGoalPos = get_closest( self.origin , self.pathNodeArray );
|
|
|
|
self thread think();
|
|
self thread maps\mp\agents\dog\_dog_think::watchOwnerDeath();
|
|
self thread maps\mp\agents\dog\_dog_think::watchOwnerTeamChange();
|
|
self thread maps\mp\agents\dog\_dog_think::WaitForBadPath();
|
|
self thread maps\mp\agents\dog\_dog_think::WaitForPathSet();
|
|
|
|
/#
|
|
self thread maps\mp\agents\dog\_dog_think::debug_dog();
|
|
#/
|
|
}
|
|
|
|
think()
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
if ( IsDefined( self.owner ) )
|
|
{
|
|
self endon( "owner_disconnect" );
|
|
self thread maps\mp\agents\dog\_dog_think::destroyOnOwnerDisconnect( self.owner );
|
|
}
|
|
|
|
self thread [[self.watchAttackStateFunc]]();
|
|
self thread maps\mp\agents\dog\_dog_think::MonitorFlash();
|
|
|
|
while ( true )
|
|
{
|
|
/#
|
|
if ( self maps\mp\agents\dog\_dog_think::ProcessDebugMode() )
|
|
continue;
|
|
#/
|
|
|
|
if ( self.aiState != "melee" && !self.stateLocked && self maps\mp\agents\dog\_dog_think::readyToMeleeTarget() && !self maps\mp\agents\dog\_dog_think::DidPastMeleeFail() )
|
|
self ScrAgentBeginMelee( self.curMeleeTarget );
|
|
|
|
switch ( self.aiState )
|
|
{
|
|
case "idle":
|
|
self updateMove();
|
|
break;
|
|
case "move":
|
|
self updateMove();
|
|
break;
|
|
case "melee":
|
|
self maps\mp\agents\dog\_dog_think::updateMelee();
|
|
break;
|
|
}
|
|
wait( 0.05 );
|
|
}
|
|
}
|
|
|
|
updateMove()
|
|
{
|
|
self UpdateMoveState();
|
|
}
|
|
|
|
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 maps\mp\agents\dog\_dog_think::GetMoveState();
|
|
}
|
|
|
|
if ( self.moveState == "pursuit" )
|
|
{
|
|
attackPoint = self maps\mp\agents\dog\_dog_think::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 ( !isReallyAlive( self.enemy )
|
|
|| bLastBadMeleeTarget
|
|
|| self maps\mp\agents\dog\_dog_think::wantToAttackTargetButCant( true )
|
|
|| self maps\mp\agents\dog\_dog_think::DidPastPursuitFail( self.enemy )
|
|
)
|
|
{
|
|
self.moveState = "follow";
|
|
bWantedPursuitButFollowInstead = true;
|
|
}
|
|
}
|
|
|
|
self maps\mp\agents\dog\_dog_think::SetPastPursuitFailed( bWantedPursuitButFollowInstead );
|
|
|
|
if ( self.moveState == "follow" )
|
|
{
|
|
self.curMeleeTarget = undefined;
|
|
self.moveMode = self maps\mp\agents\dog\_dog_think::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.wolfGoalPos.origin );
|
|
|
|
if ( ( distFromGoalPos < 800 ) )
|
|
{
|
|
self pickNewLocation();
|
|
}
|
|
self ScrAgentSetGoalPos( self.wolfGoalPos.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.wolfGoalPos = GetStruct ( self.wolfGoalPos.target, "targetname" );
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// !!! UGH, this is awful
|
|
// dupliated directly from dog_think because I need to replace the sounds played by the wolves
|
|
// wish there was a better way
|
|
watchAttackState() // self == dog
|
|
{
|
|
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 maps\mp\agents\dog\_dog_think::wantsToGrowlAtTarget() )
|
|
{
|
|
self.attackState = "warning";
|
|
self SetSoundState( "growl", "warning" );
|
|
}
|
|
else
|
|
{
|
|
self.attackState = self.aiState;
|
|
self SetSoundState( "pant" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !self maps\mp\agents\dog\_dog_think::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 maps\mp\agents\dog\_dog_think::playPanting();
|
|
}
|
|
else
|
|
{
|
|
assertmsg( "unknown sound state " + state );
|
|
}
|
|
}
|
|
}
|
|
|
|
playBark( state ) // self == dog
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
self endon( "end_dog_sound" );
|
|
|
|
if( !isDefined( self.barking_sound ) )
|
|
{
|
|
self PlaySoundOnMovingEnt( "mine_wolf_bark" );
|
|
self.barking_sound = true;
|
|
self watchBarking();
|
|
}
|
|
}
|
|
|
|
watchBarking() // self == dog
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
self endon( "end_dog_sound" );
|
|
|
|
wait( RandomIntRange( 4, 6 ) ); // allow wolves to bark more frequently than dogs
|
|
self.barking_sound = undefined;
|
|
}
|
|
|
|
playGrowl( state ) // self == dog
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
self endon( "end_dog_sound" );
|
|
|
|
if ( IsDefined( self.lastGrowlPlayedTime ) && GetTime() - self.lastGrowlPlayedTime < 3000 )
|
|
wait( 3 );
|
|
|
|
// while the dog is in this state randomly play growl
|
|
while ( true )
|
|
{
|
|
self.lastGrowlPlayedTime = GetTime();
|
|
self PlaySoundOnMovingEnt( "mine_wolf_growl" );
|
|
|
|
wait( RandomIntRange( 3, 6 ) );
|
|
}
|
|
}
|