rott/kf_sources/ScrnZedPack/Classes/HardPat.uc
2026-07-14 20:27:09 +07:00

613 lines
16 KiB
Ucode

Class HardPat extends ZombieBoss;
var transient float GiveUpTime;
var byte MissilesLeft;
var bool bEndGameBoss,bMovingChaingunAttack;
var(Sounds) sound SaveMeSound;
replication
{
reliable if( ROLE==ROLE_AUTHORITY )
bMovingChaingunAttack;
}
simulated function PostBeginPlay()
{
super.PostBeginPlay();
SyringeCount = 3; // no healing for mid-game bosses
Health *= 0.75; // less hp for mid-game bosses
}
function bool MakeGrandEntry()
{
Health = HealthMax; // restore original hp
bEndGameBoss = true;
SyringeCount = 0; // restore healing for end-game boss
return Super.MakeGrandEntry();
}
function Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
{
Super(KFMonster).Died(Killer,damageType,HitLocation);
if ( bEndGameBoss )
KFGameType(Level.Game).DoBossDeath();
}
simulated function bool HitCanInterruptAction()
{
return (!bWaitForAnim && !bShotAnim);
}
function RangedAttack(Actor A)
{
local float D;
local bool bOnlyE;
local bool bDesireChainGun;
// Randomly make him want to chaingun more
if( Controller.LineOfSightTo(A) && FRand() < 0.15 && LastChainGunTime<Level.TimeSeconds )
{
bDesireChainGun = true;
}
if ( bShotAnim )
{
if( !IsAnimating(ExpectingChannel) )
bShotAnim = false;
return;
}
D = VSize(A.Location-Location);
bOnlyE = (Pawn(A)!=None && OnlyEnemyAround(Pawn(A)));
if ( IsCloseEnuf(A) )
{
bShotAnim = true;
if( Health>1500 && Pawn(A)!=None && FRand() < 0.5 )
{
SetAnimAction('MeleeImpale');
}
else
{
SetAnimAction('MeleeClaw');
//PlaySound(sound'Claw2s', SLOT_None); KFTODO: Replace this
}
}
else if( Level.TimeSeconds>LastSneakedTime )
{
if( FRand() < 0.3 )
{
// Wait another 20-40 to try this again
LastSneakedTime = Level.TimeSeconds+20.f+FRand()*20;
Return;
}
SetAnimAction('transition');
GoToState('SneakAround');
}
else if( bChargingPlayer && (bOnlyE || D<200) )
Return;
else if( !bDesireChainGun && !bChargingPlayer && (D<300 || (D<700 && bOnlyE)) &&
(Level.TimeSeconds - LastChargeTime > (5.0 + 5.0 * FRand())) ) // Don't charge again for a few seconds
{
SetAnimAction('transition');
GoToState('Charging');
}
else if( LastMissileTime<Level.TimeSeconds && (D>500 || SyringeCount>=2) )
{
if( !Controller.LineOfSightTo(A) || FRand() > 0.75 )
{
LastMissileTime = Level.TimeSeconds+FRand() * 5;
Return;
}
LastMissileTime = Level.TimeSeconds + 10 + FRand() * 15;
bShotAnim = true;
Acceleration = vect(0,0,0);
SetAnimAction('PreFireMissile');
HandleWaitForAnim('PreFireMissile');
GoToState('FireMissile');
}
else if ( !bWaitForAnim && !bShotAnim && LastChainGunTime<Level.TimeSeconds )
{
if ( !Controller.LineOfSightTo(A) || FRand()> 0.85 )
{
LastChainGunTime = Level.TimeSeconds+FRand()*4;
Return;
}
LastChainGunTime = Level.TimeSeconds + 5 + FRand() * 10;
bShotAnim = true;
Acceleration = vect(0,0,0);
SetAnimAction('PreFireMG');
HandleWaitForAnim('PreFireMG');
if ( bEndGameBoss )
MGFireCounter = Rand(60) + 35*(SyringeCount+1);
else
MGFireCounter = Rand(60) + 60;
GoToState('FireChaingun');
}
}
simulated function bool AnimNeedsWait(name TestAnim)
{
if( TestAnim == 'FireMG' )
return !bMovingChaingunAttack;
return Super.AnimNeedsWait(TestAnim);
}
simulated final function rotator GetBoneTransRot()
{
local rotator R;
R.Yaw = 24576+Rotation.Yaw;
R.Pitch = 16384+Rotation.Pitch;
return R;
}
simulated function int DoAnimAction( name AnimName )
{
if( AnimName=='FireMG' && bMovingChaingunAttack )
{
AnimBlendParams(1, 1.0, 0.0,, FireRootBone, True);
//SetBoneDirection(FireRootBone,GetBoneTransRot(),,1,1);
PlayAnim('FireMG',, 0.f, 1);
return 1;
}
else if( AnimName=='FireEndMG' )
{
//SetBoneDirection(FireRootBone,rot(0,0,0),,0,0);
AnimBlendParams(1, 0);
}
return Super.DoAnimAction( AnimName );
}
simulated function AnimEnd(int Channel)
{
local name Sequence;
local float Frame, Rate;
if( Level.NetMode==NM_Client && bMinigunning )
{
GetAnimParams( Channel, Sequence, Frame, Rate );
if( Sequence != 'PreFireMG' && Sequence != 'FireMG' )
{
//SetBoneDirection(FireRootBone,rot(0,0,0),,0,0);
Super(KFMonster).AnimEnd(Channel);
return;
}
if( bMovingChaingunAttack )
DoAnimAction('FireMG');
else
{
PlayAnim('FireMG');
bWaitForAnim = true;
bShotAnim = true;
IdleTime = Level.TimeSeconds;
}
}
else
{
//SetBoneDirection(FireRootBone,rot(0,0,0),,0,0);
Super(KFMonster).AnimEnd(Channel);
}
}
// Fix: Don't spawn needle before last stage.
simulated function NotifySyringeA()
{
if( Level.NetMode!=NM_Client )
{
if( SyringeCount<3 )
SyringeCount++;
if( Level.NetMode!=NM_DedicatedServer )
PostNetReceive();
}
if( Level.NetMode!=NM_DedicatedServer )
DropNeedle();
}
simulated function NotifySyringeC()
{
if( Level.NetMode!=NM_DedicatedServer )
{
CurrentNeedle = Spawn(Class'BossHPNeedle');
CurrentNeedle.Velocity = vect(-45,300,-90) >> Rotation;
DropNeedle();
}
}
simulated function ZombieCrispUp() // Don't become crispy.
{
bAshen = true;
bCrispified = true;
SetBurningBehavior();
}
state KnockDown
{
Ignores RangedAttack;
Begin:
if ( Health > 0 )
{
Sleep(GetAnimDuration('KnockDown'));
CloakBoss();
PlaySound(SaveMeSound, SLOT_Misc, 2.0,,500.0);
if ( KFGameType(Level.Game).FinalSquadNum == SyringeCount )
{
KFGameType(Level.Game).AddBossBuddySquad();
}
GotoState('Escaping');
}
else
{
GotoState('');
}
}
state FireChaingun
{
function BeginState()
{
Super.BeginState();
bMovingChaingunAttack = (SyringeCount>=2);
bChargingPlayer = bEndGameBoss && SyringeCount>=3 && FRand()<0.4f;
bCanStrafe = true;
}
function EndState()
{
bChargingPlayer = false;
Super.EndState();
bMovingChaingunAttack = false;
bCanStrafe = false;
}
function Tick( float Delta )
{
Super(KFMonster).Tick(Delta);
if( bChargingPlayer )
GroundSpeed = OriginalGroundSpeed * 2.3;
else GroundSpeed = OriginalGroundSpeed * 1.15;
}
function AnimEnd( int Channel )
{
if( MGFireCounter <= 0 )
{
bShotAnim = true;
Acceleration = vect(0,0,0);
SetAnimAction('FireEndMG');
HandleWaitForAnim('FireEndMG');
GoToState('');
}
else if( bMovingChaingunAttack )
{
if( bFireAtWill && Channel!=1 )
return;
if( Controller.Target!=None )
Controller.Focus = Controller.Target;
bShotAnim = false;
bFireAtWill = True;
SetAnimAction('FireMG');
}
else
{
if ( Controller.Enemy != none )
{
if ( Controller.LineOfSightTo(Controller.Enemy) && FastTrace(GetBoneCoords('tip').Origin,Controller.Enemy.Location))
{
MGLostSightTimeout = 0.0;
Controller.Focus = Controller.Enemy;
Controller.FocalPoint = Controller.Enemy.Location;
}
else
{
MGLostSightTimeout = Level.TimeSeconds + (0.25 + FRand() * 0.35);
Controller.Focus = None;
}
Controller.Target = Controller.Enemy;
}
else
{
MGLostSightTimeout = Level.TimeSeconds + (0.25 + FRand() * 0.35);
Controller.Focus = None;
}
if( !bFireAtWill )
{
MGFireDuration = Level.TimeSeconds + (0.75 + FRand() * 0.5);
}
else if ( FRand() < 0.03 && Controller.Enemy != none && PlayerController(Controller.Enemy.Controller) != none )
{
// Randomly send out a message about Patriarch shooting chain gun(3% chance)
PlayerController(Controller.Enemy.Controller).Speech('AUTO', 9, "");
}
bFireAtWill = True;
bShotAnim = true;
Acceleration = vect(0,0,0);
SetAnimAction('FireMG');
bWaitForAnim = true;
}
}
function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class<DamageType> damageType, optional int HitIndex)
{
local float EnemyDistSq, DamagerDistSq;
global.TakeDamage(Damage,instigatedBy,hitlocation,Momentum,damageType);
if( bMovingChaingunAttack || Health<=0 )
return;
// if someone close up is shooting us, just charge them
if( InstigatedBy != none )
{
DamagerDistSq = VSizeSquared(Location - InstigatedBy.Location);
if( (ChargeDamage > 200 && DamagerDistSq < (500 * 500)) || DamagerDistSq < (100 * 100) )
{
SetAnimAction('transition');
GoToState('Charging');
return;
}
}
if( Controller.Enemy != none && InstigatedBy != none && InstigatedBy != Controller.Enemy )
{
EnemyDistSq = VSizeSquared(Location - Controller.Enemy.Location);
DamagerDistSq = VSizeSquared(Location - InstigatedBy.Location);
}
if( InstigatedBy != none && (DamagerDistSq < EnemyDistSq || Controller.Enemy == none) )
{
MonsterController(Controller).ChangeEnemy(InstigatedBy,Controller.CanSee(InstigatedBy));
Controller.Target = InstigatedBy;
Controller.Focus = InstigatedBy;
if( DamagerDistSq < (500 * 500) )
{
SetAnimAction('transition');
GoToState('Charging');
}
}
}
Begin:
While( True )
{
if( !bMovingChaingunAttack )
Acceleration = vect(0,0,0);
if( MGLostSightTimeout > 0 && Level.TimeSeconds > MGLostSightTimeout )
{
Acceleration = vect(0,0,0);
bShotAnim = true;
Acceleration = vect(0,0,0);
SetAnimAction('FireEndMG');
HandleWaitForAnim('FireEndMG');
GoToState('');
}
if( MGFireCounter <= 0 )
{
bShotAnim = true;
Acceleration = vect(0,0,0);
SetAnimAction('FireEndMG');
HandleWaitForAnim('FireEndMG');
GoToState('');
}
// Give some randomness to the patriarch's firing (constantly fire after first stage passed)
if( Level.TimeSeconds > MGFireDuration && SyringeCount==0 )
{
if( AmbientSound != MiniGunSpinSound )
{
SoundVolume=185;
SoundRadius=200;
AmbientSound = MiniGunSpinSound;
}
Sleep(0.5 + FRand() * 0.75);
MGFireDuration = Level.TimeSeconds + (0.75 + FRand() * 0.5);
}
else
{
if( bFireAtWill )
FireMGShot();
Sleep(0.05);
}
}
}
state FireMissile
{
function RangedAttack(Actor A)
{
if( SyringeCount>=2 )
{
Controller.Target = A;
Controller.Focus = A;
}
}
function BeginState()
{
if ( bEndGameBoss )
MissilesLeft = SyringeCount+Rand(SyringeCount);
else
MissilesLeft = 1+Rand(3);
Acceleration = vect(0,0,0);
}
function AnimEnd( int Channel )
{
local vector Start;
local Rotator R;
Start = GetBoneCoords('tip').Origin;
if( Controller.Target==None )
Controller.Target = Controller.Enemy;
if ( !SavedFireProperties.bInitialized )
{
SavedFireProperties.AmmoClass = MyAmmo.Class;
SavedFireProperties.ProjectileClass = Class'BossLAWProj';
SavedFireProperties.WarnTargetPct = 0.15;
SavedFireProperties.MaxRange = 10000;
SavedFireProperties.bTossed = False;
SavedFireProperties.bLeadTarget = True;
SavedFireProperties.bInitialized = true;
}
SavedFireProperties.bInstantHit = (SyringeCount<1);
SavedFireProperties.bTrySplash = (SyringeCount>=2);
R = AdjustAim(SavedFireProperties,Start,100);
PlaySound(RocketFireSound,SLOT_Interact,2.0,,TransientSoundRadius,,false);
Spawn(Class'BossLAWProj',,,Start,R);
bShotAnim = true;
Acceleration = vect(0,0,0);
SetAnimAction('FireEndMissile');
HandleWaitForAnim('FireEndMissile');
// Randomly send out a message about Patriarch shooting a rocket(5% chance)
if ( FRand() < 0.05 && Controller.Enemy != none && PlayerController(Controller.Enemy.Controller) != none )
{
PlayerController(Controller.Enemy.Controller).Speech('AUTO', 10, "");
}
if( MissilesLeft==0 )
GoToState('');
else
{
--MissilesLeft;
GoToState(,'SecondMissile');
}
}
Begin:
while ( true )
{
Acceleration = vect(0,0,0);
Sleep(0.1);
}
SecondMissile:
Acceleration = vect(0,0,0);
Sleep(0.5f);
AnimEnd(0);
}
State Escaping // Added god-mode if bEndGameBoss
{
Ignores RangedAttack;
function BeginState()
{
GiveUpTime = Level.TimeSeconds+20.f+FRand()*20.f;
Super.BeginState();
if ( bEndGameBoss ) {
bBlockActors = false;
// While escaping Pat doesn't blow up pipebombs -- PooSH
MotionDetectorThreat=0;
}
}
function EndState()
{
Super.EndState();
if( Health>0 )
bBlockActors = true;
MotionDetectorThreat = default.MotionDetectorThreat;
}
function Tick( float Delta )
{
if( Level.TimeSeconds>GiveUpTime )
{
BeginHealing();
return;
}
if( !bChargingPlayer )
{
bChargingPlayer = true;
if( Level.NetMode!=NM_DedicatedServer )
PostNetReceive();
}
GroundSpeed = OriginalGroundSpeed * 2.5;
Global.Tick(Delta);
}
function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class<DamageType> damageType, optional int HitIndex)
{
if ( !bEndGameBoss )
global.TakeDamage(Damage,instigatedBy,hitlocation,Momentum,damageType);
}
}
State SneakAround
{
function BeginState()
{
super.BeginState();
SneakStartTime = Level.TimeSeconds+10.f+FRand()*15.f;
}
function EndState()
{
super.EndState();
LastSneakedTime = Level.TimeSeconds+20.f+FRand()*30.f;
if( Controller!=None && Controller.IsInState('PatFindWay') )
Controller.GoToState('ZombieHunt');
}
function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class<DamageType> damageType, optional int HitIndex)
{
global.TakeDamage(Damage,instigatedBy,hitlocation,Momentum,damageType);
if( Health<=0 )
return;
// if someone close up is shooting us, just charge them
if( InstigatedBy!=none && VSizeSquared(Location - InstigatedBy.Location)<62500 )
GoToState('Charging');
}
Begin:
CloakBoss();
if( SyringeCount>=2 && FRand()<0.6 )
HardPatController(Controller).FindPathAround();
While( true )
{
Sleep(0.5);
if( !bCloaked && !bShotAnim )
CloakBoss();
if( !Controller.IsInState('PatFindWay') )
{
if( Level.TimeSeconds>SneakStartTime )
GoToState('');
if( !Controller.IsInState('WaitForAnim') && !Controller.IsInState('ZombieHunt') )
Controller.GoToState('ZombieHunt');
}
else SneakStartTime = Level.TimeSeconds+30.f;
}
}
defaultproperties
{
SaveMeSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_SaveMe'
RocketFireSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_FireRocket'
MiniGunFireSound=Sound'KF_BasePatriarch.Attack.Kev_MG_GunfireLoop'
MiniGunSpinSound=Sound'KF_BasePatriarch.Attack.Kev_MG_TurbineFireLoop'
MeleeImpaleHitSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_HitPlayer_Impale'
MoanVoice=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Talk'
MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_HitPlayer_Fist'
JumpSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Jump'
DetachedArmClass=Class'KFChar.SeveredArmPatriarch'
DetachedLegClass=Class'KFChar.SeveredLegPatriarch'
DetachedHeadClass=Class'KFChar.SeveredHeadPatriarch'
DetachedSpecialArmClass=Class'KFChar.SeveredRocketArmPatriarch'
HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Pain'
DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Death'
ScoringValue=1000
MenuName="Hard Pat"
ControllerClass=Class'ScrnZedPack.HardPatController'
AmbientSound=Sound'KF_BasePatriarch.Idle.Kev_IdleLoop'
Mesh=SkeletalMesh'KF_Freaks_Trip.Patriarch_Freak'
LODBias=4.000000
Skins(0)=Combiner'KF_Specimens_Trip_T.gatling_cmb'
Skins(1)=Combiner'KF_Specimens_Trip_T.patriarch_cmb'
}