498 lines
16 KiB
Ucode
498 lines
16 KiB
Ucode
class NiceZombieBrute extends NiceZombieBruteBase;
|
|
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
|
|
simulated function PostNetBeginPlay()
|
|
{
|
|
super.PostNetBeginPlay();
|
|
EnableChannelNotify(1,1);
|
|
EnableChannelNotify(2,1);
|
|
AnimBlendParams(1, 1.0, 0.0,, SpineBone1);
|
|
StartCharging();
|
|
}
|
|
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 && 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;
|
|
}
|
|
function bool CheckMiniFlinch( int flinchScore,
|
|
Pawn instigatedBy,
|
|
Vector hitLocation,
|
|
Vector momentum,
|
|
class<NiceWeaponDamageType> damageType,
|
|
float headshotLevel,
|
|
KFPlayerReplicationInfo KFPRI){
|
|
return false;
|
|
}
|
|
function TakeDamageClient(int Damage, Pawn InstigatedBy, Vector HitLocation, Vector Momentum, class<NiceWeaponDamageType> damageType, float headshotLevel, float lockonTime){
|
|
local float D;
|
|
local bool bIsHeadshot;
|
|
local bool bIsBlocking;
|
|
bBlockedHS = false;
|
|
if(bServerBlock && !IsTweening(1))
|
|
bIsBlocking = true;
|
|
if(headshotLevel > 0.0 && bIsBlocking){
|
|
D = vector(Rotation) dot Normal(Momentum);
|
|
D *= -1;
|
|
if(D > 0.30) {
|
|
bBlockedHS = true;
|
|
headshotLevel = 0.0;
|
|
}
|
|
}
|
|
bIsHeadShot = (headshotLevel > 0.0);
|
|
// damage, which doen't make headshots, always does full damage to Brute -- PooSH
|
|
if (damageType != none && damageType.default.bCheckForHeadShots) {
|
|
if (!bIsHeadShot && bBlockedHS)
|
|
{
|
|
if(damageType != none || damageType.default.bIsProjectile)
|
|
PlaySound(class'MetalHitEmitter'.default.ImpactSounds[rand(3)],, 128);
|
|
else if(class<NiceDamageTypeVetBerserker>(damageType) != none)
|
|
PlaySound(Sound'KF_KnifeSnd.Knife_HitMetal',, 128);
|
|
if(damageType.default.bDealBurningDamage && !damageType.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<NiceDamageTypeVetBerserker>(damageType) != none)
|
|
Damage *= BlockMeleeDmgMul; // Give Brute higher melee damage resistance, but apply it only if Brute is blocking
|
|
else if(bIsHeadShot){
|
|
if (damageType.default.bIsPowerWeapon)
|
|
Damage *= HeadShotgunDmgMul; // Give Brute's head resistance to stotguns
|
|
}
|
|
}
|
|
// 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();
|
|
Super(NiceMonster).TakeDamageClient(Damage, instigatedBy, hitLocation, momentum, damageType, headshotLevel, lockonTime);
|
|
if (bDecapitated)
|
|
Died(InstigatedBy.Controller, damageType, HitLocation);
|
|
}
|
|
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;
|
|
|
|
// fix log spam
|
|
if (Controller == none || ThisTarget == none)
|
|
return false;
|
|
|
|
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 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;
|
|
}
|
|
simulated function UpdateGroundSpeed() {
|
|
super.UpdateGroundSpeed();
|
|
groundSpeed = groundSpeed + ((groundSpeed * 0.75 / maxRageCounter * (rageCounter + 1) * rageSpeedTween));
|
|
}
|
|
function EndState()
|
|
{
|
|
bChargingPlayer = false;
|
|
|
|
NiceZombieBruteController(Controller).RageFrustrationTimer = 0;
|
|
|
|
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);
|
|
}
|
|
|
|
Global.Tick(Delta);
|
|
}
|
|
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(Controller == none)
|
|
return true;
|
|
|
|
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 true;
|
|
}
|
|
// Shouldn't fight with our own
|
|
function bool SameSpeciesAs(Pawn P)
|
|
{
|
|
return (NiceZombieBrute(P) != none);
|
|
}
|
|
// ------------------------------------------------------
|
|
// Animation --------------------------------------------
|
|
// ------------------------------------------------------
|
|
// 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)];
|
|
}
|
|
else if (NewAction == 'BlockClaw')
|
|
{
|
|
NewAction = 'BruteBlockSlam';
|
|
}
|
|
else if (NewAction == 'AoeClaw')
|
|
{
|
|
NewAction = 'BruteRageAttack';
|
|
}
|
|
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'NiceBlockHitEmitter', InstigatedBy,, HitLocation, rotator(Normal(HitLocation - Location)));
|
|
else
|
|
Super.PlayHit(Damage, InstigatedBy, HitLocation, damageType, Momentum, HitIdx);
|
|
}
|
|
defaultproperties
|
|
{
|
|
BlockMeleeDmgMul=1.000000
|
|
HeadShotgunDmgMul=1.000000
|
|
HeadBulletDmgMul=1.000000
|
|
stunLoopStart=0.130000
|
|
stunLoopEnd=0.650000
|
|
idleInsertFrame=0.950000
|
|
DetachedArmClass=Class'ScrnZedPack.SeveredArmBrute'
|
|
DetachedLegClass=Class'ScrnZedPack.SeveredLegBrute'
|
|
DetachedHeadClass=Class'ScrnZedPack.SeveredHeadBrute'
|
|
ControllerClass=class'NiceZombieBruteController'
|
|
}
|