iw6-scripts-dev/maps/mp/killstreaks/mp_wolfpack_killstreak.gsc
2024-12-11 11:28:08 +01:00

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