NicePack/sources/Zeds/Nice/NiceZombieBoss.uc

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'
}