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

739 lines
21 KiB
Ucode

class ZombieBrute extends KFMonster;
#exec load obj file=ScrnZedPack_T.utx
#exec load obj file=ScrnZedPack_S.uax
#exec load obj file=ScrnZedPack_A.ukx
#exec OBJ LOAD FILE=KFWeaponSound.uax
var bool bChargingPlayer;
var bool bClientCharge;
var bool bFrustrated;
var int MaxRageCounter; // Maximum amount of players we can hit before calming down
var int RageCounter; // Decreases each time we successfully hit a player
var float RageSpeedTween;
var int TwoSecondDamageTotal;
var float LastDamagedTime;
var int RageDamageThreshold;
var int BlockHitsLanded; // Hits made while blocking or raging
var float BlockMeleeDmgMul; //Multiplier for melee damage taken, when Brute is blocking (no matter where the hit was landed)
var float HeadShotgunDmgMul; //Multiplier for shotgun damage taken into UNBLOCKED head
var float HeadBulletDmgMul; //Multiplier for non-sniper bullet damage taken into UNBLOCKED head
var name ChargingAnim;
var Sound RageSound;
// View shaking for players
var() vector ShakeViewRotMag;
var() vector ShakeViewRotRate;
var() float ShakeViewRotTime;
var() vector ShakeViewOffsetMag;
var() vector ShakeViewOffsetRate;
var() float ShakeViewOffsetTime;
var float PushForce;
var vector PushAdd; // Used to add additional height to push
var float RageDamageMul; // Multiplier for hit damage when raging
var float RageBumpDamage; // Damage done when we hit other specimens while raging
var float BlockAddScale; // Additional head scale when blocking
var bool bBlockedHS;
var bool bBlocking;
var bool bServerBlock;
var bool bClientBlock;
var float BlockDmgMul; // Multiplier for damage taken from blocked shots
var float BlockFireDmgMul;
var float BurnGroundSpeedMul; // Multiplier for ground speed when burning
replication
{
reliable if(Role == ROLE_Authority)
bChargingPlayer, bServerBlock;
}
simulated function PostNetBeginPlay()
{
super.PostNetBeginPlay();
EnableChannelNotify(1,1);
EnableChannelNotify(2,1);
AnimBlendParams(1, 1.0, 0.0,, SpineBone1);
}
function ServerRaiseBlock()
{
bServerBlock = true;
SetAnimAction('BlockLoop');
}
function ServerLowerBlock()
{
local name Sequence;
local float Frame, Rate;
bServerBlock = false;
GetAnimParams(1, Sequence, Frame, Rate);
if (Sequence == 'BlockLoop')
AnimStopLooping(1);
}
simulated function PostNetReceive()
{
local name Sequence;
local float Frame, Rate;
if(bClientCharge != bChargingPlayer)
{
bClientCharge = bChargingPlayer;
if (bChargingPlayer)
{
MovementAnims[0] = ChargingAnim;
MeleeAnims[0] = 'BruteRageAttack';
MeleeAnims[1] = 'BruteRageAttack';
MeleeAnims[2] = 'BruteRageAttack';
}
else
{
MovementAnims[0] = default.MovementAnims[0];
MeleeAnims[0] = default.MeleeAnims[0];
MeleeAnims[1] = default.MeleeAnims[1];
MeleeAnims[2] = default.MeleeAnims[2];
}
}
if (bClientBlock != bServerBlock)
{
bClientBlock = bServerBlock;
if (bClientBlock)
SetAnimAction('BlockLoop');
else
{
GetAnimParams(1, Sequence, Frame, Rate);
if (Sequence == 'BlockLoop')
AnimStopLooping(1);
}
}
}
simulated function Tick(float DeltaTime)
{
super.Tick(DeltaTime);
if (Role == ROLE_Authority)
{
// Lock to target when attacking (except on beginner!)
if (bShotAnim && Level.Game.GameDifficulty >= 2.0)
if (LookTarget != none)
Acceleration = AccelRate * Normal(LookTarget.Location - Location);
// Block according to rules
if (Role == ROLE_Authority && !bServerBlock && !bShotAnim)
if (Controller != none && Controller.Target != none)
ServerRaiseBlock();
}
}
// Override to always move when attacking
function RangedAttack(Actor A)
{
if (bShotAnim || Physics == PHYS_Swimming)
return;
else if (CanAttack(A))
{
if (bChargingPlayer)
SetAnimAction('AoeClaw');
else
{
if (Rand(BlockHitsLanded) < 1)
SetAnimAction('BlockClaw');
else
SetAnimAction('Claw');
}
bShotAnim = true;
return;
}
}
function bool IsHeadShot(vector Loc, vector Ray, float AdditionalScale)
{
local float D;
local float AddScale;
local bool bIsBlocking;
bBlockedHS = false;
if (bServerBlock && !IsTweening(1))
{
bIsBlocking = true;
AddScale = AdditionalScale + BlockAddScale;
}
else
AddScale = AdditionalScale + 1.0;
if (Super.IsHeadShot(Loc, Ray, AddScale))
{
if (bIsBlocking)
{
D = vector(Rotation) dot Ray;
if (-D > 0.20) {
bBlockedHS = true;
return false;
}
else
return true;
}
else
return true;
}
else
return false;
}
// Damage, which doen't make headshots, always does full damage to Brute
// Melee weapon resistance is applied only while blocking
function TakeDamage(int Damage, Pawn InstigatedBy, Vector HitLocation, Vector Momentum, class<DamageType> DamType, optional int HitIndex)
{
local bool bIsHeadShot;
local class<KFWeaponDamageType> KFDamType;
KFDamType = class<KFWeaponDamageType>(DamType);
// damage, which doen't make headshots, always does full damage to Brute -- PooSH
if (KFDamType != none && KFDamType.default.bCheckForHeadShots) {
bIsHeadShot = IsHeadShot(HitLocation, normal(Momentum), 1.0);
if (!bIsHeadShot && bBlockedHS)
{
if (class<KFProjectileWeaponDamageType>(DamType) != none)
PlaySound(class'MetalHitEmitter'.default.ImpactSounds[rand(3)],, 128);
else if (class<DamTypeChainsaw>(DamType) != none)
PlaySound(Sound'KF_ChainsawSnd.Chainsaw_Impact_Metal',, 128);
else if (class<DamTypeMelee>(DamType) != none)
PlaySound(Sound'KF_KnifeSnd.Knife_HitMetal',, 128);
if ( KFDamType.default.bDealBurningDamage && !KFDamType.default.bIsPowerWeapon )
Damage *= BlockFireDmgMul; // Fire damage isn't reduced as much, excluding TrenchGun
else
Damage *= BlockDmgMul; // Greatly reduce damage as we only hit the metal plating
}
else if ( bServerBlock && class<DamTypeMelee>(DamType) != none )
Damage *= BlockMeleeDmgMul; // Give Brute higher melee damage resistance, but apply it only if Brute is blocking
else if ( bIsHeadShot ) {
if ( KFDamType.default.bIsPowerWeapon )
Damage *= HeadShotgunDmgMul; // Give Brute's head resistance to stotguns
else if ( !KFDamType.default.bSniperWeapon && !KFDamType.default.bIsMeleeDamage )
Damage *= HeadBulletDmgMul; // Give damage bonus to commando and gunslinger weapons
}
}
// Record damage over 2-second frames
if (LastDamagedTime < Level.TimeSeconds)
{
TwoSecondDamageTotal = 0;
LastDamagedTime = Level.TimeSeconds + 2;
}
TwoSecondDamageTotal += Damage;
// If criteria is met make him rage
if (!bDecapitated && !bChargingPlayer && TwoSecondDamageTotal > RageDamageThreshold)
{
StartCharging();
if (InstigatedBy != None)
if (Controller.Target != InstigatedBy)
MonsterController(Controller).ChangeEnemy(InstigatedBy, Controller.CanSee(InstigatedBy));
}
if (InstigatedBy == none || KFDamType == none)
Super(Monster).TakeDamage(Damage, instigatedBy, hitLocation, momentum, DamType); // skip NONE-reference error
else
Super(KFMonster).TakeDamage(Damage, instigatedBy, hitLocation, momentum, DamType);
if (bDecapitated)
Died(InstigatedBy.Controller, DamType, HitLocation);
}
function TakeFireDamage(int Damage, Pawn Instigator)
{
Super.TakeFireDamage(Damage, Instigator);
// Adjust movement speed if not charging
if (!bChargingPlayer)
{
if (bBurnified)
GroundSpeed = GetOriginalGroundSpeed() * BurnGroundSpeedMul;
else
GroundSpeed = GetOriginalGroundSpeed();
}
}
function ClawDamageTarget()
{
local KFHumanPawn HumanTarget;
local float UsedMeleeDamage;
local Actor OldTarget;
local name Sequence;
local float Frame, Rate;
local bool bHitSomeone;
if (MeleeDamage > 1)
UsedMeleeDamage = (MeleeDamage - (MeleeDamage * 0.05)) + (MeleeDamage * (FRand() * 0.1));
else
UsedMeleeDamage = MeleeDamage;
GetAnimParams(1, Sequence, Frame, Rate);
if (Controller != none && Controller.Target != none)
{
if (Sequence == 'BruteRageAttack')
{
OldTarget = Controller.Target;
foreach VisibleCollidingActors(class'KFHumanPawn', HumanTarget, MeleeRange + class'KFHumanPawn'.default.CollisionRadius)
{
bHitSomeone = ClawDamageSingleTarget(UsedMeleeDamage, HumanTarget);
}
Controller.Target = OldTarget;
if (bHitSomeone)
BlockHitsLanded++;
}
else if (Sequence != 'BruteAttack1' && Sequence != 'BruteAttack2' && Sequence != 'DoorBash') // Block attack
{
bHitSomeone = ClawDamageSingleTarget(UsedMeleeDamage, Controller.Target);
if (bHitSomeone)
BlockHitsLanded++;
}
else
bHitSomeone = ClawDamageSingleTarget(UsedMeleeDamage, Controller.Target);
if (bHitSomeone)
PlaySound(MeleeAttackHitSound, SLOT_Interact, 1.25);
}
}
function bool ClawDamageSingleTarget(float UsedMeleeDamage, Actor ThisTarget)
{
local Pawn HumanTarget;
local KFPlayerController HumanTargetController;
local bool bHitSomeone;
local float EnemyAngle;
local vector PushForceVar;
EnemyAngle = Normal(ThisTarget.Location - Location) dot vector(Rotation);
if (EnemyAngle > 0)
{
Controller.Target = ThisTarget;
if (MeleeDamageTarget(UsedMeleeDamage, vect(0, 0, 0)))
{
HumanTarget = KFHumanPawn(ThisTarget);
if (HumanTarget != None)
{
EnemyAngle = (EnemyAngle * 0.5) + 0.5; // Players at sides get knocked back half as much
PushForceVar = (PushForce * Normal(HumanTarget.Location - Location) * EnemyAngle) + PushAdd;
if (!bChargingPlayer)
PushForceVar *= 0.85;
// (!) I'm sure the VeterancyName string is localized but I'm not sure of another way compatible with ServerPerks
if (KFPlayerReplicationInfo(HumanTarget.Controller.PlayerReplicationInfo).ClientVeteranSkill != none)
if (KFPlayerReplicationInfo(HumanTarget.Controller.PlayerReplicationInfo).ClientVeteranSkill
.default.VeterancyName == "Berserker")
PushForceVar *= 0.75;
if (!(HumanTarget.Physics == PHYS_WALKING || HumanTarget.Physics == PHYS_NONE))
PushForceVar *= vect(1, 1, 0); // (!) Don't throw upwards if we are not on the ground - adjust for more flexibility
HumanTarget.AddVelocity(PushForceVar);
HumanTargetController = KFPlayerController(HumanTarget.Controller);
if (HumanTargetController != None)
HumanTargetController.ShakeView(ShakeViewRotMag, ShakeViewRotRate, ShakeViewRotTime,
ShakeViewOffsetMag, ShakeViewOffsetRate, ShakeViewOffsetTime);
bHitSomeone = true;
}
}
}
return bHitSomeone;
}
function StartCharging()
{
// How many times should we hit before we cool down?
if (Level.Game.NumPlayers <= 3)
MaxRageCounter = 2;
else
MaxRageCounter = 3;
RageCounter = MaxRageCounter;
PlaySound(RageSound, SLOT_Talk, 255);
GotoState('RageCharging');
}
state RageCharging
{
Ignores StartCharging;
function PlayDirectionalHit(Vector HitLoc)
{
if (!bShotAnim)
super.PlayDirectionalHit(HitLoc);
}
function bool CanGetOutOfWay()
{
return false;
}
function bool CanSpeedAdjust()
{
return false;
}
function BeginState()
{
bFrustrated = false;
bChargingPlayer = true;
RageSpeedTween = 0.0;
if (Level.NetMode != NM_DedicatedServer)
ClientChargingAnims();
NetUpdateTime = Level.TimeSeconds - 1;
}
function EndState()
{
bChargingPlayer = false;
BruteZombieController(Controller).RageFrustrationTimer = 0;
if (Health > 0)
{
GroundSpeed = GetOriginalGroundSpeed();
if (bBurnified)
GroundSpeed *= BurnGroundSpeedMul;
}
if( Level.NetMode!=NM_DedicatedServer )
ClientChargingAnims();
NetUpdateTime = Level.TimeSeconds - 1;
}
function Tick(float Delta)
{
if (!bShotAnim)
{
RageSpeedTween = FClamp(RageSpeedTween + (Delta * 0.75), 0, 1.0);
GroundSpeed = OriginalGroundSpeed + ((OriginalGroundSpeed * 0.75 / MaxRageCounter * (RageCounter + 1) * RageSpeedTween));
if (bBurnified)
GroundSpeed *= BurnGroundSpeedMul;
}
Global.Tick(Delta);
}
function Bump(Actor Other)
{
local KFMonster KFM;
KFM = KFMonster(Other);
// Hurt enemies that we run into while raging
if (!bShotAnim && KFM != None && ZombieBrute(Other) == None && Pawn(Other).Health > 0)
Other.TakeDamage(RageBumpDamage, self, Other.Location, Velocity * Other.Mass, class'DamTypePoundCrushed');
else Global.Bump(Other);
}
function bool MeleeDamageTarget(int HitDamage, vector PushDir)
{
local bool DamDone, bWasEnemy;
bWasEnemy = (Controller.Target == Controller.Enemy);
DamDone = Super.MeleeDamageTarget(HitDamage * RageDamageMul, vect(0, 0, 0));
if (bWasEnemy && DamDone)
{
ChangeTarget();
CalmDown();
}
return DamDone;
}
function CalmDown()
{
RageCounter = FClamp(RageCounter - 1, 0, MaxRageCounter);
if (RageCounter == 0)
GotoState('');
}
function ChangeTarget()
{
local Controller C;
local Pawn BestPawn;
local float Dist, BestDist;
for (C = Level.ControllerList; C != none; C = C.NextController)
if (C.Pawn != none && KFHumanPawn(C.Pawn) != none)
{
Dist = VSize(C.Pawn.Location - Location);
if (C.Pawn == Controller.Target)
Dist += GroundSpeed * 4;
if (BestPawn == none)
{
BestPawn = C.Pawn;
BestDist = Dist;
}
else if (Dist < BestDist)
{
BestPawn = C.Pawn;
BestDist = Dist;
}
}
if (BestPawn != none && BestPawn != Controller.Enemy)
MonsterController(Controller).ChangeEnemy(BestPawn, Controller.CanSee(BestPawn));
}
}
// Override to prevent stunning
function bool FlipOver()
{
return false;
}
// Shouldn't fight with our own
function bool SameSpeciesAs(Pawn P)
{
return (ZombieBrute(P) != none);
}
// ------------------------------------------------------
// Animation --------------------------------------------
// ------------------------------------------------------
function PlayTakeHit(vector HitLocation, int Damage, class<DamageType> DamageType)
{
if (Level.TimeSeconds - LastPainAnim < MinTimeBetweenPainAnims)
return;
// Uncomment this if we want some damage to make him drop his block
/*if( !Controller.IsInState('WaitForAnim') && Damage >= 10 )
PlayDirectionalHit(HitLocation);*/
LastPainAnim = Level.TimeSeconds;
if (Level.TimeSeconds - LastPainSound < MinTimeBetweenPainSounds)
return;
LastPainSound = Level.TimeSeconds;
PlaySound(HitSound[0], SLOT_Pain,1.25,,400);
}
// Overridden to handle playing upper body only attacks when moving
simulated event SetAnimAction(name NewAction)
{
if (NewAction=='')
return;
if (NewAction == 'Claw')
{
NewAction = MeleeAnims[rand(2)];
CurrentDamType = ZombieDamType[0];
}
else if (NewAction == 'BlockClaw')
{
NewAction = 'BruteBlockSlam';
CurrentDamType = ZombieDamType[0];
}
else if (NewAction == 'AoeClaw')
{
NewAction = 'BruteRageAttack';
CurrentDamType = ZombieDamType[0];
}
else if (NewAction == 'DoorBash')
CurrentDamType = ZombieDamType[Rand(3)];
ExpectingChannel = DoAnimAction(NewAction);
if (AnimNeedsWait(NewAction))
bWaitForAnim = true;
else
bWaitForAnim = false;
if (Level.NetMode != NM_Client)
{
AnimAction = NewAction;
bResetAnimAct = True;
ResetAnimActTime = Level.TimeSeconds+0.3;
}
}
simulated function int DoAnimAction( name AnimName )
{
if (AnimName=='BruteAttack1' || AnimName=='BruteAttack2' || AnimName=='ZombieFireGun' || AnimName == 'DoorBash')
{
if (Role == ROLE_Authority)
ServerLowerBlock();
AnimBlendParams(1, 1.0, 0.0,, FireRootBone);
PlayAnim(AnimName,, 0.1, 1);
return 1;
}
else if (AnimName == 'BruteRageAttack')
{
if (Role == ROLE_Authority)
ServerLowerBlock();
AnimBlendParams(1, 1.0, 0.0,, FireRootBone);
PlayAnim(AnimName,, 0.1, 1);
return 1;
}
else if (AnimName == 'BlockLoop')
{
AnimBlendParams(1, 1.0, 0.0,, FireRootBone);
LoopAnim(AnimName,, 0.25, 1);
return 1;
}
else if (AnimName == 'BruteBlockSlam')
{
AnimBlendParams(2, 1.0, 0.0,, FireRootBone);
PlayAnim(AnimName,, 0.1, 2);
return 2;
}
return Super.DoAnimAction(AnimName);
}
// The animation is full body and should set the bWaitForAnim flag
simulated function bool AnimNeedsWait(name TestAnim)
{
if (TestAnim == 'DoorBash')
return true;
return false;
}
simulated function AnimEnd(int Channel)
{
local name Sequence;
local float Frame, Rate;
GetAnimParams(Channel, Sequence, Frame, Rate);
// Don't allow notification for a looping animation
if (Sequence == 'BlockLoop')
return;
// Disable channel 2 when we're done with it
if (Channel == 2 && Sequence == 'BruteBlockSlam')
{
AnimBlendParams(2, 0);
bShotAnim = false;
return;
}
Super.AnimEnd(Channel);
}
simulated function ClientChargingAnims()
{
PostNetReceive();
}
function PlayHit(float Damage, Pawn InstigatedBy, vector HitLocation, class<DamageType> damageType, vector Momentum, optional int HitIdx)
{
local Actor A;
if (bBlockedHS)
A = Spawn(class'BlockHitEmitter', InstigatedBy,, HitLocation, rotator(Normal(HitLocation - Location)));
else
Super.PlayHit(Damage, InstigatedBy, HitLocation, damageType, Momentum, HitIdx);
}
defaultproperties
{
RageDamageThreshold=50
BlockMeleeDmgMul=0.500000
HeadShotgunDmgMul=0.500000
HeadBulletDmgMul=1.250000
ChargingAnim="BruteRun"
RageSound=SoundGroup'ScrnZedPack_S.Brute.BruteRage'
ShakeViewRotMag=(X=500.000000,Y=500.000000,Z=600.000000)
ShakeViewRotRate=(X=12500.000000,Y=12500.000000,Z=12500.000000)
ShakeViewRotTime=6.000000
ShakeViewOffsetMag=(X=5.000000,Y=10.000000,Z=5.000000)
ShakeViewOffsetRate=(X=300.000000,Y=300.000000,Z=300.000000)
ShakeViewOffsetTime=3.500000
PushForce=860.000000
PushAdd=(Z=150.000000)
RageDamageMul=1.100000
RageBumpDamage=4.000000
BlockAddScale=2.500000
BlockDmgMul=0.100000
BlockFireDmgMul=1.000000
BurnGroundSpeedMul=0.600000
MeleeAnims(0)="BruteAttack1"
MeleeAnims(1)="BruteAttack2"
MeleeAnims(2)="BruteBlockSlam"
MoanVoice=SoundGroup'ScrnZedPack_S.Brute.BruteTalk'
BleedOutDuration=7.000000
ZombieFlag=3
MeleeDamage=20
damageForce=25000
bFatAss=True
KFRagdollName="FleshPound_Trip"
MeleeAttackHitSound=SoundGroup'ScrnZedPack_S.Brute.BruteHitPlayer'
JumpSound=SoundGroup'ScrnZedPack_S.Brute.BruteJump'
SpinDamConst=20.000000
SpinDamRand=20.000000
bMeleeStunImmune=True
bUseExtendedCollision=True
ColOffset=(Z=52.000000)
ColRadius=35.000000
ColHeight=25.000000
SeveredArmAttachScale=1.300000
SeveredLegAttachScale=1.200000
SeveredHeadAttachScale=1.500000
DetachedArmClass=Class'ScrnZedPack.SeveredArmBrute'
DetachedLegClass=Class'ScrnZedPack.SeveredLegBrute'
DetachedHeadClass=Class'ScrnZedPack.SeveredHeadBrute'
PlayerCountHealthScale=0.250000
OnlineHeadshotOffset=(X=22.000000,Z=68.000000)
OnlineHeadshotScale=1.300000
HeadHealth=210.000000
PlayerNumHeadHealthScale=0.150000
MotionDetectorThreat=5.000000
HitSound(0)=SoundGroup'ScrnZedPack_S.Brute.BrutePain'
DeathSound(0)=SoundGroup'ScrnZedPack_S.Brute.BruteDeath'
ChallengeSound(0)=SoundGroup'ScrnZedPack_S.Brute.BruteChallenge'
ChallengeSound(1)=SoundGroup'ScrnZedPack_S.Brute.BruteChallenge'
ChallengeSound(2)=SoundGroup'ScrnZedPack_S.Brute.BruteChallenge'
ChallengeSound(3)=SoundGroup'ScrnZedPack_S.Brute.BruteChallenge'
ScoringValue=60
IdleHeavyAnim="BruteIdle"
IdleRifleAnim="BruteIdle"
RagDeathUpKick=100.000000
MeleeRange=85.000000
GroundSpeed=140.000000
WaterSpeed=120.000000
HealthMax=1000.000000
Health=1000
HeadHeight=2.500000
HeadScale=1.300000
MenuName="Brute.se"
ControllerClass=Class'ScrnZedPack.BruteZombieController'
MovementAnims(0)="BruteWalkC"
MovementAnims(1)="BruteWalkC"
WalkAnims(0)="BruteWalkC"
WalkAnims(1)="BruteWalkC"
WalkAnims(2)="RunL"
WalkAnims(3)="RunR"
IdleCrouchAnim="BruteIdle"
IdleWeaponAnim="BruteIdle"
IdleRestAnim="BruteIdle"
AmbientSound=SoundGroup'ScrnZedPack_S.Brute.BruteIdle1Shot'
Mesh=SkeletalMesh'ScrnZedPack_A.BruteMesh'
PrePivot=(Z=0.000000)
Skins(0)=Combiner'ScrnZedPack_T.Brute.Brute_Final'
Mass=600.000000
RotationRate=(Yaw=45000,Roll=0)
}