437 lines
15 KiB
Ucode
437 lines
15 KiB
Ucode
|
//=============================================================================
|
||
|
// KFAIController_ZedHusk
|
||
|
//=============================================================================
|
||
|
//
|
||
|
//=============================================================================
|
||
|
// Killing Floor 2
|
||
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
||
|
//=============================================================================
|
||
|
|
||
|
class KFAIController_ZedHusk extends KFAIController_Ranged;
|
||
|
|
||
|
var bool bBaseCommandInitialized;
|
||
|
|
||
|
var float MinDistanceToSuicide;
|
||
|
var float RequiredHealthPercentForSuicide;
|
||
|
var vector LastEnemySeenPosition;
|
||
|
|
||
|
var float LastFireBallTime;
|
||
|
var float LastFlameThrowerTime;
|
||
|
|
||
|
/** The time until we shoot again based on difficulty without any randomization */
|
||
|
var float BaseTimeBetweenFireBalls;
|
||
|
/** The time to wait until our next shot, gets randomized with each shot */
|
||
|
var float TimeBetweenFireBalls;
|
||
|
|
||
|
/** The time to wait until we can use the flamethrower again*/
|
||
|
var float TimeBetweenFlameThrower;
|
||
|
|
||
|
var float FireballRandomizedValue;
|
||
|
|
||
|
/** The maximum distance an enemy can be from our pawn for us to be able to do a flamethrower attack */
|
||
|
var int MaxDistanceForFlameThrower;
|
||
|
|
||
|
/** The minimum distance an enemy must be from our pawn for us to be able to do a fireball attack */
|
||
|
var int MinDistanceForFireBall;
|
||
|
|
||
|
/** The maximum distance an enemy can be from our pawn for us to be able to do a fireball attack */
|
||
|
var int MaxDistanceForFireBall;
|
||
|
|
||
|
var bool bCanUseFlameThrower;
|
||
|
|
||
|
var float LastCheckSpecialMoveTime;
|
||
|
var float CheckSpecialMoveTime;
|
||
|
|
||
|
/** Fireball projectile attack */
|
||
|
var name FireballSocketName;
|
||
|
/** Accuracy spread of fireball aim attack */
|
||
|
var const float FireballAimError;
|
||
|
/** Accuracy spread of leading a target for fireball aim attack */
|
||
|
var const float FireballLeadTargetAimError;
|
||
|
/** Speed of the fireball. Will override projectile speed. */
|
||
|
var const float FireballSpeed;
|
||
|
|
||
|
/** Chance to aim fireballs for splash damage on Normal difficulty */
|
||
|
var const float SplashAimChanceNormal;
|
||
|
/** Chance to aim fireballs for splash damage on Hard difficulty */
|
||
|
var const float SplashAimChanceHard;
|
||
|
/** Chance to aim fireballs for splash damage on Suicide difficulty */
|
||
|
var const float SplashAimChanceSuicidal;
|
||
|
/** Chance to aim fireballs for splash damage on Hell On Earth difficulty */
|
||
|
var const float SplashAimChanceHellOnEarth;
|
||
|
|
||
|
/** The base time to wait between firing fireballs on Normal difficulty */
|
||
|
var const float FireballFireIntervalNormal;
|
||
|
/** The base time to wait between firing fireballs on Hard difficulty */
|
||
|
var const float FireballFireIntervalHard;
|
||
|
/** The base time to wait between firing fireballs on Suicidal difficulty */
|
||
|
var const float FireballFireIntervalSuicidal;
|
||
|
/** The base time to wait between firing fireballs on Hell On Earth difficulty */
|
||
|
var const float FireballFireIntervalHellOnEarth;
|
||
|
|
||
|
/** How much to scale the used FireballInterval to get the low intensity attack scale */
|
||
|
var const float LowIntensityAttackScaleOfFireballInterval;
|
||
|
|
||
|
event PostBeginPlay()
|
||
|
{
|
||
|
local KFGameInfo KFGI;
|
||
|
|
||
|
super.PostBeginPlay();
|
||
|
|
||
|
if( WorldInfo.Game != none )
|
||
|
{
|
||
|
KFGI = KFGameInfo(WorldInfo.Game);
|
||
|
// If the difficulty is hard or higher, enable the flamethrower
|
||
|
if ( KFGI != none && KFGI.GetModifiedGameDifficulty() >= KFGI.DifficultyInfo.GetDifficultyValue(2))
|
||
|
{
|
||
|
bCanUseFlameThrower = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Determine the interval of allowing fireballs to be fired
|
||
|
if( Skill == class'KFGameDifficultyInfo'.static.GetDifficultyValue(0) ) // Normal
|
||
|
{
|
||
|
BaseTimeBetweenFireBalls = FireballFireIntervalNormal;
|
||
|
}
|
||
|
else if( Skill <= class'KFGameDifficultyInfo'.static.GetDifficultyValue(1) ) // Hard
|
||
|
{
|
||
|
BaseTimeBetweenFireBalls = FireballFireIntervalHard;
|
||
|
}
|
||
|
else if( Skill <= class'KFGameDifficultyInfo'.static.GetDifficultyValue(2) ) // Suicidal
|
||
|
{
|
||
|
BaseTimeBetweenFireBalls = FireballFireIntervalSuicidal;
|
||
|
}
|
||
|
else // Hell on Earth
|
||
|
{
|
||
|
BaseTimeBetweenFireBalls = FireballFireIntervalHellOnEarth;
|
||
|
}
|
||
|
|
||
|
// Set the low intensity attack cooldown based off the current fireball interval
|
||
|
LowIntensityAttackCooldown = BaseTimeBetweenFireBalls * LowIntensityAttackScaleOfFireballInterval;
|
||
|
|
||
|
TimeBetweenFireBalls = BaseTimeBetweenFireBalls + RandRange(-FireballRandomizedValue, FireballRandomizedValue);
|
||
|
}
|
||
|
|
||
|
simulated function Tick( FLOAT DeltaTime )
|
||
|
{
|
||
|
local float DistToTargetSq;
|
||
|
|
||
|
super.Tick(DeltaTime);
|
||
|
|
||
|
if( Role == ROLE_Authority && Enemy != none && MyKFPawn != none )
|
||
|
{
|
||
|
// Do not check every tick
|
||
|
if( `TimeSince(LastCheckSpecialMoveTime) >= CheckSpecialMoveTime && !MyKFPawn.IsDoingSpecialMove() )
|
||
|
{
|
||
|
if( GetActiveCommand() != none && !GetActiveCommand().IsA('AICommand_SpecialMove') )
|
||
|
{
|
||
|
// Trace from worldinfo, open doors ignore traces from zeds
|
||
|
if( WorldInfo.FastTrace( Enemy.Location, Pawn.Location,, true ) )
|
||
|
{
|
||
|
DistToTargetSq = VSizeSq( Enemy.Location - Pawn.Location );
|
||
|
|
||
|
// If you are suicidal, do not even try to use the flamethrower or fireball
|
||
|
if( IsSuicidal() )
|
||
|
{
|
||
|
if( CanDoSuicide(DistToTargetSq) )
|
||
|
{
|
||
|
class'AICommand_Husk_Suicide'.static.Suicide(self);
|
||
|
}
|
||
|
}
|
||
|
// Check if i can use my flamethrower
|
||
|
else if( CanDoFlamethrower(DistToTargetSq) )
|
||
|
{
|
||
|
if( KFGameInfo(WorldInfo.Game) != none && KFGameInfo(WorldInfo.Game).GameConductor != none )
|
||
|
{
|
||
|
KFGameInfo(WorldInfo.Game).GameConductor.UpdateOverallAttackCoolDowns(self);
|
||
|
}
|
||
|
|
||
|
class'AICommand_HuskFlameThrowerAttack'.static.FlameThrowerAttack(self);
|
||
|
}
|
||
|
// Check if i can use my projectile
|
||
|
else if( CanDoFireball(DistToTargetSq) )
|
||
|
{
|
||
|
if( KFGameInfo(WorldInfo.Game) != none && KFGameInfo(WorldInfo.Game).GameConductor != none )
|
||
|
{
|
||
|
KFGameInfo(WorldInfo.Game).GameConductor.UpdateOverallAttackCoolDowns(self);
|
||
|
}
|
||
|
|
||
|
class'AICommand_HuskFireBallAttack'.static.FireBallAttack(self);
|
||
|
// Randomize the next fireball time
|
||
|
TimeBetweenFireBalls = BaseTimeBetweenFireBalls + RandRange(-FireballRandomizedValue, FireballRandomizedValue);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
LastCheckSpecialMoveTime = WorldInfo.TimeSeconds;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function bool IsSuicidal()
|
||
|
{
|
||
|
if( MyKFPawn == none || MyKFPawn.bIsHeadless )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return( GetHealthPercentage() <= RequiredHealthPercentForSuicide );
|
||
|
}
|
||
|
|
||
|
function bool CanDoSuicide( float DistToTargetSq )
|
||
|
{
|
||
|
if( DistToTargetSq <= (MinDistanceToSuicide * MinDistanceToSuicide) &&
|
||
|
MyKFPawn.CanDoSpecialMove(SM_Suicide) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function bool CanDoFlamethrower( float DistToTargetSq )
|
||
|
{
|
||
|
if( !CheckOverallCooldownTimer() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if( bCanUseFlameThrower &&
|
||
|
(LastFlameThrowerTime == 0 || (`TimeSince(LastFlameThrowerTime) > TimeBetweenFlameThrower)) &&
|
||
|
DistToTargetSq <= MaxDistanceForFlameThrower * MaxDistanceForFlameThrower &&
|
||
|
MyKFPawn.CanDoSpecialMove(SM_HoseWeaponAttack) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function bool CanDoFireball( float DistToTargetSq )
|
||
|
{
|
||
|
if( !CheckOverallCooldownTimer() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return ((LastFireBallTime == 0 || (`TimeSince(LastFireBallTime) > TimeBetweenFireBalls))
|
||
|
&& DistToTargetSq >= Square(MinDistanceForFireBall)
|
||
|
&& DistToTargetSq <= Square(MaxDistanceForFireBall)
|
||
|
&& MyKFPawn.CanDoSpecialMove(SM_StandAndShootAttack));
|
||
|
}
|
||
|
|
||
|
/** Overridden so the husk will not change to an enemy outside his view while doing the fireball attack */
|
||
|
event bool SetEnemy( Pawn NewEnemy )
|
||
|
{
|
||
|
if( MyKFPawn == none || MyKFPawn.IsDoingSpecialMove(SM_StandAndShootAttack) )
|
||
|
{
|
||
|
if( MyKFPawn.NeedToTurn(NewEnemy.Location) )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() rejecting "$NewEnemy$" because current enemy ("$Enemy$") we would need to turn to see it", 'SetEnemy' );
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return super.SetEnemy( NewEnemy );
|
||
|
}
|
||
|
|
||
|
/** Always sprint if we are suicidal */
|
||
|
function bool ShouldSprint()
|
||
|
{
|
||
|
if( IsSuicidal() )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
return super.ShouldSprint();
|
||
|
}
|
||
|
|
||
|
function DoStrike()
|
||
|
{
|
||
|
if( MyKFPawn.PawnAnimInfo.Attacks[PendingAnimStrikeIndex].Tag == 'Projectile' )
|
||
|
{
|
||
|
`DialogManager.PlaySpotRocketsDialog( MyKFPawn );
|
||
|
}
|
||
|
|
||
|
super.DoStrike();
|
||
|
}
|
||
|
|
||
|
function ShootFireball( class<KFProj_Husk_Fireball> FireballClass, vector FireballOffset )
|
||
|
{
|
||
|
local vector SocketLocation, DirToEnemy, HitLocation, HitNormal;
|
||
|
local KFProj_Husk_Fireball MyFireball;
|
||
|
local actor HitActor;
|
||
|
local Vector AimLocation, GroundAimLocation;
|
||
|
local float SplashAimChance;
|
||
|
local vector randVectorDraw;
|
||
|
local float randDraw;
|
||
|
local vector displacementToHitLoc;
|
||
|
local float distanceToHitLoc;
|
||
|
local KFPawn_ZedHusk MyHuskPawn;
|
||
|
|
||
|
if( MyKFPawn == none )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( bDebugAimError )
|
||
|
{
|
||
|
// Render debug lines and simulate several shots at once to evaluate aimerror
|
||
|
DebugAimError(FireballSocketName, FireballAimError);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SocketLocation = MyKFPawn.GetPawnViewLocation() + (FireballOffset >> Pawn.GetViewRotation());
|
||
|
if( MyKFPawn.Health > 0.f && Role == ROLE_Authority && MyKFPawn.IsDoingSpecialMove(SM_StandAndShootAttack) )
|
||
|
{
|
||
|
AimLocation = Enemy.Location;
|
||
|
|
||
|
// Determine the random chance of aiming at the ground for splash damage
|
||
|
if( Skill == class'KFGameDifficultyInfo'.static.GetDifficultyValue(0) ) // Normal
|
||
|
{
|
||
|
SplashAimChance = SplashAimChanceNormal;
|
||
|
}
|
||
|
else if( Skill <= class'KFGameDifficultyInfo'.static.GetDifficultyValue(1) ) // Hard
|
||
|
{
|
||
|
SplashAimChance = SplashAimChanceHard;
|
||
|
}
|
||
|
else if( Skill <= class'KFGameDifficultyInfo'.static.GetDifficultyValue(2) ) // Suicidal
|
||
|
{
|
||
|
SplashAimChance = SplashAimChanceSuicidal;
|
||
|
}
|
||
|
else // Hell on Earth
|
||
|
{
|
||
|
SplashAimChance = SplashAimChanceHellOnEarth;
|
||
|
}
|
||
|
|
||
|
randDraw = FRand();
|
||
|
|
||
|
`AILog_Ext( GetFuncName() @ " SplashAimChance: " @ SplashAimChance @ " randDraw: " @ randDraw, 'FireBall', self );
|
||
|
|
||
|
if( randDraw < SplashAimChance )
|
||
|
{
|
||
|
// Simple pass at making the Husk try and do splash damage when it shoots at a player rather than just shoot directly at them (and most likely miss)
|
||
|
GroundAimLocation = Enemy.Location - (vect(0,0,1) * Enemy.GetCollisionHeight());
|
||
|
|
||
|
if( GroundAimLocation.Z < SocketLocation.Z )
|
||
|
{
|
||
|
AimLocation = GroundAimLocation;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//DrawDebugLine( SocketLocation, AimLocation, 0, 0, 255, true );
|
||
|
|
||
|
HitActor = WorldInfo.Trace( HitLocation, HitNormal, AimLocation, SocketLocation, true );
|
||
|
|
||
|
// Don't shoot if it's too close
|
||
|
if( HitActor == Enemy )
|
||
|
{
|
||
|
//DrawDebugStar( HitLocation, 50, 0, 255, 0, true );
|
||
|
|
||
|
randVectorDraw = VRand();
|
||
|
|
||
|
if( !bCanLeadTarget )
|
||
|
{
|
||
|
DirToEnemy = normal( AimLocation - SocketLocation ) + randVectorDraw * FireballAimError;
|
||
|
|
||
|
`AILog_Ext( GetFuncName() @ " HitActor: " @ HitActor @ " Is My Enemy: " @ Enemy @ " randVectorDraw: " @ randVectorDraw @ " - not leading with an error of: " @ randVectorDraw * FireballAimError, 'FireBall', self );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DirToEnemy = normal( CalcAimLocToHit(AimLocation, SocketLocation, FireballSpeed, FireballSpeed, Enemy.Velocity) - SocketLocation ) + randVectorDraw * FireballLeadTargetAimError;
|
||
|
|
||
|
`AILog_Ext( GetFuncName() @ " HitActor: " @ HitActor @ " Is My Enemy: " @ Enemy @ " randVectorDraw: " @ randVectorDraw @ " - leading with an error of: " @ randVectorDraw * FireballLeadTargetAimError, 'FireBall', self );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// If our path to the enemy is blocked, try his last known position
|
||
|
AimLocation = AICommand_HuskFireBallAttack(GetActiveCommand()).LastKnownEnemyLocation;
|
||
|
HitActor = Trace( HitLocation, HitNormal, AimLocation, SocketLocation, true );
|
||
|
|
||
|
displacementToHitLoc = HitLocation - SocketLocation;
|
||
|
distanceToHitLoc = VSize( displacementToHitLoc );
|
||
|
|
||
|
// If there is an object in front of us, cancel the attack
|
||
|
if( distanceToHitLoc < 300 )
|
||
|
{
|
||
|
//DrawDebugStar( HitLocation, 50, 255, 0, 0, true );
|
||
|
|
||
|
`AILog_Ext( GetFuncName() @ " HitActor: " @ HitActor @ " Is NOT My Enemy: " @ Enemy @ " and distanceToHitLoc: " @ distanceToHitLoc @ " is too close so not firing!!!", 'FireBall', self );
|
||
|
MyKFPawn.SpecialMoves[ SM_StandAndShootAttack ].AbortedByAICommand();
|
||
|
LastFireBallTime = WorldInfo.TimeSeconds;
|
||
|
return;
|
||
|
}
|
||
|
else // Otherwise fire at the enemies last known position
|
||
|
{
|
||
|
//DrawDebugStar( HitLocation, 50, 0, 0, 255, true );
|
||
|
|
||
|
randVectorDraw = VRand();
|
||
|
DirToEnemy = normal( (AimLocation - SocketLocation) + randVectorDraw * FireballAimError );
|
||
|
|
||
|
`AILog_Ext( GetFuncName() @" Fire at enemy last known position: " @ Enemy @ AimLocation @ " randVectorDraw: " @ randVectorDraw @ " - leading with an error of: " @ randVectorDraw * FireballLeadTargetAimError, 'FireBall', self );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//DrawDebugLine( SocketLocation, SocketLocation + DirToEnemy * 5000.0, 255, 0, 0, true );
|
||
|
MyHuskPawn = KFPawn_ZedHusk( MyKFPawn );
|
||
|
|
||
|
// Shoot the fireball
|
||
|
MyFireball = Spawn( FireballClass, MyKFPawn,, SocketLocation, Rotator(DirToEnemy) );
|
||
|
MyFireball.Instigator = MyKFPawn;
|
||
|
MyFireball.InstigatorController = self;
|
||
|
MyFireball.Speed = FireballSpeed;
|
||
|
MyFireball.MaxSpeed = FireballSpeed;
|
||
|
|
||
|
// Set our difficulty setings
|
||
|
MyFireball.ExplosionTemplate.MomentumTransferScale = MyHuskPawn.FireballSettings.ExplosionMomentum;
|
||
|
MyFireball.bSpawnGroundFire = MyHuskPawn.FireballSettings.bSpawnGroundFire;
|
||
|
|
||
|
// Fire
|
||
|
MyFireball.Init( DirToEnemy );
|
||
|
|
||
|
LastFireBallTime = WorldInfo.TimeSeconds;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DefaultProperties
|
||
|
{
|
||
|
BaseShapeOfProjectileForCalc=(X=10,Y=10,Z=10)
|
||
|
FireballSocketName=FireballSocket
|
||
|
bUseDesiredRotationForMelee=false
|
||
|
bCanTeleportCloser=false
|
||
|
|
||
|
// ---------------------------------------------
|
||
|
// Behaviors
|
||
|
|
||
|
EvadeGrenadeChance=0.9f
|
||
|
|
||
|
CheckSpecialMoveTime=0.25f
|
||
|
|
||
|
// Fireball
|
||
|
MinDistanceForFireBall=300
|
||
|
MaxDistanceForFireBall=4000
|
||
|
|
||
|
FireballFireIntervalNormal=5.0
|
||
|
FireballFireIntervalHard=4.5
|
||
|
FireballFireIntervalSuicidal=4
|
||
|
FireballFireIntervalHellOnEarth=3.5
|
||
|
|
||
|
FireballRandomizedValue=1
|
||
|
|
||
|
SplashAimChanceNormal=0.25
|
||
|
SplashAimChanceHard=0.35
|
||
|
SplashAimChanceSuicidal=0.5
|
||
|
SplashAimChanceHellOnEarth=0.75
|
||
|
|
||
|
FireballSpeed=3600.f
|
||
|
FireballAimError=0.03f
|
||
|
FireballLeadTargetAimError=0.03f
|
||
|
bDebugAimError=false
|
||
|
bCanLeadTarget=false
|
||
|
|
||
|
// FlameThrower
|
||
|
TimeBetweenFlameThrower=5
|
||
|
MaxDistanceForFlameThrower=500
|
||
|
LowIntensityAttackScaleOfFireballInterval=1.25
|
||
|
|
||
|
// Suicide
|
||
|
MinDistanceToSuicide=280
|
||
|
RequiredHealthPercentForSuicide=0.15f
|
||
|
}
|