1818 lines
56 KiB
Ucode
1818 lines
56 KiB
Ucode
// Zombie Monster for KF Invasion gametype
|
|
class NiceZombieBoss extends NiceZombieBossBase;
|
|
#exec OBJ LOAD FILE=KFPatch2.utx
|
|
#exec OBJ LOAD FILE=KF_Specimens_Trip_T.utx
|
|
//----------------------------------------------------------------------------
|
|
// NOTE: Most Variables are declared in the base class to eliminate hitching
|
|
//----------------------------------------------------------------------------
|
|
var NiceBossHPNeedle CurrentNeedle;
|
|
// Last time we checked if a player was melee exploiting us
|
|
var float LastMeleeExploitCheckTime;
|
|
// Used to track what type of melee exploiters you have
|
|
var int NumLumberJacks;
|
|
var int NumNinjas;
|
|
// Make the Boss's ambient scale higher, since there is only 1, doesn't matter if he's relevant almost all the time
|
|
simulated function CalcAmbientRelevancyScale()
|
|
{
|
|
// Make the zed only relevant by thier ambient sound out to a range of 100 meters
|
|
CustomAmbientRelevancyScale = 5000/(100 * SoundRadius);
|
|
}
|
|
function vector ComputeTrajectoryByTime( vector StartPosition, vector EndPosition, float fTimeEnd )
|
|
{
|
|
local vector NewVelocity;
|
|
NewVelocity = Super.ComputeTrajectoryByTime( StartPosition, EndPosition, fTimeEnd );
|
|
if( PhysicsVolume.IsA( 'KFPhysicsVolume' ) && StartPosition.Z < EndPosition.Z )
|
|
{
|
|
if( PhysicsVolume.Gravity.Z < class'PhysicsVolume'.default.Gravity.Z )
|
|
{
|
|
// Just checking mass to be extra-cautious.
|
|
if( Mass > 900 )
|
|
{
|
|
// Extra velocity boost to counter oversized mass weighing the boss down.
|
|
NewVelocity.Z += 90;
|
|
}
|
|
}
|
|
}
|
|
return NewVelocity;
|
|
}
|
|
function ZombieMoan()
|
|
{
|
|
if( !bShotAnim ) // Do not moan while taunting
|
|
Super.ZombieMoan();
|
|
}
|
|
// Speech notifies called from the anims
|
|
function PatriarchKnockDown()
|
|
{
|
|
PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_KnockedDown', SLOT_Misc, 2.0,true,500.0);
|
|
}
|
|
function PatriarchEntrance()
|
|
{
|
|
PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Entrance', SLOT_Misc, 2.0,true,500.0);
|
|
}
|
|
function PatriarchVictory()
|
|
{
|
|
PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Victory', SLOT_Misc, 2.0,true,500.0);
|
|
}
|
|
function PatriarchMGPreFire()
|
|
{
|
|
PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_WarnGun', SLOT_Misc, 2.0,true,1000.0);
|
|
}
|
|
function PatriarchMisslePreFire()
|
|
{
|
|
PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_WarnRocket', SLOT_Misc, 2.0,true,1000.0);
|
|
}
|
|
// Taunt to use when doing the melee exploit radial attack
|
|
function PatriarchRadialTaunt()
|
|
{
|
|
if( NumNinjas > 0 && NumNinjas > NumLumberJacks )
|
|
{
|
|
PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_TauntNinja', SLOT_Misc, 2.0,true,500.0);
|
|
}
|
|
else if( NumLumberJacks > 0 && NumLumberJacks > NumNinjas )
|
|
{
|
|
PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_TauntLumberJack', SLOT_Misc, 2.0,true,500.0);
|
|
}
|
|
else
|
|
{
|
|
PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_TauntRadial', SLOT_Misc, 2.0,true,500.0);
|
|
}
|
|
}
|
|
// Don't do this for the Patriarch
|
|
// No need to overload these when inheriting from NiceMonster
|
|
/*simulated function SetBurningBehavior(){}
|
|
simulated function UnSetBurningBehavior(){}*/
|
|
function bool CanGetOutOfWay(){
|
|
return false;
|
|
}
|
|
simulated function Tick(float DeltaTime)
|
|
{
|
|
local KFHumanPawn HP;
|
|
Super.Tick(DeltaTime);
|
|
// Process the pipe bomb time damage scale, reducing the scale over time so
|
|
// it goes back up to 100% damage over a few seconds
|
|
if( Role == ROLE_Authority )
|
|
{
|
|
PipeBombDamageScale -= DeltaTime * 0.33;
|
|
|
|
if( PipeBombDamageScale < 0 )
|
|
{
|
|
PipeBombDamageScale = 0;
|
|
}
|
|
}
|
|
if( Level.NetMode==NM_DedicatedServer )
|
|
Return; // Servers aren't intrested in this info.
|
|
bSpecialCalcView = bIsBossView;
|
|
if( bZapped )
|
|
{
|
|
// Make sure we check if we need to be cloaked as soon as the zap wears off
|
|
LastCheckTimes = Level.TimeSeconds;
|
|
}
|
|
else if( bCloaked && Level.TimeSeconds>LastCheckTimes )
|
|
{
|
|
LastCheckTimes = Level.TimeSeconds+0.8;
|
|
ForEach VisibleCollidingActors(Class'KFHumanPawn',HP,1000,Location)
|
|
{
|
|
if( HP.Health <= 0 || !HP.ShowStalkers() )
|
|
continue;
|
|
|
|
// If he's a commando, we've been spotted.
|
|
if( !bSpotted )
|
|
{
|
|
bSpotted = True;
|
|
CloakBoss();
|
|
}
|
|
Return;
|
|
}
|
|
// if we're uberbrite, turn down the light
|
|
if( bSpotted )
|
|
{
|
|
bSpotted = False;
|
|
bUnlit = false;
|
|
CloakBoss();
|
|
}
|
|
}
|
|
}
|
|
simulated function CloakBoss()
|
|
{
|
|
local Controller C;
|
|
local int Index;
|
|
// No cloaking if zapped
|
|
if( bZapped )
|
|
{
|
|
return;
|
|
}
|
|
if( bSpotted )
|
|
{
|
|
Visibility = 120;
|
|
if( Level.NetMode==NM_DedicatedServer )
|
|
Return;
|
|
Skins[0] = Finalblend'KFX.StalkerGlow';
|
|
Skins[1] = Finalblend'KFX.StalkerGlow';
|
|
bUnlit = true;
|
|
return;
|
|
}
|
|
Visibility = 1;
|
|
bCloaked = true;
|
|
if( Level.NetMode!=NM_Client )
|
|
{
|
|
For( C=Level.ControllerList; C!=none; C=C.NextController )
|
|
{
|
|
if( C.bIsPlayer && C.Enemy==Self )
|
|
C.Enemy = none; // Make bots lose sight with me.
|
|
}
|
|
}
|
|
if( Level.NetMode==NM_DedicatedServer )
|
|
Return;
|
|
Skins[0] = Shader'KF_Specimens_Trip_T.patriarch_invisible_gun';
|
|
Skins[1] = Shader'KF_Specimens_Trip_T.patriarch_invisible';
|
|
// Invisible - no shadow
|
|
if(PlayerShadow != none)
|
|
PlayerShadow.bShadowActive = false;
|
|
// Remove/disallow projectors on invisible people
|
|
Projectors.Remove(0, Projectors.Length);
|
|
bAcceptsProjectors = false;
|
|
SetOverlayMaterial(FinalBlend'KF_Specimens_Trip_T.patriarch_fizzle_FB', 1.0, true);
|
|
// Randomly send out a message about Patriarch going invisible(10% chance)
|
|
if ( FRand() < 0.10 )
|
|
{
|
|
// Pick a random Player to say the message
|
|
Index = Rand(Level.Game.NumPlayers);
|
|
|
|
for ( C = Level.ControllerList; C != none; C = C.NextController )
|
|
{
|
|
if ( PlayerController(C) != none )
|
|
{
|
|
if ( Index == 0 )
|
|
{
|
|
PlayerController(C).Speech('AUTO', 8, "");
|
|
break;
|
|
}
|
|
|
|
Index--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
simulated function UnCloakBoss()
|
|
{
|
|
if( bZapped )
|
|
{
|
|
return;
|
|
}
|
|
Visibility = default.Visibility;
|
|
bCloaked = false;
|
|
bSpotted = False;
|
|
bUnlit = False;
|
|
if( Level.NetMode==NM_DedicatedServer )
|
|
Return;
|
|
Skins = Default.Skins;
|
|
if (PlayerShadow != none)
|
|
PlayerShadow.bShadowActive = true;
|
|
bAcceptsProjectors = true;
|
|
SetOverlayMaterial( none, 0.0, true );
|
|
}
|
|
// Set the zed to the zapped behavior
|
|
simulated function SetZappedBehavior()
|
|
{
|
|
super.SetZappedBehavior();
|
|
// Handle setting the zed to uncloaked so the zapped overlay works properly
|
|
if( Level.Netmode != NM_DedicatedServer )
|
|
{
|
|
bUnlit = false;
|
|
Skins = Default.Skins;
|
|
|
|
if (PlayerShadow != none)
|
|
PlayerShadow.bShadowActive = true;
|
|
|
|
bAcceptsProjectors = true;
|
|
SetOverlayMaterial(Material'KFZED_FX_T.Energy.ZED_overlay_Hit_Shdr', 999, true);
|
|
}
|
|
}
|
|
// Turn off the zapped behavior
|
|
simulated function UnSetZappedBehavior()
|
|
{
|
|
super.UnSetZappedBehavior();
|
|
// Handle getting the zed back cloaked if need be
|
|
if( Level.Netmode != NM_DedicatedServer )
|
|
{
|
|
LastCheckTimes = Level.TimeSeconds;
|
|
SetOverlayMaterial(none, 0.0f, true);
|
|
}
|
|
}
|
|
// Overridden because we need to handle the overlays differently for zombies that can cloak
|
|
function SetZapped(float ZapAmount, Pawn Instigator)
|
|
{
|
|
LastZapTime = Level.TimeSeconds;
|
|
if( bZapped )
|
|
{
|
|
TotalZap = ZapThreshold;
|
|
RemainingZap = ZapDuration;
|
|
}
|
|
else
|
|
{
|
|
TotalZap += ZapAmount;
|
|
|
|
if( TotalZap >= ZapThreshold )
|
|
{
|
|
RemainingZap = ZapDuration;
|
|
bZapped = true;
|
|
}
|
|
}
|
|
ZappedBy = Instigator;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// PostBeginPlay
|
|
//-----------------------------------------------------------------------------
|
|
simulated function PostBeginPlay()
|
|
{
|
|
super.PostBeginPlay();
|
|
if( Role < ROLE_Authority )
|
|
{
|
|
return;
|
|
}
|
|
// Difficulty Scaling
|
|
if (Level.Game != none)
|
|
{
|
|
if(Level.Game.GameDifficulty >= 5.0) // Hell on Earth & Suicidal
|
|
MGDamage = default.MGDamage * 1.3;
|
|
else
|
|
MGDamage = default.MGDamage * 1.0;
|
|
}
|
|
HealingLevels[0] = Health/1.25; // Around 5600 HP
|
|
HealingLevels[1] = Health/2.f; // Around 3500 HP
|
|
HealingLevels[2] = Health/3.2; // Around 2187 HP
|
|
// log("Health = "$Health);
|
|
// log("HealingLevels[0] = "$HealingLevels[0]);
|
|
// log("HealingLevels[1] = "$HealingLevels[1]);
|
|
// log("HealingLevels[2] = "$HealingLevels[2]);
|
|
HealingAmount = Health/4; // 1750 HP
|
|
// log("HealingAmount = "$HealingAmount);
|
|
}
|
|
function bool MakeGrandEntry()
|
|
{
|
|
bShotAnim = true;
|
|
Acceleration = vect(0,0,0);
|
|
SetAnimAction('Entrance');
|
|
HandleWaitForAnim('Entrance');
|
|
GotoState('MakingEntrance');
|
|
return True;
|
|
}
|
|
// State of playing the initial entrance anim
|
|
state MakingEntrance
|
|
{
|
|
Ignores RangedAttack;
|
|
function Tick( float Delta )
|
|
{
|
|
Acceleration = vect(0,0,0);
|
|
|
|
global.Tick(Delta);
|
|
}
|
|
Begin:
|
|
Sleep(GetAnimDuration('Entrance'));
|
|
GotoState('InitialSneak');
|
|
}
|
|
// State of doing a radial damaging attack that we do when people are trying to melee exploit
|
|
state RadialAttack
|
|
{
|
|
Ignores RangedAttack;
|
|
function bool ShouldChargeFromDamage()
|
|
{
|
|
return false;
|
|
}
|
|
function Tick( float Delta )
|
|
{
|
|
Acceleration = vect(0,0,0);
|
|
|
|
//DrawDebugSphere( Location, 150, 12, 0, 255, 0);
|
|
|
|
global.Tick(Delta);
|
|
}
|
|
function ClawDamageTarget()
|
|
{
|
|
local vector PushDir;
|
|
local float UsedMeleeDamage;
|
|
local bool bDamagedSomeone, bDamagedThisHit;
|
|
local KFHumanPawn P;
|
|
local Actor OldTarget;
|
|
local float RadialDamageBase;
|
|
|
|
MeleeRange = 150;
|
|
|
|
if(Controller!=none && Controller.Target!=none)
|
|
PushDir = (damageForce * Normal(Controller.Target.Location - Location));
|
|
else
|
|
PushDir = damageForce * vector(Rotation);
|
|
|
|
OldTarget = Controller.Target;
|
|
|
|
// Damage all players within a radius
|
|
foreach DynamicActors(class'KFHumanPawn', P)
|
|
{
|
|
if ( VSize(P.Location - Location) < MeleeRange)
|
|
{
|
|
Controller.Target = P;
|
|
|
|
// This attack cuts through shields, so crank up the damage if they have a lot of shields
|
|
if( P.ShieldStrength >= 50 )
|
|
{
|
|
RadialDamageBase = 240;
|
|
}
|
|
else
|
|
{
|
|
RadialDamageBase = 120;
|
|
}
|
|
|
|
// Randomize the damage a bit so everyone gets really hurt, but only some poeple die
|
|
UsedMeleeDamage = (RadialDamageBase - (RadialDamageBase * 0.55)) + (RadialDamageBase * (FRand() * 0.45));
|
|
//log("UsedMeleeDamage = "$UsedMeleeDamage);
|
|
|
|
bDamagedThisHit = MeleeDamageTarget(UsedMeleeDamage, damageForce * Normal(P.Location - Location));
|
|
if( !bDamagedSomeone && bDamagedThisHit )
|
|
{
|
|
bDamagedSomeone = true;
|
|
}
|
|
MeleeRange = 150;
|
|
}
|
|
}
|
|
|
|
Controller.Target = OldTarget;
|
|
|
|
MeleeRange = Default.MeleeRange;
|
|
|
|
if ( bDamagedSomeone )
|
|
{
|
|
// Maybe cause zedtime when the patriarch does his radial attack
|
|
KFGameType(Level.Game).DramaticEvent(0.3);
|
|
PlaySound(MeleeAttackHitSound, SLOT_Interact, 2.0);
|
|
}
|
|
}
|
|
function EndState()
|
|
{
|
|
NumLumberJacks = 0;
|
|
NumNinjas = 0;
|
|
}
|
|
Begin:
|
|
// Don't let the zed move and play the radial attack
|
|
bShotAnim = true;
|
|
Acceleration = vect(0,0,0);
|
|
SetAnimAction('RadialAttack');
|
|
KFMonsterController(Controller).bUseFreezeHack = True;
|
|
HandleWaitForAnim('RadialAttack');
|
|
Sleep(GetAnimDuration('RadialAttack'));
|
|
// TODO: this sleep is here to allow for playing the taunt sound. Take it out when the animation is extended with the taunt - Ramm
|
|
//Sleep(2.5);
|
|
GotoState('');
|
|
}
|
|
simulated function Destroyed()
|
|
{
|
|
if( mTracer!=none )
|
|
mTracer.Destroy();
|
|
if( mMuzzleFlash!=none )
|
|
mMuzzleFlash.Destroy();
|
|
Super.Destroyed();
|
|
}
|
|
simulated Function PostNetBeginPlay()
|
|
{
|
|
EnableChannelNotify ( 1,1);
|
|
AnimBlendParams(1, 1.0, 0.0,, SpineBone1);
|
|
super.PostNetBeginPlay();
|
|
TraceHitPos = vect(0,0,0);
|
|
bNetNotify = True;
|
|
}
|
|
function bool IsStunPossible(){
|
|
return false;
|
|
}
|
|
function bool CheckStun(int stunScore,
|
|
Pawn instigatedBy,
|
|
Vector hitLocation,
|
|
Vector momentum,
|
|
class<NiceWeaponDamageType> damageType,
|
|
float headshotLevel,
|
|
KFPlayerReplicationInfo KFPRI){
|
|
return false;
|
|
}
|
|
function bool CheckMiniFlinch( int flinchScore,
|
|
Pawn instigatedBy,
|
|
Vector hitLocation,
|
|
Vector momentum,
|
|
class<NiceWeaponDamageType> damageType,
|
|
float headshotLevel,
|
|
KFPlayerReplicationInfo KFPRI){
|
|
return false;
|
|
}
|
|
function bool OnlyEnemyAround( Pawn Other )
|
|
{
|
|
local Controller C;
|
|
For( C=Level.ControllerList; C!=none; C=C.NextController )
|
|
{
|
|
if( C.bIsPlayer && C.Pawn!=none && C.Pawn!=Other && ((VSize(C.Pawn.Location-Location)<1500 && FastTrace(C.Pawn.Location,Location))
|
|
|| (VSize(C.Pawn.Location-Other.Location)<1000 && FastTrace(C.Pawn.Location,Other.Location))) )
|
|
Return False;
|
|
}
|
|
Return True;
|
|
}
|
|
function bool IsCloseEnuf( Actor A )
|
|
{
|
|
local vector V;
|
|
if( A==none )
|
|
Return False;
|
|
V = A.Location-Location;
|
|
if( Abs(V.Z)>(CollisionHeight+A.CollisionHeight) )
|
|
Return False;
|
|
V.Z = 0;
|
|
Return (VSize(V)<(CollisionRadius+A.CollisionRadius+25));
|
|
}
|
|
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 )
|
|
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 > 20.0 )
|
|
{
|
|
if( FRand() < 0.3 )
|
|
{
|
|
// Wait another 20 to try this again
|
|
LastSneakedTime = Level.TimeSeconds;//+FRand()*120;
|
|
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 )
|
|
{
|
|
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');
|
|
MGFireCounter = Rand(60) + 35;
|
|
|
|
GoToState('FireChaingun');
|
|
}
|
|
}
|
|
event Bump(actor Other)
|
|
{
|
|
Super(Monster).Bump(Other);
|
|
if( Other==none )
|
|
return;
|
|
if( Other.IsA('NetKActor') && Physics != PHYS_Falling && !bShotAnim && Abs(Other.Location.Z-Location.Z)<(CollisionHeight+Other.CollisionHeight) )
|
|
{ // Kill the annoying deco brat.
|
|
Controller.Target = Other;
|
|
Controller.Focus = Other;
|
|
bShotAnim = true;
|
|
Acceleration = (Other.Location-Location);
|
|
SetAnimAction('MeleeClaw');
|
|
//PlaySound(sound'Claw2s', SLOT_none); KFTODO: Replace this
|
|
HandleWaitForAnim('MeleeClaw');
|
|
}
|
|
}
|
|
simulated function AddTraceHitFX( vector HitPos )
|
|
{
|
|
local vector Start,SpawnVel,SpawnDir;
|
|
local float hitDist;
|
|
Start = GetBoneCoords('tip').Origin;
|
|
if( mTracer==none )
|
|
mTracer = Spawn(Class'KFMod.KFNewTracer',,,Start);
|
|
else mTracer.SetLocation(Start);
|
|
if( mMuzzleFlash==none )
|
|
{
|
|
// KFTODO: Replace this
|
|
mMuzzleFlash = Spawn(Class'MuzzleFlash3rdMG');
|
|
AttachToBone(mMuzzleFlash, 'tip');
|
|
}
|
|
else mMuzzleFlash.SpawnParticle(1);
|
|
hitDist = VSize(HitPos - Start) - 50.f;
|
|
if( hitDist>10 )
|
|
{
|
|
SpawnDir = Normal(HitPos - Start);
|
|
SpawnVel = SpawnDir * 10000.f;
|
|
mTracer.Emitters[0].StartVelocityRange.X.Min = SpawnVel.X;
|
|
mTracer.Emitters[0].StartVelocityRange.X.Max = SpawnVel.X;
|
|
mTracer.Emitters[0].StartVelocityRange.Y.Min = SpawnVel.Y;
|
|
mTracer.Emitters[0].StartVelocityRange.Y.Max = SpawnVel.Y;
|
|
mTracer.Emitters[0].StartVelocityRange.Z.Min = SpawnVel.Z;
|
|
mTracer.Emitters[0].StartVelocityRange.Z.Max = SpawnVel.Z;
|
|
mTracer.Emitters[0].LifetimeRange.Min = hitDist / 10000.f;
|
|
mTracer.Emitters[0].LifetimeRange.Max = mTracer.Emitters[0].LifetimeRange.Min;
|
|
mTracer.SpawnParticle(1);
|
|
}
|
|
Instigator = Self;
|
|
if( HitPos != vect(0,0,0) )
|
|
{
|
|
Spawn(class'ROBulletHitEffect',,, HitPos, Rotator(Normal(HitPos - Start)));
|
|
}
|
|
}
|
|
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' )
|
|
{
|
|
Super.AnimEnd(Channel);
|
|
return;
|
|
}
|
|
|
|
PlayAnim('FireMG');
|
|
bWaitForAnim = true;
|
|
bShotAnim = true;
|
|
IdleTime = Level.TimeSeconds;
|
|
}
|
|
else Super.AnimEnd(Channel);
|
|
}
|
|
state FireChaingun
|
|
{
|
|
function RangedAttack(Actor A)
|
|
{
|
|
Controller.Target = A;
|
|
Controller.Focus = A;
|
|
}
|
|
// Chaingun mode handles this itself
|
|
function bool ShouldChargeFromDamage()
|
|
{
|
|
return false;
|
|
}
|
|
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,vect(0,0,0),damageType);
|
|
|
|
// 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');
|
|
//log("Frak this shizz, Charging!!!!");
|
|
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');
|
|
}
|
|
}
|
|
}
|
|
function EndState()
|
|
{
|
|
TraceHitPos = vect(0,0,0);
|
|
bMinigunning = False;
|
|
|
|
AmbientSound = default.AmbientSound;
|
|
SoundVolume=default.SoundVolume;
|
|
SoundRadius=default.SoundRadius;
|
|
MGFireCounter=0;
|
|
|
|
LastChainGunTime = Level.TimeSeconds + 5 + (FRand()*10);
|
|
}
|
|
function BeginState()
|
|
{
|
|
bFireAtWill = False;
|
|
Acceleration = vect(0,0,0);
|
|
MGLostSightTimeout = 0.0;
|
|
bMinigunning = True;
|
|
}
|
|
function AnimEnd( int Channel )
|
|
{
|
|
if( MGFireCounter <= 0 )
|
|
{
|
|
bShotAnim = true;
|
|
Acceleration = vect(0,0,0);
|
|
SetAnimAction('FireEndMG');
|
|
HandleWaitForAnim('FireEndMG');
|
|
GoToState('');
|
|
}
|
|
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 FireMGShot()
|
|
{
|
|
local vector Start,End,HL,HN,Dir;
|
|
local rotator R;
|
|
local Actor A;
|
|
|
|
MGFireCounter--;
|
|
|
|
if( AmbientSound != MiniGunFireSound )
|
|
{
|
|
SoundVolume=255;
|
|
SoundRadius=400;
|
|
AmbientSound = MiniGunFireSound;
|
|
}
|
|
|
|
Start = GetBoneCoords('tip').Origin;
|
|
if( Controller.Focus!=none )
|
|
R = rotator(Controller.Focus.Location-Start);
|
|
else R = rotator(Controller.FocalPoint-Start);
|
|
if( NeedToTurnFor(R) )
|
|
R = Rotation;
|
|
// KFTODO: Maybe scale this accuracy by his skill or the game difficulty
|
|
Dir = Normal(vector(R)+VRand()*0.06); //*0.04
|
|
End = Start+Dir*10000;
|
|
|
|
// Have to turn of hit point collision so trace doesn't hit the Human Pawn's bullet whiz cylinder
|
|
bBlockHitPointTraces = false;
|
|
A = Trace(HL,HN,End,Start,True);
|
|
bBlockHitPointTraces = true;
|
|
|
|
if( A==none )
|
|
Return;
|
|
TraceHitPos = HL;
|
|
if( Level.NetMode!=NM_DedicatedServer )
|
|
AddTraceHitFX(HL);
|
|
|
|
if( A!=Level )
|
|
{
|
|
A.TakeDamage(MGDamage+Rand(3),Self,HL,Dir*500,Class'DamageType');
|
|
}
|
|
}
|
|
function bool NeedToTurnFor( rotator targ )
|
|
{
|
|
local int YawErr;
|
|
|
|
targ.Yaw = DesiredRotation.Yaw & 65535;
|
|
YawErr = (targ.Yaw - (Rotation.Yaw & 65535)) & 65535;
|
|
return !((YawErr < 2000) || (YawErr > 64535));
|
|
}
|
|
Begin:
|
|
While( True )
|
|
{
|
|
Acceleration = vect(0,0,0);
|
|
|
|
if( MGLostSightTimeout > 0 && Level.TimeSeconds > MGLostSightTimeout )
|
|
{
|
|
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
|
|
if( Level.TimeSeconds > MGFireDuration )
|
|
{
|
|
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
|
|
{
|
|
Ignores RangedAttack;
|
|
function bool ShouldChargeFromDamage()
|
|
{
|
|
return false;
|
|
}
|
|
function BeginState()
|
|
{
|
|
Acceleration = vect(0,0,0);
|
|
}
|
|
function AnimEnd( int Channel )
|
|
{
|
|
local vector Start;
|
|
local Rotator R;
|
|
|
|
Start = GetBoneCoords('tip').Origin;
|
|
|
|
if ( !SavedFireProperties.bInitialized )
|
|
{
|
|
SavedFireProperties.AmmoClass = MyAmmo.Class;
|
|
SavedFireProperties.ProjectileClass = MyAmmo.ProjectileClass;
|
|
SavedFireProperties.WarnTargetPct = 0.15;
|
|
SavedFireProperties.MaxRange = 10000;
|
|
SavedFireProperties.bTossed = False;
|
|
SavedFireProperties.bTrySplash = False;
|
|
SavedFireProperties.bLeadTarget = True;
|
|
SavedFireProperties.bInstantHit = True;
|
|
SavedFireProperties.bInitialized = true;
|
|
}
|
|
|
|
R = AdjustAim(SavedFireProperties,Start,100);
|
|
PlaySound(RocketFireSound,SLOT_Interact,2.0,,TransientSoundRadius,,false);
|
|
Spawn(Class'NiceBossLAWProj',,,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, "");
|
|
}
|
|
|
|
GoToState('');
|
|
}
|
|
Begin:
|
|
while ( true )
|
|
{
|
|
Acceleration = vect(0,0,0);
|
|
Sleep(0.1);
|
|
}
|
|
}
|
|
function bool MeleeDamageTarget(int hitdamage, vector pushdir)
|
|
{
|
|
if( Controller.Target!=none && Controller.Target.IsA('NetKActor') )
|
|
pushdir = Normal(Controller.Target.Location-Location)*100000; // Fly bitch!
|
|
// Used to set MeleeRange = Default.MeleeRange; in Balance Round 1, fixed in Balance Round 2
|
|
return Super.MeleeDamageTarget(hitdamage, pushdir);
|
|
}
|
|
state Charging
|
|
{
|
|
// Don't override speed in this state
|
|
function bool CanSpeedAdjust()
|
|
{
|
|
return false;
|
|
}
|
|
function bool ShouldChargeFromDamage()
|
|
{
|
|
return false;
|
|
}
|
|
function BeginState()
|
|
{
|
|
bChargingPlayer = True;
|
|
if( Level.NetMode!=NM_DedicatedServer )
|
|
PostNetReceive();
|
|
|
|
// How many charge attacks we can do randomly 1-3
|
|
NumChargeAttacks = Rand(2) + 1;
|
|
}
|
|
function EndState()
|
|
{
|
|
SetGroundSpeed(GetOriginalGroundSpeed());
|
|
bChargingPlayer = False;
|
|
ChargeDamage = 0;
|
|
if( Level.NetMode!=NM_DedicatedServer )
|
|
PostNetReceive();
|
|
|
|
LastChargeTime = Level.TimeSeconds;
|
|
}
|
|
function Tick( float Delta )
|
|
{
|
|
|
|
if( NumChargeAttacks <= 0 )
|
|
{
|
|
GoToState('');
|
|
}
|
|
|
|
// Keep the flesh pound moving toward its target when attacking
|
|
if( Role == ROLE_Authority && bShotAnim)
|
|
{
|
|
if( bChargingPlayer )
|
|
{
|
|
bChargingPlayer = false;
|
|
if( Level.NetMode!=NM_DedicatedServer )
|
|
PostNetReceive();
|
|
}
|
|
SetGroundSpeed(OriginalGroundSpeed * 1.25);
|
|
if( LookTarget!=none )
|
|
{
|
|
Acceleration = AccelRate * Normal(LookTarget.Location - Location);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( !bChargingPlayer )
|
|
{
|
|
bChargingPlayer = true;
|
|
if( Level.NetMode!=NM_DedicatedServer )
|
|
PostNetReceive();
|
|
}
|
|
|
|
// Zapping slows him down, but doesn't stop him
|
|
if( bZapped )
|
|
{
|
|
SetGroundSpeed(OriginalGroundSpeed * 1.5);
|
|
}
|
|
else
|
|
{
|
|
SetGroundSpeed(OriginalGroundSpeed * 2.5);
|
|
}
|
|
}
|
|
|
|
Global.Tick(Delta);
|
|
}
|
|
function bool MeleeDamageTarget(int hitdamage, vector pushdir)
|
|
{
|
|
local bool RetVal;
|
|
|
|
NumChargeAttacks--;
|
|
|
|
RetVal = Global.MeleeDamageTarget(hitdamage, pushdir*1.5);
|
|
if( RetVal )
|
|
GoToState('');
|
|
return RetVal;
|
|
}
|
|
function RangedAttack(Actor A)
|
|
{
|
|
if( VSize(A.Location-Location)>700 && Level.TimeSeconds - LastForceChargeTime > 3.0 )
|
|
GoToState('');
|
|
Global.RangedAttack(A);
|
|
}
|
|
Begin:
|
|
Sleep(6);
|
|
GoToState('');
|
|
}
|
|
function BeginHealing()
|
|
{
|
|
MonsterController(Controller).WhatToDoNext(55);
|
|
}
|
|
|
|
state Healing // Healing
|
|
{
|
|
function bool ShouldChargeFromDamage()
|
|
{
|
|
return false;
|
|
}
|
|
Begin:
|
|
Sleep(GetAnimDuration('Heal'));
|
|
GoToState('');
|
|
}
|
|
state KnockDown // Knocked
|
|
{
|
|
function bool ShouldChargeFromDamage()
|
|
{
|
|
return false;
|
|
}
|
|
Begin:
|
|
if( Health > 0 )
|
|
{
|
|
Sleep(GetAnimDuration('KnockDown'));
|
|
CloakBoss();
|
|
PlaySound(sound'KF_EnemiesFinalSnd.Patriarch.Kev_SaveMe', SLOT_Misc, 2.0,,500.0);
|
|
if( KFGameType(Level.Game).FinalSquadNum == SyringeCount )
|
|
{
|
|
KFGameType(Level.Game).AddBossBuddySquad();
|
|
}
|
|
GotoState('Escaping');
|
|
}
|
|
else
|
|
{
|
|
GotoState('');
|
|
}
|
|
}
|
|
State Escaping extends Charging // Got hurt and running away...
|
|
{
|
|
function BeginHealing()
|
|
{
|
|
bShotAnim = true;
|
|
Acceleration = vect(0,0,0);
|
|
SetAnimAction('Heal');
|
|
HandleWaitForAnim('Heal');
|
|
|
|
GoToState('Healing');
|
|
}
|
|
function RangedAttack(Actor A)
|
|
{
|
|
if ( bShotAnim )
|
|
return;
|
|
else if ( IsCloseEnuf(A) )
|
|
{
|
|
if( bCloaked )
|
|
UnCloakBoss();
|
|
bShotAnim = true;
|
|
Acceleration = vect(0,0,0);
|
|
Acceleration = (A.Location-Location);
|
|
SetAnimAction('MeleeClaw');
|
|
//PlaySound(sound'Claw2s', SLOT_none); Claw2s
|
|
}
|
|
}
|
|
function bool MeleeDamageTarget(int hitdamage, vector pushdir)
|
|
{
|
|
return Global.MeleeDamageTarget(hitdamage, pushdir*1.5);
|
|
}
|
|
function Tick( float Delta )
|
|
{
|
|
|
|
// Keep the flesh pound moving toward its target when attacking
|
|
if( Role == ROLE_Authority && bShotAnim)
|
|
{
|
|
if( bChargingPlayer )
|
|
{
|
|
bChargingPlayer = false;
|
|
if( Level.NetMode!=NM_DedicatedServer )
|
|
PostNetReceive();
|
|
}
|
|
SetGroundSpeed(GetOriginalGroundSpeed());
|
|
}
|
|
else
|
|
{
|
|
if( !bChargingPlayer )
|
|
{
|
|
bChargingPlayer = true;
|
|
if( Level.NetMode!=NM_DedicatedServer )
|
|
PostNetReceive();
|
|
}
|
|
|
|
// Zapping slows him down, but doesn't stop him
|
|
if( bZapped )
|
|
{
|
|
SetGroundSpeed(OriginalGroundSpeed * 1.5);
|
|
}
|
|
else
|
|
{
|
|
SetGroundSpeed(OriginalGroundSpeed * 2.5);
|
|
}
|
|
}
|
|
|
|
Global.Tick(Delta);
|
|
}
|
|
function EndState()
|
|
{
|
|
SetGroundSpeed(GetOriginalGroundSpeed());
|
|
bChargingPlayer = False;
|
|
if( Level.NetMode!=NM_DedicatedServer )
|
|
PostNetReceive();
|
|
if( bCloaked )
|
|
UnCloakBoss();
|
|
}
|
|
Begin:
|
|
While( true )
|
|
{
|
|
Sleep(0.5);
|
|
if( !bCloaked && !bShotAnim )
|
|
CloakBoss();
|
|
if( !Controller.IsInState('SyrRetreat') && !Controller.IsInState('WaitForAnim'))
|
|
Controller.GoToState('SyrRetreat');
|
|
}
|
|
}
|
|
State SneakAround extends Escaping // Attempt to sneak around.
|
|
{
|
|
function BeginHealing()
|
|
{
|
|
MonsterController(Controller).WhatToDoNext(56);
|
|
GoToState('');
|
|
}
|
|
function bool MeleeDamageTarget(int hitdamage, vector pushdir)
|
|
{
|
|
local bool RetVal;
|
|
|
|
RetVal = super.MeleeDamageTarget(hitdamage, pushdir);
|
|
|
|
GoToState('');
|
|
return RetVal;
|
|
}
|
|
function BeginState()
|
|
{
|
|
super.BeginState();
|
|
SneakStartTime = Level.TimeSeconds;
|
|
}
|
|
function EndState()
|
|
{
|
|
super.EndState();
|
|
LastSneakedTime = Level.TimeSeconds;
|
|
}
|
|
|
|
Begin:
|
|
CloakBoss();
|
|
While( true )
|
|
{
|
|
Sleep(0.5);
|
|
|
|
if( Level.TimeSeconds - SneakStartTime > 10.0 )
|
|
{
|
|
GoToState('');
|
|
}
|
|
|
|
if( !bCloaked && !bShotAnim )
|
|
CloakBoss();
|
|
if( !Controller.IsInState('ZombieHunt') && !Controller.IsInState('WaitForAnim') )
|
|
{
|
|
Controller.GoToState('ZombieHunt');
|
|
}
|
|
}
|
|
}
|
|
State InitialSneak extends SneakAround // Sneak attack the players straight off the bat.
|
|
{
|
|
Begin:
|
|
CloakBoss();
|
|
While( true )
|
|
{
|
|
Sleep(0.5);
|
|
SneakCount++;
|
|
|
|
// Added sneakcount hack to try and fix the endless loop crash. Try and track down what was causing this later - Ramm
|
|
if( SneakCount > 1000 || (Controller != none && NiceZombieBossController(Controller).bAlreadyFoundEnemy) )
|
|
{
|
|
GoToState('');
|
|
}
|
|
|
|
if( !bCloaked && !bShotAnim )
|
|
CloakBoss();
|
|
if( !Controller.IsInState('InitialHunting') && !Controller.IsInState('WaitForAnim') )
|
|
{
|
|
Controller.GoToState('InitialHunting');
|
|
}
|
|
}
|
|
}
|
|
simulated function DropNeedle()
|
|
{
|
|
if( CurrentNeedle!=none )
|
|
{
|
|
DetachFromBone(CurrentNeedle);
|
|
CurrentNeedle.SetLocation(GetBoneCoords('Rpalm_MedAttachment').Origin);
|
|
CurrentNeedle.DroppedNow();
|
|
CurrentNeedle = none;
|
|
}
|
|
}
|
|
simulated function NotifySyringeA()
|
|
{
|
|
//log("Heal Part 1");
|
|
if( Level.NetMode!=NM_Client )
|
|
{
|
|
if( SyringeCount<3 )
|
|
SyringeCount++;
|
|
if( Level.NetMode!=NM_DedicatedServer )
|
|
PostNetReceive();
|
|
}
|
|
if( Level.NetMode!=NM_DedicatedServer )
|
|
{
|
|
DropNeedle();
|
|
CurrentNeedle = Spawn(Class'NiceBossHPNeedle');
|
|
AttachToBone(CurrentNeedle,'Rpalm_MedAttachment');
|
|
}
|
|
}
|
|
function NotifySyringeB()
|
|
{
|
|
//log("Heal Part 2");
|
|
if( Level.NetMode != NM_Client )
|
|
{
|
|
if(bOnFire)
|
|
Health += HealingAmount / 2;
|
|
else
|
|
Health += HealingAmount;
|
|
RemoveFlamingEffects();
|
|
StopBurnFX();
|
|
bOnFire = false;
|
|
bHealed = true;
|
|
FlameFuel = InitFlameFuel;
|
|
}
|
|
}
|
|
simulated function NotifySyringeC()
|
|
{
|
|
//log("Heal Part 3");
|
|
if( Level.NetMode!=NM_DedicatedServer && CurrentNeedle!=none )
|
|
{
|
|
CurrentNeedle.Velocity = vect(-45,300,-90) >> Rotation;
|
|
DropNeedle();
|
|
}
|
|
}
|
|
simulated function PostNetReceive()
|
|
{
|
|
if( bClientMiniGunning != bMinigunning )
|
|
{
|
|
bClientMiniGunning = bMinigunning;
|
|
// Hack so Patriarch won't go out of MG Firing to play his idle anim online
|
|
if( bMinigunning )
|
|
{
|
|
IdleHeavyAnim='FireMG';
|
|
IdleRifleAnim='FireMG';
|
|
IdleCrouchAnim='FireMG';
|
|
IdleWeaponAnim='FireMG';
|
|
IdleRestAnim='FireMG';
|
|
}
|
|
else
|
|
{
|
|
IdleHeavyAnim='BossIdle';
|
|
IdleRifleAnim='BossIdle';
|
|
IdleCrouchAnim='BossIdle';
|
|
IdleWeaponAnim='BossIdle';
|
|
IdleRestAnim='BossIdle';
|
|
}
|
|
}
|
|
if( bClientCharg!=bChargingPlayer )
|
|
{
|
|
bClientCharg = bChargingPlayer;
|
|
if (bChargingPlayer)
|
|
{
|
|
MovementAnims[0] = ChargingAnim;
|
|
MovementAnims[1] = ChargingAnim;
|
|
MovementAnims[2] = ChargingAnim;
|
|
MovementAnims[3] = ChargingAnim;
|
|
}
|
|
else if( !bChargingPlayer )
|
|
{
|
|
MovementAnims[0] = default.MovementAnims[0];
|
|
MovementAnims[1] = default.MovementAnims[1];
|
|
MovementAnims[2] = default.MovementAnims[2];
|
|
MovementAnims[3] = default.MovementAnims[3];
|
|
}
|
|
}
|
|
else if( ClientSyrCount!=SyringeCount )
|
|
{
|
|
ClientSyrCount = SyringeCount;
|
|
Switch( SyringeCount )
|
|
{
|
|
Case 1:
|
|
SetBoneScale(3,0,'Syrange1');
|
|
Break;
|
|
Case 2:
|
|
SetBoneScale(3,0,'Syrange1');
|
|
SetBoneScale(4,0,'Syrange2');
|
|
Break;
|
|
Case 3:
|
|
SetBoneScale(3,0,'Syrange1');
|
|
SetBoneScale(4,0,'Syrange2');
|
|
SetBoneScale(5,0,'Syrange3');
|
|
Break;
|
|
Default: // WTF? reset...?
|
|
SetBoneScale(3,1,'Syrange1');
|
|
SetBoneScale(4,1,'Syrange2');
|
|
SetBoneScale(5,1,'Syrange3');
|
|
Break;
|
|
}
|
|
}
|
|
else if( TraceHitPos!=vect(0,0,0) )
|
|
{
|
|
AddTraceHitFX(TraceHitPos);
|
|
TraceHitPos = vect(0,0,0);
|
|
}
|
|
else if( bClientCloaked!=bCloaked )
|
|
{
|
|
bClientCloaked = bCloaked;
|
|
bCloaked = !bCloaked;
|
|
if( bCloaked )
|
|
UnCloakBoss();
|
|
else CloakBoss();
|
|
bCloaked = bClientCloaked;
|
|
}
|
|
}
|
|
simulated function int DoAnimAction( name AnimName )
|
|
{
|
|
if( AnimName=='MeleeImpale' || AnimName=='MeleeClaw' || AnimName=='transition' /*|| AnimName=='FireMG'*/ )
|
|
{
|
|
AnimBlendParams(1, 1.0, 0.0,, SpineBone1);
|
|
PlayAnim(AnimName,, 0.1, 1);
|
|
Return 1;
|
|
}
|
|
else if( AnimName=='RadialAttack' )
|
|
{
|
|
// Get rid of blending, this is a full body anim
|
|
AnimBlendParams(1, 0.0);
|
|
PlayAnim(AnimName,,0.1);
|
|
return 0;
|
|
}
|
|
Return Super.DoAnimAction(AnimName);
|
|
}
|
|
|
|
simulated event SetAnimAction(name NewAction)
|
|
{
|
|
local int meleeAnimIndex;
|
|
if( NewAction=='' )
|
|
Return;
|
|
if(NewAction == 'Claw')
|
|
{
|
|
meleeAnimIndex = Rand(3);
|
|
NewAction = meleeAnims[meleeAnimIndex];
|
|
}
|
|
ExpectingChannel = DoAnimAction(NewAction);
|
|
if( Controller != none )
|
|
{
|
|
NiceZombieBossController(Controller).AnimWaitChannel = ExpectingChannel;
|
|
}
|
|
if( AnimNeedsWait(NewAction) )
|
|
{
|
|
bWaitForAnim = true;
|
|
}
|
|
else
|
|
{
|
|
bWaitForAnim = false;
|
|
}
|
|
if( Level.NetMode!=NM_Client )
|
|
{
|
|
AnimAction = NewAction;
|
|
bResetAnimAct = True;
|
|
|
|
ResetAnimActTime = Level.TimeSeconds+0.3;
|
|
}
|
|
}
|
|
// Hand sending the controller to the WaitForAnim state
|
|
simulated function HandleWaitForAnim( name NewAnim )
|
|
{
|
|
local float RageAnimDur;
|
|
Controller.GoToState('WaitForAnim');
|
|
RageAnimDur = GetAnimDuration(NewAnim);
|
|
NiceZombieBossController(Controller).SetWaitForAnimTimout(RageAnimDur,NewAnim);
|
|
}
|
|
// The animation is full body and should set the bWaitForAnim flag
|
|
simulated function bool AnimNeedsWait(name TestAnim)
|
|
{
|
|
if( /*TestAnim == 'MeleeImpale' || TestAnim =='MeleeClaw' || TestAnim =='transition' ||*/ TestAnim == 'FireMG' ||
|
|
TestAnim == 'PreFireMG' || TestAnim == 'PreFireMissile' || TestAnim == 'FireEndMG'|| TestAnim == 'FireEndMissile' ||
|
|
TestAnim == 'Heal' || TestAnim == 'KnockDown' || TestAnim == 'Entrance' || TestAnim == 'VictoryLaugh' || TestAnim == 'RadialAttack' )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
simulated function HandleBumpGlass()
|
|
{
|
|
}
|
|
|
|
function bool FlipOver()
|
|
{
|
|
Return False;
|
|
}
|
|
// Return true if we want to charge from taking too much damage
|
|
function bool ShouldChargeFromDamage()
|
|
{
|
|
// If we don;t want to heal, charge whoever damaged us!!!
|
|
if( (SyringeCount==0 && Health<HealingLevels[0]) || (SyringeCount==1 && Health<HealingLevels[1]) || (SyringeCount==2 && Health<HealingLevels[2]) )
|
|
{
|
|
return false;
|
|
}
|
|
else if( !bChargingPlayer && Level.TimeSeconds - LastForceChargeTime > (5.0 + 5.0 * FRand()) )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function TakeDamageClient(int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class<NiceWeaponDamageType> damageType, float headshotLevel, float lockonTime){
|
|
local float DamagerDistSq;
|
|
//local float UsedPipeBombDamScale;
|
|
local KFHumanPawn P;
|
|
local int NumPlayersSurrounding;
|
|
local bool bDidRadialAttack;
|
|
if( Level.TimeSeconds - LastMeleeExploitCheckTime > 1.0 && (class<NiceDamageTypeVetBerserker>(damageType) != none) )
|
|
{
|
|
LastMeleeExploitCheckTime = Level.TimeSeconds;
|
|
NumLumberJacks = 0;
|
|
NumNinjas = 0;
|
|
|
|
foreach DynamicActors(class'KFHumanPawn', P)
|
|
{
|
|
// look for guys attacking us within 3 meters
|
|
if ( VSize(P.Location - Location) < 150 )
|
|
{
|
|
NumPlayersSurrounding++;
|
|
|
|
if( P != none && P.Weapon != none )
|
|
{
|
|
if( Axe(P.Weapon) != none || Chainsaw(P.Weapon) != none )
|
|
{
|
|
NumLumberJacks++;
|
|
}
|
|
else if( Katana(P.Weapon) != none )
|
|
{
|
|
NumNinjas++;
|
|
}
|
|
}
|
|
|
|
if( !bDidRadialAttack && NumPlayersSurrounding >= 3 )
|
|
{
|
|
bDidRadialAttack = true;
|
|
GotoState('RadialAttack');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Scale damage from the pipebomb down a bit if lots of pipe bomb damage happens
|
|
// at around the same times. Prevent players from putting all thier pipe bombs
|
|
// in one place and owning the patriarch in one blow.
|
|
/*if ( class<NiceDamTypePipeBomb>(damageType) != none )
|
|
{
|
|
UsedPipeBombDamScale = FMax(0,(1.0 - PipeBombDamageScale));
|
|
|
|
PipeBombDamageScale += 0.075;
|
|
|
|
if( PipeBombDamageScale > 1.0 )
|
|
{
|
|
PipeBombDamageScale = 1.0;
|
|
}
|
|
|
|
Damage *= UsedPipeBombDamScale;
|
|
}*/
|
|
Super.TakeDamageClient(Damage,instigatedBy,hitlocation,Momentum,damageType,headshotLevel,lockonTime);
|
|
if( Level.TimeSeconds - LastDamageTime > 10 )
|
|
{
|
|
ChargeDamage = 0;
|
|
}
|
|
else
|
|
{
|
|
LastDamageTime = Level.TimeSeconds;
|
|
ChargeDamage += Damage;
|
|
}
|
|
if( ShouldChargeFromDamage() && ChargeDamage > 200 )
|
|
{
|
|
// If someone close up is shooting us, just charge them
|
|
if( InstigatedBy != none )
|
|
{
|
|
DamagerDistSq = VSizeSquared(Location - InstigatedBy.Location);
|
|
|
|
if( DamagerDistSq < (700 * 700) )
|
|
{
|
|
SetAnimAction('transition');
|
|
ChargeDamage=0;
|
|
LastForceChargeTime = Level.TimeSeconds;
|
|
GoToState('Charging');
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if( Health<=0 || SyringeCount==3 || IsInState('Escaping') || IsInState('KnockDown') || IsInState('RadialAttack') || bDidRadialAttack/*|| bShotAnim*/ )
|
|
Return;
|
|
if( (SyringeCount==0 && Health<HealingLevels[0]) || (SyringeCount==1 && Health<HealingLevels[1]) || (SyringeCount==2 && Health<HealingLevels[2]) )
|
|
{
|
|
//log(GetStateName()$" Took damage and want to heal!!! Health="$Health$" HealingLevels "$HealingLevels[SyringeCount]);
|
|
|
|
bShotAnim = true;
|
|
Acceleration = vect(0,0,0);
|
|
SetAnimAction('KnockDown');
|
|
HandleWaitForAnim('KnockDown');
|
|
KFMonsterController(Controller).bUseFreezeHack = True;
|
|
GoToState('KnockDown');
|
|
}
|
|
}
|
|
function DoorAttack(Actor A)
|
|
{
|
|
if ( bShotAnim )
|
|
return;
|
|
else if ( A!=none )
|
|
{
|
|
Controller.Target = A;
|
|
bShotAnim = true;
|
|
Acceleration = vect(0,0,0);
|
|
SetAnimAction('PreFireMissile');
|
|
HandleWaitForAnim('PreFireMissile');
|
|
GoToState('FireMissile');
|
|
}
|
|
}
|
|
function RemoveHead();
|
|
function PlayDirectionalHit(Vector HitLoc);
|
|
function bool SameSpeciesAs(Pawn P)
|
|
{
|
|
return False;
|
|
}
|
|
// Creapy endgame camera when the evil wins.
|
|
function bool SetBossLaught()
|
|
{
|
|
local Controller C;
|
|
GoToState('');
|
|
bShotAnim = true;
|
|
Acceleration = vect(0,0,0);
|
|
SetAnimAction('VictoryLaugh');
|
|
HandleWaitForAnim('VictoryLaugh');
|
|
bIsBossView = True;
|
|
bSpecialCalcView = True;
|
|
For( C=Level.ControllerList; C!=none; C=C.NextController )
|
|
{
|
|
if( PlayerController(C)!=none )
|
|
{
|
|
PlayerController(C).SetViewTarget(Self);
|
|
PlayerController(C).ClientSetViewTarget(Self);
|
|
PlayerController(C).ClientSetBehindView(True);
|
|
}
|
|
}
|
|
Return True;
|
|
}
|
|
simulated function bool SpectatorSpecialCalcView(PlayerController Viewer, out Actor ViewActor, out vector CameraLocation, out rotator CameraRotation)
|
|
{
|
|
Viewer.bBehindView = True;
|
|
ViewActor = Self;
|
|
CameraRotation.Yaw = Rotation.Yaw-32768;
|
|
CameraRotation.Pitch = 0;
|
|
CameraRotation.Roll = Rotation.Roll;
|
|
CameraLocation = Location + (vect(80,0,80) >> Rotation);
|
|
Return True;
|
|
}
|
|
// Overridden to do a cool slomo death view of the patriarch dying
|
|
function Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
|
|
{
|
|
local Controller C;
|
|
super.Died(Killer,damageType,HitLocation);
|
|
KFGameType(Level.Game).DoBossDeath();
|
|
For( C=Level.ControllerList; C!=none; C=C.NextController )
|
|
{
|
|
if( PlayerController(C)!=none )
|
|
{
|
|
PlayerController(C).SetViewTarget(Self);
|
|
PlayerController(C).ClientSetViewTarget(Self);
|
|
PlayerController(C).bBehindView = true;
|
|
PlayerController(C).ClientSetBehindView(True);
|
|
}
|
|
}
|
|
}
|
|
function ClawDamageTarget()
|
|
{
|
|
local vector PushDir;
|
|
local name Anim;
|
|
local float frame,rate;
|
|
local float UsedMeleeDamage;
|
|
local bool bDamagedSomeone;
|
|
local KFHumanPawn P;
|
|
local Actor OldTarget;
|
|
if( MeleeDamage > 1 )
|
|
{
|
|
UsedMeleeDamage = (MeleeDamage - (MeleeDamage * 0.05)) + (MeleeDamage * (FRand() * 0.1));
|
|
}
|
|
else
|
|
{
|
|
UsedMeleeDamage = MeleeDamage;
|
|
}
|
|
GetAnimParams(1, Anim,frame,rate);
|
|
if( Anim == 'MeleeImpale' )
|
|
{
|
|
MeleeRange = ImpaleMeleeDamageRange;
|
|
}
|
|
else
|
|
{
|
|
MeleeRange = ClawMeleeDamageRange;
|
|
}
|
|
if(Controller!=none && Controller.Target!=none)
|
|
PushDir = (damageForce * Normal(Controller.Target.Location - Location));
|
|
else
|
|
PushDir = damageForce * vector(Rotation);
|
|
// Begin Balance Round 1(damages everyone in Round 2 and added seperate code path for MeleeImpale in Round 3)
|
|
if( Anim == 'MeleeImpale' )
|
|
{
|
|
bDamagedSomeone = MeleeDamageTarget(UsedMeleeDamage, PushDir);
|
|
}
|
|
else
|
|
{
|
|
OldTarget = Controller.Target;
|
|
|
|
foreach DynamicActors(class'KFHumanPawn', P)
|
|
{
|
|
if ( (P.Location - Location) dot PushDir > 0.0 ) // Added dot Product check in Balance Round 3
|
|
{
|
|
Controller.Target = P;
|
|
bDamagedSomeone = bDamagedSomeone || MeleeDamageTarget(UsedMeleeDamage, damageForce * Normal(P.Location - Location)); // Always pushing players away added in Balance Round 3
|
|
}
|
|
}
|
|
|
|
Controller.Target = OldTarget;
|
|
}
|
|
MeleeRange = Default.MeleeRange;
|
|
// End Balance Round 1, 2, and 3
|
|
if ( bDamagedSomeone )
|
|
{
|
|
if( Anim == 'MeleeImpale' )
|
|
{
|
|
PlaySound(MeleeImpaleHitSound, SLOT_Interact, 2.0);
|
|
}
|
|
else
|
|
{
|
|
PlaySound(MeleeAttackHitSound, SLOT_Interact, 2.0);
|
|
}
|
|
}
|
|
}
|
|
simulated function ProcessHitFX()
|
|
{
|
|
local Coords boneCoords;
|
|
local class<xEmitter> HitEffects[4];
|
|
local int i,j;
|
|
local float GibPerterbation;
|
|
if( (Level.NetMode == NM_DedicatedServer) || bSkeletized || (Mesh == SkeletonMesh))
|
|
{
|
|
SimHitFxTicker = HitFxTicker;
|
|
return;
|
|
}
|
|
for ( SimHitFxTicker = SimHitFxTicker; SimHitFxTicker != HitFxTicker; SimHitFxTicker = (SimHitFxTicker + 1) % ArrayCount(HitFX) )
|
|
{
|
|
j++;
|
|
if ( j > 30 )
|
|
{
|
|
SimHitFxTicker = HitFxTicker;
|
|
return;
|
|
}
|
|
|
|
if( (HitFX[SimHitFxTicker].damtype == none) || (Level.bDropDetail && (Level.TimeSeconds - LastRenderTime > 3) && !IsHumanControlled()) )
|
|
continue;
|
|
|
|
//log("Processing effects for damtype "$HitFX[SimHitFxTicker].damtype);
|
|
|
|
if( HitFX[SimHitFxTicker].bone == 'obliterate' && !class'GameInfo'.static.UseLowGore())
|
|
{
|
|
SpawnGibs( HitFX[SimHitFxTicker].rotDir, 1);
|
|
bGibbed = true;
|
|
// Wait a tick on a listen server so the obliteration can replicate before the pawn is destroyed
|
|
if( Level.NetMode == NM_ListenServer )
|
|
{
|
|
bDestroyNextTick = true;
|
|
TimeSetDestroyNextTickTime = Level.TimeSeconds;
|
|
}
|
|
else
|
|
{
|
|
Destroy();
|
|
}
|
|
return;
|
|
}
|
|
|
|
boneCoords = GetBoneCoords( HitFX[SimHitFxTicker].bone );
|
|
|
|
if ( !Level.bDropDetail && !class'GameInfo'.static.NoBlood() && !bSkeletized && !class'GameInfo'.static.UseLowGore() )
|
|
{
|
|
//AttachEmitterEffect( BleedingEmitterClass, HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir );
|
|
|
|
HitFX[SimHitFxTicker].damtype.static.GetHitEffects( HitEffects, Health );
|
|
|
|
if( !PhysicsVolume.bWaterVolume ) // don't attach effects under water
|
|
{
|
|
for( i = 0; i < ArrayCount(HitEffects); i++ )
|
|
{
|
|
if( HitEffects[i] == none )
|
|
continue;
|
|
|
|
AttachEffect( HitEffects[i], HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir );
|
|
}
|
|
}
|
|
}
|
|
if ( class'GameInfo'.static.UseLowGore() )
|
|
HitFX[SimHitFxTicker].bSever = false;
|
|
|
|
if( HitFX[SimHitFxTicker].bSever )
|
|
{
|
|
GibPerterbation = HitFX[SimHitFxTicker].damtype.default.GibPerterbation;
|
|
|
|
switch( HitFX[SimHitFxTicker].bone )
|
|
{
|
|
case 'obliterate':
|
|
break;
|
|
|
|
case LeftThighBone:
|
|
if( !bLeftLegGibbed )
|
|
{
|
|
SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) );
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
|
|
bLeftLegGibbed=true;
|
|
}
|
|
break;
|
|
|
|
case RightThighBone:
|
|
if( !bRightLegGibbed )
|
|
{
|
|
SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) );
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
|
|
bRightLegGibbed=true;
|
|
}
|
|
break;
|
|
|
|
case LeftFArmBone:
|
|
if( !bLeftArmGibbed )
|
|
{
|
|
SpawnSeveredGiblet( DetachedSpecialArmClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) );
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;;
|
|
bLeftArmGibbed=true;
|
|
}
|
|
break;
|
|
|
|
case RightFArmBone:
|
|
if( !bRightArmGibbed )
|
|
{
|
|
SpawnSeveredGiblet( DetachedArmClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) );
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
|
|
bRightArmGibbed=true;
|
|
}
|
|
break;
|
|
|
|
case 'head':
|
|
if( !bHeadGibbed )
|
|
{
|
|
if ( HitFX[SimHitFxTicker].damtype == class'DamTypeDecapitation' )
|
|
{
|
|
DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false);
|
|
}
|
|
else if( HitFX[SimHitFxTicker].damtype == class'DamTypeProjectileDecap' )
|
|
{
|
|
DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false, true);
|
|
}
|
|
else if( HitFX[SimHitFxTicker].damtype == class'DamTypeMeleeDecapitation' )
|
|
{
|
|
DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, true);
|
|
}
|
|
|
|
bHeadGibbed=true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if( HitFX[SimHitFXTicker].bone != 'Spine' && HitFX[SimHitFXTicker].bone != FireRootBone &&
|
|
HitFX[SimHitFXTicker].bone != 'head' && Health <=0 )
|
|
HideBone(HitFX[SimHitFxTicker].bone);
|
|
}
|
|
}
|
|
}
|
|
static simulated function PreCacheMaterials(LevelInfo myLevel)
|
|
{//should be derived and used.
|
|
/*
|
|
myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.gatling_cmb');
|
|
myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.gatling_env_cmb');
|
|
myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.gatling_D');
|
|
myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.PatGungoInvisible_cmb');
|
|
myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.patriarch_cmb');
|
|
myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.patriarch_env_cmb');
|
|
myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.patriarch_D');
|
|
myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.patriarch_invisible');
|
|
myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.patriarch_invisible_gun');
|
|
myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.patriarch_fizzle_FB');
|
|
myLevel.AddPrecacheMaterial(Texture'kf_fx_trip_t.Gore.Patriarch_Gore_Limbs_Diff');
|
|
myLevel.AddPrecacheMaterial(Texture'kf_fx_trip_t.Gore.Patriarch_Gore_Limbs_Spec');
|
|
*/
|
|
}
|
|
defaultproperties
|
|
{
|
|
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'
|
|
ControllerClass=Class'NicePack.NiceZombieBossController'
|
|
AmbientSound=Sound'KF_BasePatriarch.Idle.Kev_IdleLoop'
|
|
Mesh=SkeletalMesh'KF_Freaks_Trip.Patriarch_Freak'
|
|
Skins(0)=Combiner'KF_Specimens_Trip_T.gatling_cmb'
|
|
Skins(1)=Combiner'KF_Specimens_Trip_T.patriarch_cmb'
|
|
}
|