NicePack/sources/Zeds/Nice/NiceZombieShiver.uc
Anton Tarasenko 8f5511e899 Add attack delay to Shivers right after teleport
Now shivers cannot attack for 0.5 seconds after teleporting, which
should help with them getting cheap shots on players.
2024-10-29 03:17:30 +07:00

480 lines
15 KiB
Ucode

// Different naming scheme 'cause kf-scrntestingrounds has a stupid restriction on what zeds can be used in it's spawns
class NiceZombieShiver extends NiceZombieShiverBase;
var float TeleportBlockTime;
var float HeadOffsetY;
var transient bool bRunning, bClientRunning;
var float teleportAttackCooldownEndTime;
replication
{
reliable if ( Role == ROLE_Authority)
bRunning;
}
simulated function PostNetReceive()
{
super.PostNetReceive();
if( bClientRunning != bRunning )
{
bClientRunning = bRunning;
if( bRunning ) {
MovementAnims[0] = RunAnim;
}
else {
MovementAnims[0] = WalkAnim;
}
}
}
simulated function PostBeginPlay()
{
Super.PostBeginPlay();
if (Level.NetMode != NM_DedicatedServer)
{
MatAlphaSkin = ColorModifier(Level.ObjectPool.AllocateObject(class'ColorModifier'));
if (MatAlphaSkin != none)
{
MatAlphaSkin.Color = class'Canvas'.static.MakeColor(255, 255, 255, 255);
MatAlphaSkin.RenderTwoSided = false;
MatAlphaSkin.AlphaBlend = true;
MatAlphaSkin.Material = Skins[0];
Skins[0] = MatAlphaSkin;
}
}
}
function bool CanAttack(Actor target) {
if (level.timeSeconds < teleportAttackCooldownEndTime)
return false;
return super.CanAttack(target);
}
simulated event SetAnimAction(name NewAction) {
local int meleeAnimIndex;
if (bFrozenZed) return;
if (newAction == '') return;
if (newAction == 'Claw') {
meleeAnimIndex = Rand(2); // Shivers only have two animations now
newAction = meleeAnims[meleeAnimIndex];
currentDamtype = zombieDamType[meleeAnimIndex];
} 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 Destroyed()
{
if (Level.NetMode != NM_DedicatedServer && MatAlphaSkin != none)
{
Skins[0] = default.Skins[0];
Level.ObjectPool.FreeObject(MatAlphaSkin);
}
Super.Destroyed();
}
// Overridden to add HeadOffsetY
function bool IsHeadShot(vector loc, vector ray, float AdditionalScale)
{
local coords C;
local vector HeadLoc, B, M, diff;
local float t, DotMM, Distance;
local int look;
local bool bUseAltHeadShotLocation;
local bool bWasAnimating;
if (HeadBone == '')
return False;
// If we are a dedicated server estimate what animation is most likely playing on the client
if (Level.NetMode == NM_DedicatedServer)
{
if (Physics == PHYS_Falling)
PlayAnim(AirAnims[0], 1.0, 0.0);
else if (Physics == PHYS_Walking)
{
// Only play the idle anim if we're not already doing a different anim.
// This prevents anims getting interrupted on the server and borking things up - Ramm
if( !IsAnimating(0) && !IsAnimating(1) )
{
if (bIsCrouched)
{
PlayAnim(IdleCrouchAnim, 1.0, 0.0);
}
else
{
bUseAltHeadShotLocation=true;
}
}
else
{
bWasAnimating = true;
}
if ( bDoTorsoTwist )
{
SmoothViewYaw = Rotation.Yaw;
SmoothViewPitch = ViewPitch;
look = (256 * ViewPitch) & 65535;
if (look > 32768)
look -= 65536;
SetTwistLook(0, look);
}
}
else if (Physics == PHYS_Swimming)
PlayAnim(SwimAnims[0], 1.0, 0.0);
if( !bWasAnimating )
{
SetAnimFrame(0.5);
}
}
if( bUseAltHeadShotLocation )
{
HeadLoc = Location + (OnlineHeadshotOffset >> Rotation);
AdditionalScale *= OnlineHeadshotScale;
}
else
{
C = GetBoneCoords(HeadBone);
HeadLoc = C.Origin + (HeadHeight * HeadScale * AdditionalScale * C.XAxis)
+ HeadOffsetY * C.YAxis;
}
//ServerHeadLocation = HeadLoc;
// Express snipe trace line in terms of B + tM
B = loc;
M = ray * (2.0 * CollisionHeight + 2.0 * CollisionRadius);
// Find Point-Line Squared Distance
diff = HeadLoc - B;
t = M Dot diff;
if (t > 0)
{
DotMM = M dot M;
if (t < DotMM)
{
t = t / DotMM;
diff = diff - (t * M);
}
else
{
t = 1;
diff -= M;
}
}
else
t = 0;
Distance = Sqrt(diff Dot diff);
return (Distance < (HeadRadius * HeadScale * AdditionalScale));
}
function bool FlipOver()
{
if ( super.FlipOver() ) {
TeleportBlockTime = Level.TimeSeconds + 3.0; // can't teleport during stun
// do not rotate while stunned
Controller.Focus = none;
Controller.FocalPoint = Location + 512*vector(Rotation);
}
return false;
}
simulated function StopBurnFX()
{
if (bBurnApplied)
{
MatAlphaSkin.Material = Texture'PatchTex.Common.ZedBurnSkin';
Skins[0] = MatAlphaSkin;
}
Super.StopBurnFX();
}
function RangedAttack(Actor A)
{
if (bShotAnim || Physics == PHYS_Swimming)
return;
else if (CanAttack(A))
{
bShotAnim = true;
SetAnimAction('Claw');
return;
}
}
state Running
{
function Tick(float Delta)
{
Global.Tick(Delta);
if (RunUntilTime < Level.TimeSeconds)
GotoState('');
}
function BeginState()
{
bRunning = true;
RunUntilTime = Level.TimeSeconds + PeriodRunBase + FRand() * PeriodRunRan;
MovementAnims[0] = RunAnim;
}
function EndState()
{
bRunning = false;
RunCooldownEnd = Level.TimeSeconds + PeriodRunCoolBase + FRand() * PeriodRunCoolRan;
MovementAnims[0] = WalkAnim;
}
simulated function UpdateGroundSpeed() {
super.UpdateGroundSpeed();
groundSpeed *= 2.5;
}
function bool CanSpeedAdjust()
{
return false;
}
}
/*function TakeDamage(int Damage, Pawn InstigatedBy, Vector HitLocation, Vector Momentum, class<DamageType> DamType, optional int HitIndex)
{
if (InstigatedBy == none || class<KFWeaponDamageType>(DamType) == none)
Super(Monster).TakeDamage(Damage, instigatedBy, hitLocation, momentum, DamType); // skip none-reference error
else
Super(KFMonster).TakeDamage(Damage, instigatedBy, hitLocation, momentum, DamType);
}*/
// returns true also for KnockDown (stun) animation -- PooSH
simulated function bool AnimNeedsWait(name TestAnim)
{
if( TestAnim == 'DoorBash' || TestAnim == 'KnockDown' )
{
return true;
}
return ExpectingChannel == 0;
}
simulated function HandleAnimation(float Delta)
{
// hehehe
}
simulated function Tick(float Delta)
{
Super.Tick(Delta);
if (Health > 0 && !bBurnApplied)
{
if (Level.NetMode != NM_DedicatedServer)
HandleAnimation(Delta);
// Handle targetting
if (Level.NetMode != NM_Client && !bDecapitated)
{
if (Controller == none || Controller.Target == none || !Controller.LineOfSightTo(Controller.Target))
{
if (bCanSeeTarget) bCanSeeTarget = false;
}
else
{
if (!bCanSeeTarget)
{
bCanSeeTarget = true;
SeeTargetTime = Level.TimeSeconds;
}
else if (Level.TimeSeconds > SeeTargetTime + PeriodSeeTarget)
{
if (VSize(Controller.Target.Location - Location) < MaxTeleportDist)
{
if (VSize(Controller.Target.Location - Location) > MinTeleportDist || !Controller.ActorReachable(Controller.Target))
{
if (CanTeleport())
StartTelePort();
}
else
{
if (CanRun())
GotoState('Running');
}
}
}
}
}
}
// Handle client-side teleport variables
if (!bBurnApplied)
{
if (Level.NetMode != NM_DedicatedServer && OldFadeStage != FadeStage)
{
OldFadeStage = FadeStage;
if (FadeStage == 2)
AlphaFader = 0;
else
AlphaFader = 255;
}
// Handle teleporting
if (FadeStage == 1) // Fade out (pre-teleport)
{
AlphaFader = FMax(AlphaFader - Delta * 512, 0);
if (Level.NetMode != NM_Client && AlphaFader == 0)
{
SetCollision(true, true);
FlashTeleport();
SetCollision(true, false);
FadeStage = 2;
}
}
else if (FadeStage == 2) // Fade in (post-teleport)
{
AlphaFader = FMin(AlphaFader + Delta * 512, 255);
if (Level.NetMode != NM_Client && AlphaFader == 255)
{
FadeStage = 0;
SetCollision(true, true);
GotoState('Running');
}
}
if (Level.NetMode != NM_DedicatedServer && ColorModifier(Skins[0]) != none)
ColorModifier(Skins[0]).Color.A = AlphaFader;
}
}
//can't teleport if set on fire
function bool CanTeleport()
{
if (HeadHealth <= 0) return false;
return !bFlashTeleporting && !bOnFire && Physics == PHYS_Walking && Level.TimeSeconds > TeleportBlockTime
&& LastFlashTime + 7.5 < Level.TimeSeconds && !bIsStunned;
}
function bool CanRun()
{
local float distanceToTargetSquared;
if(controller == none) return false;
if(controller.focus != none){
distanceToTargetSquared = VSize(controller.focus.location - location);
if(distanceToTargetSquared > 900 * 2500) // (30 * 50)^2 / 30 meters
return false;
}
return (!bFlashTeleporting && !IsInState('Running') && RunCooldownEnd < Level.TimeSeconds);
}
function StartTeleport()
{
FadeStage = 1;
AlphaFader = 255;
SetCollision(true, false);
bFlashTeleporting = true;
}
function FlashTeleport()
{
local Actor Target;
local vector OldLoc;
local vector NewLoc;
local vector HitLoc;
local vector HitNorm;
local rotator RotOld;
local rotator RotNew;
local float LandTargetDist;
local int iEndAngle;
local int iAttempts;
if (Controller == none || Controller.Target == none)
return;
Target = Controller.Target;
RotOld = rotator(Target.Location - Location);
RotNew = RotOld;
OldLoc = Location;
for (iEndAngle = 0; iEndAngle < MaxTeleportAngles; iEndAngle++)
{
RotNew = RotOld;
RotNew.Yaw += iEndAngle * (65536 / MaxTelePortAngles);
for (iAttempts = 0; iAttempts < MaxTeleportAttempts; iAttempts++)
{
LandTargetDist = Target.CollisionRadius + CollisionRadius +
MinLandDist + (MaxLandDist - MinLandDist) * (iAttempts / (MaxTeleportAttempts - 1.0));
NewLoc = Target.Location - vector(RotNew) * LandTargetDist; // Target.Location - Location
NewLoc.Z = Target.Location.Z;
if (Trace(HitLoc, HitNorm, NewLoc + vect(0, 0, -500), NewLoc) != none)
NewLoc.Z = HitLoc.Z + CollisionHeight;
// Try a new location
if (SetLocation(NewLoc))
{
SetPhysics(PHYS_Walking);
if (Controller.PointReachable(Target.Location))
{
Velocity = vect(0, 0, 0);
Acceleration = vect(0, 0, 0);
SetRotation(rotator(Target.Location - Location));
PlaySound(Sound'ScrnZedPack_S.Shiver.ShiverWarpGroup', SLOT_Interact, 4.0);
Controller.GotoState('');
MonsterController(Controller).WhatToDoNext(0);
goto Teleported;
}
}
// Reset location
SetLocation(OldLoc);
}
}
Teleported:
bFlashTeleporting = false;
LastFlashTime = Level.TimeSeconds;
teleportAttackCooldownEndTime = level.timeSeconds + 0.5;
}
function Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
{
// (!)
Super.Died(Killer, damageType, HitLocation);
}
function RemoveHead()
{
local class<KFWeaponDamageType> KFDamType;
KFDamType = class<KFWeaponDamageType>(LastDamagedByType);
if ( KFDamType != none && !KFDamType.default.bIsPowerWeapon
&& !KFDamType.default.bSniperWeapon && !KFDamType.default.bIsMeleeDamage
&& !KFDamType.default.bIsExplosive && !KFDamType.default.bDealBurningDamage
&& !ClassIsChildOf(KFDamType, class'DamTypeDualies')
&& !ClassIsChildOf(KFDamType, class'DamTypeMK23Pistol')
&& !ClassIsChildOf(KFDamType, class'DamTypeMagnum44Pistol') )
{
LastDamageAmount *= 3.5; //significantly raise decapitation bonus for Assault Rifles
//award shiver kill on decap for Commandos
if ( KFPawn(LastDamagedBy)!=none && KFPlayerController(LastDamagedBy.Controller) != none
&& KFSteamStatsAndAchievements(KFPlayerController(LastDamagedBy.Controller).SteamStatsAndAchievements) != none )
{
KFDamType.Static.AwardKill(
KFSteamStatsAndAchievements(KFPlayerController(LastDamagedBy.Controller).SteamStatsAndAchievements),
KFPlayerController(LastDamagedBy.Controller), self);
}
}
if (IsInState('Running'))
GotoState('');
Super(NiceMonster).RemoveHead();
}
function bool CheckMiniFlinch(int flinchScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class<NiceWeaponDamageType> damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){
if(IsInState('Running'))
return false;
return super.CheckMiniFlinch(flinchScore, instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI);
}
simulated function int DoAnimAction( name AnimName )
{
if (AnimName=='Claw' || AnimName=='Claw2' || AnimName=='Claw3')
{
AnimBlendParams(1, 1.0, 0.1,, FireRootBone);
PlayAnim(AnimName,, 0.1, 1);
return 1;
}
return Super.DoAnimAction(AnimName);
}
defaultproperties
{
headOffsetY=-3.000000
idleInsertFrame=0.468000
onlineHeadshotOffset=(X=19.000000,Z=39.000000)
scoringValue=15
headRadius=8.000000
headHeight=3.000000
// Override third animation just in case.
// However it shouldn't be required since we've already changed
// `SetAnimAction` function to only use first two animations
meleeAnims(0)="Claw"
meleeAnims(1)="Claw2"
meleeAnims(2)="Claw"
}