4130 lines
122 KiB
Ucode
4130 lines
122 KiB
Ucode
// Base Zombie Class.
|
|
class KFMonster extends Skaarj
|
|
hidecategories(AnimTweaks,DeRes,Force,Gib,Karma,Udamage,UnrealPawn)
|
|
Abstract;
|
|
|
|
#exec OBJ LOAD FILE=KF_EnemyGlobalSndTwo.uax
|
|
#exec OBJ LOAD FILE=KFZED_Temp_UT.utx
|
|
#exec OBJ LOAD FILE=KFZED_FX_T.utx
|
|
|
|
var array<string> EventClasses;
|
|
|
|
/* reference to the ZombieVolume which spawned me .. */
|
|
var ZombieVolume SpawnVolume;
|
|
|
|
var name MeleeAnims[3];
|
|
var name HitAnims[3];
|
|
|
|
var(Sounds) sound MoanVoice; // The sound of this Monster randomly "talking"
|
|
var(Sounds) float MoanVolume;// The volume to use for the zombie moaning
|
|
|
|
var float NextBileTime, BileFrequency;
|
|
var int BileCount;
|
|
var Pawn BileInstigator;
|
|
var class<DamTypeVomit> LastBileDamagedByType;
|
|
|
|
var name KFHitFront;
|
|
var name KFHitBack;
|
|
var name KFHitLeft;
|
|
var name KFHitRight;
|
|
|
|
var(Karma) float RagMaxSpinAmount; // The max we'll scale up spin amount for locational hit damage
|
|
|
|
var int LookRotPitch,LookRotYaw;
|
|
var Pawn LookTarget;
|
|
|
|
var int HitMomentum;
|
|
|
|
var bool bRanged;
|
|
|
|
var() bool bStunImmune; // is the zombie immune to stun hit effects?
|
|
var bool bSTUNNED;
|
|
var float StunTime;
|
|
var int StunsRemaining; // Number of stuns that can ever play on one instance of this character(-1 is the default and is infinite)
|
|
var bool bDecapitated; // has he lost his noggin'!?
|
|
var int Gored; // Has he lost his whole torso?! if so, how much of it?
|
|
var bool DECAP;
|
|
var bool bBurnified;
|
|
var bool bBurnApplied;
|
|
var byte HeatAmount;
|
|
var() float BleedOutDuration; // How long this zombie survives after losing its head
|
|
var float BleedOutTime; // When this zombie will die from bleeding out
|
|
var bool bNoBrainBitEmitter; // We want to skip the brain bit emitter for this zed
|
|
|
|
var() bool bCannibal; // If true, this enemy will stop to eat corpses it finds.
|
|
|
|
var bool bZapped; // This zed has been zapped by the ZEDGun
|
|
var bool bOldZapped; // The last state of the bZapped flag
|
|
var float RemainingZap; // How much zap time this zed has left
|
|
var float TotalZap; // How much zap this zed has taken
|
|
var float LastZapTime; // The last time we recieved any "zap"
|
|
var() float ZapDuration; // How long a zap lasts
|
|
var() float ZappedSpeedMod; // How much to slow down zeds when they are zapped
|
|
var() float ZapThreshold; // How much time of being hit with zap before this zed get's "zapped"
|
|
var() float ZappedDamageMod; // How much to scale damage by when this zed is zapped
|
|
var() float ZapResistanceScale; // Every time the zed gets zapped, scale his resistance up by this modifier. This prevent zeds from getting constantly raped by zap (like the fleshpound)
|
|
var Pawn ZappedBy; //who did the zapping
|
|
|
|
var bool bHarpoonStunned; // This zed has been stunned by a harpoon
|
|
var bool bOldHarpoonStunned; // The last state of the bHarpoonStunned flag
|
|
var() bool bHarpoonToHeadStuns;// A harpoon to this zeds head will stun it
|
|
var() bool bHarpoonToBodyStuns;// A harpoon to this zeds body will stun it
|
|
var int NumHarpoonsAttached;// The number of harpoons stuck in us
|
|
|
|
var float DamageToMonsterScale;// How much to scale up damage for this zed damaging other monsters. Monsters have much higher health than humans, so when monsters duke it out they need to do more damage to each other
|
|
var float HumanBileAggroChance;// What random percentage chance (0.15-1.0) to have a zed go attack the nearest bloat when a human sprays them with a bloat bile weapon
|
|
|
|
// Zombie flags:
|
|
// 0 - Normal zombie
|
|
// 1 - Ranged zombie
|
|
// 2 - Leaping zombie
|
|
// 3 - Massive zombie
|
|
var() byte ZombieFlag;
|
|
|
|
var float LastPuntTime, DecapTime;
|
|
|
|
var rotator NewTorsoRotation;
|
|
//var int MaxTorsoYaw,MaxTorsoPitch,MaxTorsoRoll; // limits on SetBoneRot for hit reactions.
|
|
|
|
var(AI) int MaxSpineVariation; // Maximum base amount the bone can bend. Set in defprops.
|
|
var(AI) bool bContorts ; // Does this guy do the LIiiiMBO!!!
|
|
var(AI) float MaxContortionPercentage; // def. 0.25
|
|
|
|
var() int MeleeDamage; // The amount of damage this Zed does when melee attacking
|
|
var int damageForce;
|
|
var float LastPainAnim;
|
|
var(AI) float MinTimeBetweenPainAnims;
|
|
var bool playedHit;
|
|
var vector KickLocation, ImpactVector;
|
|
var actor KickTarget;
|
|
|
|
var bool bPlayBrainSplash;
|
|
var bool bPlayGoreSplash;
|
|
var float FeedThreshold; // OBSOLOTE.
|
|
|
|
var float CorpseStaticTime; // The level.timeseconds record of when the corpse has stopped moving, and should soon go static. 1 second later, it will.
|
|
var bool bCorpsePositionSet;
|
|
var float CorpseLifeSpan; // The time the zombie corpse will be around for.
|
|
var bool bDestroyNextTick; // Destroy this pawn next tick because destroying it now will cause problems
|
|
var float TimeSetDestroyNextTickTime; // The time we set the bDestroyNextTick flag
|
|
|
|
var pawn LastDamagedBy;
|
|
var class<damagetype> LastDamagedByType;
|
|
var int LastDamageAmount;
|
|
var vector LastHitLocation,LastMomentum ;
|
|
var float TorsoReturnAlpha;
|
|
var bool bBackstabbed;
|
|
|
|
var bool bFatAss; // HACK for pathfinding
|
|
|
|
var string KFRagdollName;
|
|
|
|
var class<DamTypeZombieAttack> ZombieDamType[3];
|
|
var class<DamTypeZombieAttack> CurrentDamType;
|
|
|
|
var sound MiscSound; //
|
|
|
|
var(Sounds) sound HeadLessDeathSound; //The sound of this zombie dieing without a head
|
|
var(Sounds) sound DecapitationSound; //The sound of this zombies head exploding
|
|
var(Sounds) sound MeleeAttackHitSound; //The sound of this zombie's melee attack hitting a player
|
|
var(Sounds) sound JumpSound; // sound of this zombie jumping
|
|
|
|
var float SpinDamConst;
|
|
var float SpinDamRand;
|
|
var() int ScreamDamage;
|
|
|
|
var() bool bMeleeStunImmune; // if true, this monster cannot be stunned or staggered by melee blows
|
|
|
|
// Fire Related
|
|
var int BurnDown; // Number of times our zombie must suffer Fire Damage.
|
|
var bool bAshen; // is our Zed crispy yet?
|
|
var class<Emitter> BurnEffect; // The appearance of the flames we are attaching to our Zed.
|
|
var class<Emitter> AltBurnEffect; // The low gore appearance of the "flames" we are attaching to our Zed.
|
|
var int LastBurnDamage; // Record the last amount of Fire damage the pawn suffered.
|
|
var() int CrispUpThreshhold; // How much burn down does this zed have to take before crisping up
|
|
var bool bCrispified; // The zed has been "ashen" alredy
|
|
var class<DamageType> FireDamageClass; // Holds the Fire Damage Type that was last done(Flamethrower or Mac10)
|
|
|
|
var Effect_ShadowController RealtimeShadow;
|
|
var(Pawn) bool bRealtimeShadows; // Advanced Shadows care of Squirrelzero's code.
|
|
|
|
var(Pawn) Name PuntAnim; // The animation to play when a zombie punts a karma object
|
|
|
|
|
|
// Add an animation name here, to have it randomize with the monster's default movement animations.
|
|
var Array<Name>AdditionalWalkAnims;
|
|
var Name SpawningWalkAnim; // the anim we're confirmed to spawn with.
|
|
|
|
var float BloodStreakInterval;
|
|
var transient float LastStreakTime;
|
|
var vector LastStreakLocation; // Stores the last place we left a streak, so we don't put them too close together
|
|
|
|
|
|
var class <KFGib> MonsterHeadGiblet;
|
|
var class <KFGib> MonsterThighGiblet;
|
|
var class <KFGib> MonsterArmGiblet;
|
|
var class <KFGib> MonsterLegGiblet;
|
|
var class <KFGib> MonsterTorsoGiblet;
|
|
var class <KFGib> MonsterLowerTorsoGiblet;
|
|
|
|
// Better Gore
|
|
|
|
var(Pawn) Material GoredMat; // Swap this in when he's blown in half.
|
|
|
|
var bool bDiffAdjusted; // has this monster had it's stats adjusted for the server's difficulty? Do once.
|
|
|
|
var bool bCloaked;
|
|
var bool bSpotted; // if true , use the "revealed" shader, instead of the cloak effect
|
|
var Emitter FlamingFXs;
|
|
|
|
var Pawn BurnInstigator;
|
|
|
|
var(AI) enum EIntelligence
|
|
{
|
|
BRAINS_Retarded, // Dumbasses
|
|
BRAINS_Stupid, // Just plain stupid.
|
|
BRAINS_Mammal,
|
|
BRAINS_Human // Smarties
|
|
} Intelligence;
|
|
var(AI) bool bCanDistanceAttackDoors;
|
|
var bool bDistanceAttackingDoor;
|
|
|
|
var() bool bStartUpDisabled,bNoAutoHuntEnemies;
|
|
var() int HealthModifer;
|
|
var() name FirstSeePlayerEvent;
|
|
|
|
var int ExpectingChannel;
|
|
var bool bResetAnimAct;
|
|
var float ResetAnimActTime;
|
|
|
|
var(ExtendedCollision) bool bUseExtendedCollision; // Whether or not this monster has an extra collision cylinder
|
|
var(ExtendedCollision) vector ColOffset; // The offset of the extended collision cylinder from the root bone
|
|
var(ExtendedCollision) float ColRadius,ColHeight; // The size of the extended collision cylinder
|
|
var(ExtendedCollision) name ExtCollAttachBoneName; // The bone name to attach the extended collision to
|
|
var ExtendedZCollision MyExtCollision; // The extended collision cylinder
|
|
var bool SavedExtCollision; // Saved original state of the external collision
|
|
|
|
// Bone Names - These are used by the gore system. Need to use variables instead
|
|
// of hard coding these while we switch over from old player models to new ones - Ramm
|
|
var() name LeftShoulderBone; // The bone name of the left shoulder bone
|
|
var() name RightShoulderBone; // The bone name of the right shoulder bone
|
|
var() name LeftThighBone; // The bone name of the left thigh bone
|
|
var() name RightThighBone; // The bone name of the right thigh bone
|
|
var() name LeftFArmBone; // The bone name of the left forearm bone
|
|
var() name RightFArmBone; // The bone name of the right forearm bone
|
|
var() name LeftFootBone; // The bone name of the left foot bone
|
|
var() name RightFootBone; // The bone name of the right foot bone
|
|
var() name LeftHandBone; // The bone name of the left hand bone
|
|
var() name RightHandBone; // The bone name of the right hand bone
|
|
var() name NeckBone; // The bone name of the neck bone
|
|
|
|
var float OriginalGroundSpeed;// The difficulty adjusted ground speed (need to store this off, because we have to restore this value at certain times)
|
|
|
|
// Gore
|
|
var SeveredAppendageAttachment SeveredLeftArm; // The meaty attachments that get attached when body parts are blown off
|
|
var SeveredAppendageAttachment SeveredRightArm; // The meaty attachments that get attached when body parts are blown off
|
|
var SeveredAppendageAttachment SeveredLeftLeg; // The meaty attachments that get attached when body parts are blown off
|
|
var SeveredAppendageAttachment SeveredRightLeg; // The meaty attachments that get attached when body parts are blown off
|
|
var SeveredAppendageAttachment SeveredHead; // The meaty attachments that get attached when body parts are blown off
|
|
var(Gore) float SeveredArmAttachScale; // The drawscale of the arm gore attachement
|
|
var(Gore) float SeveredLegAttachScale; // The drawscale of the leg gore attachement
|
|
var(Gore) float SeveredHeadAttachScale; // The drawscale of the head gore attachement
|
|
|
|
var class<DismembermentJetHead> NeckSpurtEmitterClass; // class of the destroyed head neck emitter
|
|
var class<DismembermentJetDecapitate> NeckSpurtNoGibEmitterClass;// class of the chopped off head neck emitter
|
|
var class<DismembermentJetLimb> LimbSpurtEmitterClass; // class of the chopped off head neck emitter
|
|
|
|
var class<SeveredAppendageAttachment> SeveredArmAttachClass; // class of the severed arm for this role
|
|
var class<SeveredAppendageAttachment> SeveredLegAttachClass; // class of the severed arm for this role
|
|
var class<SeveredAppendageAttachment> SeveredHeadAttachClass; // class of the severed arm for this role
|
|
|
|
var class <ProjectileBloodSplat> ProjectileBloodSplatClass; // class of the wall bloodsplat from a projectile's impact
|
|
var class <SeveredAppendage> DetachedArmClass; // class of detached arm to spawn for this pawn. Modified by the subclass to match the player model
|
|
var class <SeveredAppendage> DetachedLegClass; // class of detached leg to spawn for this pawn. Modified by the subclass to match the player model
|
|
var class <SeveredAppendage> DetachedHeadClass; // class of detached head to spawn for this pawn. Modified by the subclass to match the player model
|
|
var class <SeveredAppendage> DetachedSpecialArmClass;// class of detached special arm to spawn for this pawn. Modified by the subclass to match the player model
|
|
|
|
var bool bLeftArmGibbed; // LeftArm is already blown off
|
|
var bool bRightArmGibbed; // RightArm is already blown off
|
|
var bool bLeftLegGibbed; // LeftLeg is already blown off
|
|
var bool bRightLegGibbed; // RightLeg is already blown off
|
|
var bool bHeadGibbed; // Head is already blown off
|
|
var class <Emitter> ObliteratedEffectClass; // class of detached arm to spawn for this pawn. Modified by the RoleInfo to match the player model
|
|
|
|
var() float PlayerCountHealthScale; // How much % of total health to add to this Zed for each additional player in the game
|
|
|
|
var float LastViewCheckTime; // internal use, used to see how long its been since we checked if there was line of site between this Zed and the local player controller
|
|
var float LastSeenOrRelevantTime; // The last time this Zed was seen, or was relevant
|
|
|
|
var name HeadlessWalkAnims[4]; // Headless movement anims - Forward, Back, Left, Right
|
|
var name BurningWalkFAnims[3]; // Burning move forward anims
|
|
var name BurningWalkAnims[3]; // Burning movement anims, Back, Left, Right
|
|
|
|
var() float PoundRageBumpDamScale; // How much to scale the damage from the FleshPound running into this AI
|
|
|
|
var() float HiddenGroundSpeed; // How fast this Zed should move when it's out of view;
|
|
|
|
var rotator NeckRot; // Used for the low gore "decapitaton"
|
|
var bool bZedUnderControl; // The zed is under the control of the zed control device
|
|
var int NumZCDHits; // How many times has this zed been hit with the ZCD Device
|
|
|
|
var() vector OnlineHeadshotOffset; // Headshot offset for when a zed isn't animating online. "Best guess" since movement anims aren't played on the server side
|
|
var() float OnlineHeadshotScale; // Headshot scale for when a zed isn't animating online. Scaled a little bit to cover the area the head might be in when moving, since movement anims aren't played server side
|
|
var() float HeadHealth; // How much health the zed's head has, get below this and they lose thier head
|
|
var() float PlayerNumHeadHealthScale;// How much % of total head health to add to this Zed for each additional player in the game
|
|
|
|
var() float MotionDetectorThreat; // How much of a threat is this zed considered to be to motion sensing devices
|
|
|
|
// Headshot debugging
|
|
//var vector ServerHeadLocation; // The location of the Zed's head on the server, used for debugging
|
|
//var vector LastServerHeadLocation;
|
|
|
|
// Achievements Helpers
|
|
var bool bHealed;
|
|
var bool bOnlyDamagedByCrossbow;
|
|
var bool bDamagedAPlayer;
|
|
var bool bLaserSightedEBRM14Headshotted;
|
|
|
|
replication
|
|
{
|
|
reliable if(bNetDirty && Role == ROLE_Authority)
|
|
bDecapitated,Gored,LookTarget,bBurnified,bAshen,FeedThreshold,bCannibal,bDiffAdjusted,bCloaked,
|
|
bCrispified,bZedUnderControl;
|
|
|
|
reliable if(bNetDirty && Role == ROLE_Authority)
|
|
bZapped, bHarpoonStunned;
|
|
|
|
// Headshot debugging
|
|
// reliable if(Role == ROLE_Authority)
|
|
// ServerHeadLocation;
|
|
}
|
|
|
|
event PreBeginPlay()
|
|
{
|
|
Super.PreBeginPlay();
|
|
|
|
CalcAmbientRelevancyScale();
|
|
}
|
|
|
|
simulated function CalcAmbientRelevancyScale()
|
|
{
|
|
// Make the zed only relevant by thier ambient sound out to a range of 10 meters
|
|
CustomAmbientRelevancyScale = 500/(100 * SoundRadius);
|
|
}
|
|
|
|
// empty functions to avoid casting
|
|
function StartCharging(){}
|
|
function DebugLog(){}
|
|
function BreakGrapple(){}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// PostBeginPlay
|
|
//-----------------------------------------------------------------------------
|
|
|
|
simulated function PostBeginPlay()
|
|
{
|
|
local float RandomGroundSpeedScale;
|
|
local float MovementSpeedDifficultyScale;
|
|
local vector AttachPos;
|
|
|
|
if(ROLE==ROLE_Authority)
|
|
{
|
|
if ( (ControllerClass != None) && (Controller == None) )
|
|
Controller = spawn(ControllerClass);
|
|
|
|
if ( Controller != None )
|
|
Controller.Possess(self);
|
|
|
|
SplashTime = 0;
|
|
SpawnTime = Level.TimeSeconds;
|
|
EyeHeight = BaseEyeHeight;
|
|
OldRotYaw = Rotation.Yaw;
|
|
if( HealthModifer!=0 )
|
|
Health = HealthModifer;
|
|
|
|
if ( bUseExtendedCollision && MyExtCollision == none )
|
|
{
|
|
MyExtCollision = Spawn(class 'ExtendedZCollision',self);
|
|
MyExtCollision.SetCollisionSize(ColRadius,ColHeight);
|
|
|
|
MyExtCollision.bHardAttach = true;
|
|
AttachPos = Location + (ColOffset >> Rotation);
|
|
MyExtCollision.SetLocation( AttachPos );
|
|
MyExtCollision.SetPhysics( PHYS_None );
|
|
MyExtCollision.SetBase( self );
|
|
SavedExtCollision = MyExtCollision.bCollideActors;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
AssignInitialPose();
|
|
// Let's randomly alter the position of our zombies' spines, to give their animations
|
|
// the appearance of being somewhat unique.
|
|
SetTimer(1.0, false);
|
|
|
|
//Set Karma Ragdoll skeleton for this character.
|
|
if (KFRagdollName != "")
|
|
RagdollOverride = KFRagdollName; //ClotKarma
|
|
//Log("Ragdoll Skeleton name is :"$RagdollOverride);
|
|
|
|
if (bActorShadows && bPlayerShadows && (Level.NetMode != NM_DedicatedServer))
|
|
{
|
|
// decide which type of shadow to spawn
|
|
if (!bRealtimeShadows)
|
|
{
|
|
PlayerShadow = Spawn(class'ShadowProjector',Self,'',Location);
|
|
PlayerShadow.ShadowActor = self;
|
|
PlayerShadow.bBlobShadow = bBlobShadow;
|
|
PlayerShadow.LightDirection = Normal(vect(1,1,3));
|
|
PlayerShadow.LightDistance = 320;
|
|
PlayerShadow.MaxTraceDistance = 350;
|
|
PlayerShadow.InitShadow();
|
|
}
|
|
else
|
|
{
|
|
RealtimeShadow = Spawn(class'Effect_ShadowController',self,'',Location);
|
|
RealtimeShadow.Instigator = self;
|
|
RealtimeShadow.Initialize();
|
|
}
|
|
}
|
|
|
|
bSTUNNED = false;
|
|
DECAP = false;
|
|
|
|
// Difficulty Scaling
|
|
if (Level.Game != none && !bDiffAdjusted)
|
|
{
|
|
//log(self$" Beginning ground speed "$default.GroundSpeed);
|
|
|
|
if( Level.Game.NumPlayers <= 3 )
|
|
{
|
|
HiddenGroundSpeed = default.HiddenGroundSpeed;
|
|
}
|
|
else if( Level.Game.NumPlayers <= 5 )
|
|
{
|
|
HiddenGroundSpeed = default.HiddenGroundSpeed * 1.3;
|
|
}
|
|
else if( Level.Game.NumPlayers >= 6 )
|
|
{
|
|
HiddenGroundSpeed = default.HiddenGroundSpeed * 1.65;
|
|
}
|
|
|
|
// Some randomization to their walk speeds.
|
|
RandomGroundSpeedScale = 1.0 + ((1.0 - (FRand() * 2.0)) * 0.1); // +/- 10%
|
|
SetGroundSpeed(default.GroundSpeed * RandomGroundSpeedScale);
|
|
//log(self$" Randomized ground speed "$GroundSpeed$" RandomGroundSpeedScale "$RandomGroundSpeedScale);
|
|
|
|
if( Level.Game.GameDifficulty < 2.0 )
|
|
{
|
|
MovementSpeedDifficultyScale = 0.95;
|
|
}
|
|
else if( Level.Game.GameDifficulty < 4.0 )
|
|
{
|
|
MovementSpeedDifficultyScale = 1.0;
|
|
}
|
|
else if( Level.Game.GameDifficulty < 5.0 )
|
|
{
|
|
MovementSpeedDifficultyScale = 1.15;
|
|
}
|
|
else if( Level.Game.GameDifficulty < 7.0 )
|
|
{
|
|
MovementSpeedDifficultyScale = 1.22;
|
|
}
|
|
else // Hardest difficulty
|
|
{
|
|
MovementSpeedDifficultyScale = 1.3;
|
|
}
|
|
|
|
GroundSpeed *= MovementSpeedDifficultyScale;
|
|
AirSpeed *= MovementSpeedDifficultyScale;
|
|
WaterSpeed *= MovementSpeedDifficultyScale;
|
|
// Store the difficulty adjusted ground speed to restore if we change it elsewhere
|
|
OriginalGroundSpeed = GroundSpeed;
|
|
|
|
//log(self$" Scaled ground speed "$GroundSpeed$" Difficulty "$Level.Game.GameDifficulty$" MovementSpeedDifficultyScale "$MovementSpeedDifficultyScale);
|
|
|
|
// Scale health by difficulty
|
|
Health *= DifficultyHealthModifer();
|
|
HealthMax *= DifficultyHealthModifer();
|
|
HeadHealth *= DifficultyHeadHealthModifer();
|
|
|
|
// Scale health by number of players
|
|
Health *= NumPlayersHealthModifer();
|
|
HealthMax *= NumPlayersHealthModifer();
|
|
HeadHealth *= NumPlayersHeadHealthModifer();
|
|
|
|
MeleeDamage = Max((DifficultyDamageModifer() * MeleeDamage),1);
|
|
|
|
SpinDamConst = Max((DifficultyDamageModifer() * SpinDamConst),1);
|
|
SpinDamRand = Max((DifficultyDamageModifer() * SpinDamRand),1);
|
|
|
|
ScreamDamage = Max((DifficultyDamageModifer() * ScreamDamage),1);
|
|
|
|
bDiffAdjusted = true;
|
|
}
|
|
|
|
if( Level.NetMode!=NM_DedicatedServer )
|
|
{
|
|
AdditionalWalkAnims[AdditionalWalkAnims.length] = default.MovementAnims[0];
|
|
MovementAnims[0] = AdditionalWalkAnims[Rand(AdditionalWalkAnims.length)];
|
|
}
|
|
}
|
|
|
|
// Accessor for GroundSpeed so we can track what is setting it
|
|
simulated function SetGroundSpeed(float NewGroundSpeed)
|
|
{
|
|
GroundSpeed = NewGroundSpeed;
|
|
}
|
|
|
|
// Scales the damage this Zed deals by the difficulty level
|
|
function float DifficultyDamageModifer()
|
|
{
|
|
local float AdjustedDamageModifier;
|
|
|
|
if ( Level.Game.GameDifficulty >= 7.0 ) // Hell on Earth
|
|
{
|
|
AdjustedDamageModifier = 1.75;
|
|
}
|
|
else if ( Level.Game.GameDifficulty >= 5.0 ) // Suicidal
|
|
{
|
|
AdjustedDamageModifier = 1.50;
|
|
}
|
|
else if ( Level.Game.GameDifficulty >= 4.0 ) // Hard
|
|
{
|
|
AdjustedDamageModifier = 1.25;
|
|
}
|
|
else if ( Level.Game.GameDifficulty >= 2.0 ) // Normal
|
|
{
|
|
AdjustedDamageModifier = 1.0;
|
|
}
|
|
else //if ( GameDifficulty == 1.0 ) // Beginner
|
|
{
|
|
AdjustedDamageModifier = 0.3;
|
|
}
|
|
|
|
// Do less damage if we're alone
|
|
if( Level.Game.NumPlayers == 1 )
|
|
{
|
|
AdjustedDamageModifier *= 0.75;
|
|
}
|
|
|
|
return AdjustedDamageModifier;
|
|
}
|
|
|
|
// Scales the health this Zed has by the difficulty level
|
|
function float DifficultyHealthModifer()
|
|
{
|
|
local float AdjustedModifier;
|
|
|
|
if ( Level.Game.GameDifficulty >= 7.0 ) // Hell on Earth
|
|
{
|
|
AdjustedModifier = 1.75;
|
|
}
|
|
else if ( Level.Game.GameDifficulty >= 5.0 ) // Suicidal
|
|
{
|
|
AdjustedModifier = 1.55;
|
|
}
|
|
else if ( Level.Game.GameDifficulty >= 4.0 ) // Hard
|
|
{
|
|
AdjustedModifier = 1.35;
|
|
}
|
|
else if ( Level.Game.GameDifficulty >= 2.0 ) // Normal
|
|
{
|
|
AdjustedModifier = 1.0;
|
|
}
|
|
else //if ( GameDifficulty == 1.0 ) // Beginner
|
|
{
|
|
AdjustedModifier = 0.5;
|
|
}
|
|
|
|
return AdjustedModifier;
|
|
}
|
|
|
|
// Scales the health this Zed has by number of players
|
|
function float NumPlayersHealthModifer()
|
|
{
|
|
local float AdjustedModifier;
|
|
local int NumEnemies;
|
|
local Controller C;
|
|
|
|
AdjustedModifier = 1.0;
|
|
|
|
For( C=Level.ControllerList; C!=None; C=C.NextController )
|
|
{
|
|
if( C.bIsPlayer && C.Pawn!=None && C.Pawn.Health > 0 )
|
|
{
|
|
NumEnemies++;
|
|
}
|
|
}
|
|
|
|
if( NumEnemies > 1 )
|
|
{
|
|
AdjustedModifier += (NumEnemies - 1) * PlayerCountHealthScale;
|
|
}
|
|
|
|
return AdjustedModifier;
|
|
}
|
|
|
|
// Scales the head health this Zed has by the difficulty level
|
|
function float DifficultyHeadHealthModifer()
|
|
{
|
|
local float AdjustedModifier;
|
|
|
|
if ( Level.Game.GameDifficulty >= 7.0 ) // Hell on Earth
|
|
{
|
|
AdjustedModifier = 1.75;
|
|
}
|
|
else if ( Level.Game.GameDifficulty >= 5.0 ) // Suicidal
|
|
{
|
|
AdjustedModifier = 1.55;
|
|
}
|
|
else if ( Level.Game.GameDifficulty >= 4.0 ) // Hard
|
|
{
|
|
AdjustedModifier = 1.35;
|
|
}
|
|
else if ( Level.Game.GameDifficulty >= 2.0 ) // Normal
|
|
{
|
|
AdjustedModifier = 1.0;
|
|
}
|
|
else //if ( GameDifficulty == 1.0 ) // Beginner
|
|
{
|
|
AdjustedModifier = 0.5;
|
|
}
|
|
|
|
return AdjustedModifier;
|
|
}
|
|
|
|
// Scales the head health this Zed has by number of players
|
|
function float NumPlayersHeadHealthModifer()
|
|
{
|
|
local float AdjustedModifier;
|
|
local int NumEnemies;
|
|
local Controller C;
|
|
|
|
AdjustedModifier = 1.0;
|
|
|
|
For( C=Level.ControllerList; C!=None; C=C.NextController )
|
|
{
|
|
if( C.bIsPlayer && C.Pawn!=None && C.Pawn.Health > 0 )
|
|
{
|
|
NumEnemies++;
|
|
}
|
|
}
|
|
|
|
if( NumEnemies > 1 )
|
|
{
|
|
AdjustedModifier += (NumEnemies - 1) * PlayerNumHeadHealthScale;
|
|
}
|
|
|
|
return AdjustedModifier;
|
|
}
|
|
|
|
simulated function PostNetBeginPlay()
|
|
{
|
|
EnableChannelNotify ( 1,1);
|
|
AnimBlendParams(1, 1.0, 0.0,, SpineBone1);
|
|
AnimBlendParams(1, 1.0, 0.0,, HeadBone);
|
|
super(pawn).PostNetBeginPlay();
|
|
}
|
|
|
|
// This zed can dodge an incoming shot right now. Overriden in various states
|
|
// to prevent certain actions from being interrupted.
|
|
function bool CanGetOutOfWay()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/* DisplayDebug()
|
|
list important actor variable on canvas. Also show the pawn's controller and weapon info
|
|
*/
|
|
simulated function DisplayDebug(Canvas Canvas, out float YL, out float YPos)
|
|
{
|
|
local int i;
|
|
local name Sequence;
|
|
local float Frame, Rate;
|
|
|
|
Super.DisplayDebug(Canvas, YL, YPos);
|
|
|
|
for( i = 0; i < 16; i++ )
|
|
{
|
|
if( IsAnimating( i ) )
|
|
{
|
|
GetAnimParams( i, Sequence, Frame, Rate );
|
|
Canvas.DrawText("Anim:: Channel("@i@") Frame("@Frame@") Rate("@Rate@") Name("@Sequence@")");
|
|
YPos += YL;
|
|
Canvas.SetPos(4,YPos);
|
|
}
|
|
}
|
|
|
|
Canvas.DrawText("bShotAnim: "@bShotAnim);
|
|
YPos += YL;
|
|
Canvas.SetPos(4,YPos);
|
|
}
|
|
|
|
// This zed has been taken control of. Boost its health and speed
|
|
function SetMindControlled(bool bNewMindControlled)
|
|
{
|
|
if( bNewMindControlled )
|
|
{
|
|
NumZCDHits++;
|
|
|
|
if( bNewMindControlled != bZedUnderControl )
|
|
{
|
|
SetGroundSpeed(OriginalGroundSpeed * 1.25);
|
|
Health *= 1.25;
|
|
HealthMax *= 1.25;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NumZCDHits=0;
|
|
}
|
|
|
|
bZedUnderControl = bNewMindControlled;
|
|
}
|
|
|
|
// Handle the zed being commanded to move to a new location
|
|
function GivenNewMarker()
|
|
{
|
|
GotoState('');
|
|
}
|
|
|
|
// Getter for the Original groundspeed of the zed (adjusted for difficulty, etc)
|
|
simulated function float GetOriginalGroundSpeed()
|
|
{
|
|
if( bZedUnderControl )
|
|
{
|
|
return OriginalGroundSpeed * 1.25;
|
|
}
|
|
else
|
|
{
|
|
return OriginalGroundSpeed;
|
|
}
|
|
}
|
|
|
|
//returns how exposed this player is to another actor
|
|
function float GetExposureTo(vector TestLocation)
|
|
{
|
|
local float PercentExposed;
|
|
|
|
if( FastTrace(GetBoneCoords(HeadBone).Origin,TestLocation))
|
|
{
|
|
PercentExposed += 0.4;
|
|
}
|
|
|
|
if( FastTrace(GetBoneCoords(RootBone).Origin,TestLocation))
|
|
{
|
|
PercentExposed += 0.3;
|
|
}
|
|
|
|
if( FastTrace(GetBoneCoords(LeftFootBone).Origin,TestLocation))
|
|
{
|
|
PercentExposed += 0.15;
|
|
}
|
|
|
|
if( FastTrace(GetBoneCoords(RightFootBone).Origin,TestLocation))
|
|
{
|
|
PercentExposed += 0.15;
|
|
}
|
|
|
|
return PercentExposed;
|
|
}
|
|
|
|
// Update the shadow if the detail settings have changed in the detail menu
|
|
simulated function UpdateShadow()
|
|
{
|
|
if (bActorShadows && bPlayerShadows && (Level.NetMode != NM_DedicatedServer))
|
|
{
|
|
if (PlayerShadow != none)
|
|
PlayerShadow.Destroy();
|
|
|
|
if (RealtimeShadow != none)
|
|
RealtimeShadow.Destroy();
|
|
|
|
// decide which type of shadow to spawn
|
|
if (!bRealtimeShadows)
|
|
{
|
|
PlayerShadow = Spawn(class'ShadowProjector',Self,'',Location);
|
|
PlayerShadow.ShadowActor = self;
|
|
PlayerShadow.bBlobShadow = bBlobShadow;
|
|
PlayerShadow.LightDirection = Normal(vect(1,1,3));
|
|
PlayerShadow.LightDistance = 320;
|
|
PlayerShadow.MaxTraceDistance = 350;
|
|
PlayerShadow.InitShadow();
|
|
}
|
|
else
|
|
{
|
|
RealtimeShadow = Spawn(class'Effect_ShadowController',self,'',Location);
|
|
RealtimeShadow.Instigator = self;
|
|
RealtimeShadow.Initialize();
|
|
}
|
|
}
|
|
else if (PlayerShadow != none && Level.NetMode != NM_DedicatedServer)
|
|
{
|
|
PlayerShadow.Destroy();
|
|
PlayerShadow = none;
|
|
}
|
|
else if (RealtimeShadow != none && Level.NetMode != NM_DedicatedServer)
|
|
{
|
|
RealtimeShadow.Destroy();
|
|
RealtimeShadow = none;
|
|
}
|
|
}
|
|
|
|
// Setters for extra collision cylinders
|
|
simulated function ToggleAuxCollision(bool newbCollision)
|
|
{
|
|
if ( !newbCollision )
|
|
{
|
|
SavedExtCollision = MyExtCollision.bCollideActors;
|
|
|
|
MyExtCollision.SetCollision(false);
|
|
}
|
|
else
|
|
{
|
|
MyExtCollision.SetCollision(SavedExtCollision);
|
|
}
|
|
}
|
|
|
|
function bool MakeGrandEntry()
|
|
{
|
|
Return False;
|
|
}
|
|
|
|
function bool Cloaked()
|
|
{
|
|
return bCloaked;
|
|
}
|
|
|
|
// move karma objects by a kick
|
|
// The Kick animation will ONLY be called if the Zombie is On level ground with the KActor,
|
|
// and is facing it.
|
|
event Bump(actor Other)
|
|
{
|
|
local Vector X,Y,Z;
|
|
|
|
GetAxes(Rotation, X,Y,Z);
|
|
|
|
super.Bump(Other);
|
|
if( Other==none )
|
|
return;
|
|
|
|
if( Other.IsA('NetKActor') && Physics != PHYS_Falling && Location.Z < Other.Location.Z + CollisionHeight
|
|
&& Location.Z > Other.Location.Z - (CollisionHeight * 0.5) && Base!=Other && Base.bStatic
|
|
&& normal(X) dot normal(Other.Location - Location) >= 0.7)
|
|
{
|
|
if( KActor(Other).KGetMass()>=0.5 && !MonsterController(Controller).CanAttack(Controller.Enemy) )
|
|
{
|
|
// Store kick impact data
|
|
|
|
ImpactVector = Vector(controller.Rotation)*15000 + (velocity * (Mass / 2) ) ; // 30
|
|
KickLocation = Other.Location;
|
|
KickTarget = Other;
|
|
KFMonsterController(Controller).KickTarget = KActor(Other);
|
|
SetAnimAction(PuntAnim);
|
|
}
|
|
}
|
|
}
|
|
|
|
// No more File cabinet surfing zombies please..
|
|
singular event BaseChange()
|
|
{
|
|
if ( KActor(Base) != None || Pawn(Base) != None )
|
|
{
|
|
JumpOffPawn();
|
|
}
|
|
}
|
|
|
|
function JumpOffPawn()
|
|
{
|
|
Velocity += (50 + CollisionRadius) * VRand();
|
|
Velocity.Z = 80 + CollisionHeight;
|
|
SetPhysics(PHYS_Falling);
|
|
bNoJumpAdjust = true;
|
|
if ( Controller != None )
|
|
{
|
|
Controller.SetFall();
|
|
}
|
|
}
|
|
|
|
event PickWallAdjustInLowGravity( vector WallHitNormal, actor HitActor )
|
|
{
|
|
//Controller.SetFall();
|
|
}
|
|
|
|
// Actually execute the kick (this is notified in the ZombieKick animation)
|
|
function KickActor()
|
|
{
|
|
KickTarget.Velocity.Z += (Mass * 5 + (KGetMass() * 10));
|
|
KickTarget.KAddImpulse(ImpactVector, KickLocation);
|
|
Acceleration = vect(0,0,0);
|
|
Velocity = vect(0,0,0);
|
|
KFMonsterController(controller).GotoState('Kicking');
|
|
bShotAnim = true;
|
|
}
|
|
|
|
simulated function bool IsMoreThanHalf ( int AngleRot )
|
|
{
|
|
if ( AngleRot > 32768 )
|
|
{
|
|
return True;
|
|
}
|
|
else
|
|
{
|
|
return False;
|
|
}
|
|
}
|
|
|
|
// Return true if we can do the Zombie speed adjust that gets the Zeds
|
|
// to the player faster if they can't be seen
|
|
function bool CanSpeedAdjust()
|
|
{
|
|
if ( !bDecapitated && !bZapped )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
simulated function Tick(float DeltaTime)
|
|
{
|
|
local PlayerController P;
|
|
local float DistSquared;
|
|
|
|
// if(Level.NetMode == NM_DedicatedServer)
|
|
// {
|
|
// IsHeadShot(vect(0,0,0), vect(0,0,0), 1.0);
|
|
// }
|
|
|
|
// If we've flagged this character to be destroyed next tick, handle that
|
|
if( bDestroyNextTick && TimeSetDestroyNextTickTime < Level.TimeSeconds )
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
// Make Zeds move faster if they aren't net relevant, or noone has seen them
|
|
// in a while. This well get the Zeds to the player in larger groups, and
|
|
// quicker - Ramm
|
|
if ( Level.NetMode != NM_Client && CanSpeedAdjust() )
|
|
{
|
|
if ( Level.NetMode == NM_Standalone )
|
|
{
|
|
if ( Level.TimeSeconds - LastRenderTime > 5.0 )
|
|
{
|
|
P = Level.GetLocalPlayerController();
|
|
|
|
if ( P != none && P.Pawn != none )
|
|
{
|
|
if ( Level.TimeSeconds - LastViewCheckTime > 1.0 )
|
|
{
|
|
LastViewCheckTime = Level.TimeSeconds;
|
|
DistSquared = VSizeSquared(P.Pawn.Location - Location);
|
|
if( (!P.Pawn.Region.Zone.bDistanceFog || (DistSquared < Square(P.Pawn.Region.Zone.DistanceFogEnd))) &&
|
|
FastTrace(Location + EyePosition(), P.Pawn.Location + P.Pawn.EyePosition()) )
|
|
{
|
|
LastSeenOrRelevantTime = Level.TimeSeconds;
|
|
SetGroundSpeed(GetOriginalGroundSpeed());
|
|
}
|
|
else
|
|
{
|
|
SetGroundSpeed(default.GroundSpeed * (HiddenGroundSpeed / default.GroundSpeed));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LastSeenOrRelevantTime = Level.TimeSeconds;
|
|
SetGroundSpeed(GetOriginalGroundSpeed());
|
|
}
|
|
}
|
|
else if ( Level.NetMode == NM_DedicatedServer )
|
|
{
|
|
if ( Level.TimeSeconds - LastReplicateTime > 0.5 )
|
|
{
|
|
SetGroundSpeed(default.GroundSpeed * (300.0 / default.GroundSpeed));
|
|
}
|
|
else
|
|
{
|
|
LastSeenOrRelevantTime = Level.TimeSeconds;
|
|
SetGroundSpeed(GetOriginalGroundSpeed());
|
|
}
|
|
}
|
|
else if ( Level.NetMode == NM_ListenServer )
|
|
{
|
|
if ( Level.TimeSeconds - LastReplicateTime > 0.5 && Level.TimeSeconds - LastRenderTime > 5.0 )
|
|
{
|
|
P = Level.GetLocalPlayerController();
|
|
|
|
if ( P != none && P.Pawn != none )
|
|
{
|
|
if ( Level.TimeSeconds - LastViewCheckTime > 1.0 )
|
|
{
|
|
LastViewCheckTime = Level.TimeSeconds;
|
|
DistSquared = VSizeSquared(P.Pawn.Location - Location);
|
|
|
|
if ( (!P.Pawn.Region.Zone.bDistanceFog || (DistSquared < Square(P.Pawn.Region.Zone.DistanceFogEnd))) &&
|
|
FastTrace(Location + EyePosition(), P.Pawn.Location + P.Pawn.EyePosition()) )
|
|
{
|
|
LastSeenOrRelevantTime = Level.TimeSeconds;
|
|
SetGroundSpeed(GetOriginalGroundSpeed());
|
|
}
|
|
else
|
|
{
|
|
SetGroundSpeed(default.GroundSpeed * (300.0 / default.GroundSpeed));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LastSeenOrRelevantTime = Level.TimeSeconds;
|
|
SetGroundSpeed(GetOriginalGroundSpeed());
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bResetAnimAct && ResetAnimActTime<Level.TimeSeconds )
|
|
{
|
|
AnimAction = '';
|
|
bResetAnimAct = False;
|
|
}
|
|
|
|
if ( Controller != None )
|
|
{
|
|
LookTarget = Controller.Enemy;
|
|
}
|
|
|
|
// If the Zed has been bleeding long enough, make it die
|
|
if ( Role == ROLE_Authority && bDecapitated )
|
|
{
|
|
if ( BleedOutTime > 0 && Level.TimeSeconds - BleedOutTime >= 0 )
|
|
{
|
|
Died(LastDamagedBy.Controller,class'DamTypeBleedOut',Location);
|
|
BleedOutTime=0;
|
|
}
|
|
|
|
}
|
|
|
|
//SPLATTER!!!!!!!!!
|
|
//TODO - can we work this into Epic's gib code?
|
|
//Will we see enough improvement in efficiency to be worth the effort?
|
|
if ( Level.NetMode!=NM_DedicatedServer )
|
|
{
|
|
TickFX(DeltaTime);
|
|
|
|
if ( bBurnified && !bBurnApplied )
|
|
{
|
|
if ( !bGibbed )
|
|
{
|
|
StartBurnFX();
|
|
}
|
|
}
|
|
else if ( !bBurnified && bBurnApplied )
|
|
{
|
|
StopBurnFX();
|
|
}
|
|
|
|
if ( bAshen && Level.NetMode == NM_Client && !class'GameInfo'.static.UseLowGore() )
|
|
{
|
|
ZombieCrispUp();
|
|
bAshen = False;
|
|
}
|
|
}
|
|
|
|
if ( DECAP )
|
|
{
|
|
if ( Level.TimeSeconds > (DecapTime + 2.0) && Controller != none )
|
|
{
|
|
DECAP = false;
|
|
MonsterController(Controller).ExecuteWhatToDoNext();
|
|
}
|
|
}
|
|
|
|
if ( BileCount > 0 && NextBileTime<level.TimeSeconds )
|
|
{
|
|
--BileCount;
|
|
NextBileTime+=BileFrequency;
|
|
TakeBileDamage();
|
|
}
|
|
|
|
if( bZapped && Role == ROLE_Authority )
|
|
{
|
|
RemainingZap -= DeltaTime;
|
|
|
|
if( RemainingZap <= 0 )
|
|
{
|
|
RemainingZap = 0;
|
|
bZapped = False;
|
|
ZappedBy = none;
|
|
// The Zed can take more zap each time they get zapped
|
|
ZapThreshold *= ZapResistanceScale;
|
|
}
|
|
}
|
|
|
|
if( !bZapped && TotalZap > 0 && ((Level.TimeSeconds - LastZapTime) > 0.1) )
|
|
{
|
|
TotalZap -= DeltaTime;
|
|
}
|
|
|
|
if( bZapped != bOldZapped )
|
|
{
|
|
if( bZapped )
|
|
{
|
|
SetZappedBehavior();
|
|
}
|
|
else
|
|
{
|
|
UnSetZappedBehavior();
|
|
}
|
|
|
|
bOldZapped = bZapped;
|
|
}
|
|
|
|
if( bHarpoonStunned != bOldHarpoonStunned )
|
|
{
|
|
if( bHarpoonStunned )
|
|
{
|
|
SetBurningBehavior();
|
|
}
|
|
else
|
|
{
|
|
UnSetBurningBehavior();
|
|
}
|
|
|
|
bOldHarpoonStunned = bHarpoonStunned;
|
|
}
|
|
}
|
|
|
|
// Apply "Zap" to the Zed
|
|
function SetZapped(float ZapAmount, Pawn Instigator)
|
|
{
|
|
LastZapTime = Level.TimeSeconds;
|
|
|
|
if( bZapped )
|
|
{
|
|
TotalZap = ZapThreshold;
|
|
RemainingZap = ZapDuration;
|
|
SetOverlayMaterial(Material'KFZED_FX_T.Energy.ZED_overlay_Hit_Shdr', RemainingZap, true);
|
|
}
|
|
else
|
|
{
|
|
TotalZap += ZapAmount;
|
|
|
|
if( TotalZap >= ZapThreshold )
|
|
{
|
|
RemainingZap = ZapDuration;
|
|
SetOverlayMaterial(Material'KFZED_FX_T.Energy.ZED_overlay_Hit_Shdr', RemainingZap, true);
|
|
bZapped = true;
|
|
}
|
|
}
|
|
ZappedBy = Instigator;
|
|
}
|
|
|
|
// Set the zed to the zapped behavior
|
|
simulated function SetZappedBehavior()
|
|
{
|
|
if( Role == Role_Authority )
|
|
{
|
|
Intelligence = BRAINS_Retarded; // burning dumbasses!
|
|
|
|
SetGroundSpeed(OriginalGroundSpeed * ZappedSpeedMod);
|
|
AirSpeed *= ZappedSpeedMod;
|
|
WaterSpeed *= ZappedSpeedMod;
|
|
|
|
// Make them less accurate while they are burning
|
|
if( Controller != none )
|
|
{
|
|
MonsterController(Controller).Accuracy = -5; // More chance of missing. (he's burning now, after all) :-D
|
|
}
|
|
}
|
|
|
|
// Set the forward movement anim to a random burning anim
|
|
MovementAnims[0] = BurningWalkFAnims[Rand(3)];
|
|
WalkAnims[0] = BurningWalkFAnims[Rand(3)];
|
|
|
|
// Set the rest of the movement anims to the headless anim (not sure if these ever even get played) - Ramm
|
|
MovementAnims[1] = BurningWalkAnims[0];
|
|
WalkAnims[1] = BurningWalkAnims[0];
|
|
MovementAnims[2] = BurningWalkAnims[1];
|
|
WalkAnims[2] = BurningWalkAnims[1];
|
|
MovementAnims[3] = BurningWalkAnims[2];
|
|
WalkAnims[3] = BurningWalkAnims[2];
|
|
}
|
|
|
|
// Turn off the on-fire behavior
|
|
simulated function UnSetZappedBehavior()
|
|
{
|
|
local int i;
|
|
|
|
if ( Role == Role_Authority )
|
|
{
|
|
Intelligence = default.Intelligence;
|
|
|
|
if( bBurnified )
|
|
{
|
|
SetGroundSpeed(GetOriginalGroundSpeed() * 0.80);
|
|
}
|
|
else
|
|
{
|
|
SetGroundSpeed(GetOriginalGroundSpeed());
|
|
}
|
|
AirSpeed = default.AirSpeed;
|
|
WaterSpeed = default.WaterSpeed;
|
|
|
|
// Set normal accuracy
|
|
if ( Controller != none )
|
|
{
|
|
MonsterController(Controller).Accuracy = MonsterController(Controller).default.Accuracy;
|
|
}
|
|
}
|
|
|
|
// restore regular anims
|
|
for ( i = 0; i < 4; i++ )
|
|
{
|
|
MovementAnims[i] = default.MovementAnims[i];
|
|
WalkAnims[i] = default.WalkAnims[i];
|
|
}
|
|
}
|
|
|
|
function TakeBileDamage()
|
|
{
|
|
Super.TakeDamage(2 + Rand(3), BileInstigator, Location, vect(0,0,0), LastBileDamagedByType);
|
|
}
|
|
|
|
simulated function StartBurnFX()
|
|
{
|
|
local class<emitter> Effect;
|
|
|
|
if( bDeleteMe )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// No real flames when low gore, make them smoke, smoking kills
|
|
if ( class'GameInfo'.static.UseLowGore() )
|
|
{
|
|
Effect = AltBurnEffect;
|
|
}
|
|
else
|
|
{
|
|
Effect = BurnEffect;
|
|
}
|
|
|
|
if ( FlamingFXs == None )
|
|
{
|
|
FlamingFXs = Spawn(Effect);
|
|
}
|
|
|
|
FlamingFXs.SetBase(Self);
|
|
FlamingFXs.Emitters[0].SkeletalMeshActor = self;
|
|
FlamingFXs.Emitters[0].UseSkeletalLocationAs = PTSU_SpawnOffset;
|
|
AttachEmitterEffect(Effect, HeadBone, Location, Rotation);
|
|
bBurnApplied = True;
|
|
}
|
|
|
|
simulated function StopBurnFX()
|
|
{
|
|
UnSetBurningBehavior();
|
|
RemoveFlamingEffects();
|
|
|
|
if ( FlamingFXs != None )
|
|
{
|
|
FlamingFXs.Kill();
|
|
}
|
|
|
|
bBurnApplied = False;
|
|
}
|
|
|
|
// High damage was taken, make em fall over.
|
|
function bool FlipOver()
|
|
{
|
|
if( Physics==PHYS_Falling )
|
|
{
|
|
SetPhysics(PHYS_Walking);
|
|
}
|
|
|
|
bShotAnim = true;
|
|
SetAnimAction('KnockDown');
|
|
Acceleration = vect(0, 0, 0);
|
|
Velocity.X = 0;
|
|
Velocity.Y = 0;
|
|
Controller.GoToState('WaitForAnim');
|
|
KFMonsterController(Controller).bUseFreezeHack = True;
|
|
Return True;
|
|
}
|
|
|
|
function AddVelocity( vector NewVelocity)
|
|
{
|
|
if( VSize(NewVelocity) > 50 )
|
|
{
|
|
Super.AddVelocity(NewVelocity);
|
|
}
|
|
}
|
|
|
|
// Important Block of code controlling how the Zombies (excluding the Bloat and Fleshpound who cannot be stunned, respond to damage from the
|
|
// various weapons in the game. The basic rule is that any damage amount equal to or greater than 40 points will cause a stun.
|
|
// There are exceptions with the fists however, which are substantially under the damage quota but can still cause stuns 50% of the time.
|
|
// Why? Cus if they didn't at least have that functionality, they would be fundamentally useless. And anyone willing to take on a hoarde of zombies
|
|
// with only the gloves on his hands, deserves more respect than that!
|
|
|
|
simulated function PlayTakeHit(vector HitLocation, int Damage, class<DamageType> DamageType)
|
|
{
|
|
local int FistStrikeStunChance;
|
|
|
|
if( Level.TimeSeconds - LastPainAnim < MinTimeBetweenPainAnims )
|
|
return;
|
|
|
|
// No anim if we're burning, we're already playing an anim
|
|
if ( !(bCrispified && bBurnified) /*DamageType.name != 'DamTypeBurned' && DamageType.name != 'DamTypeFlamethrower'*/ )
|
|
{
|
|
if( Damage>=5 )
|
|
PlayDirectionalHit(HitLocation);
|
|
else if (DamageType.name == 'DamTypeShotgun' || DamageType.name == 'DamTypeDBShotgun'
|
|
|| DamageType.name == 'DamTypeFrag' || DamageType.name == 'DamTypeAA12Shotgun'
|
|
|| DamageType.name == 'DamTypePipeBomb' || DamageType.name == 'DamTypeM79Grenade'
|
|
|| DamageType.name == 'DamTypeM32Grenade' || DamageType.name == 'DamTypeM203Grenade'
|
|
|| DamageType.name == 'DamTypeBenelli' || DamageType.name == 'DamTypeKSGShotgun'
|
|
|| DamageType.name == 'DamTypeTrenchgun' || DamageType.name == 'DamTypeNailgun'
|
|
|| DamageType.name == 'DamTypeSPShotgun' || DamageType.name == 'DamTypeSPGrenade'
|
|
|| DamageType.name == 'DamTypeSealSquealExplosion' || DamageType.name == 'DamTypeSeekerSixRocket')
|
|
PlayDirectionalHit(HitLocation);
|
|
else if (DamageType.name == 'DamTypeClaws')
|
|
{
|
|
FistStrikeStunChance = rand(10);
|
|
if ( FistStrikeStunChance > 5 )
|
|
PlayDirectionalHit(HitLocation);
|
|
}
|
|
else if (DamageType.name == 'DamTypeKnife')
|
|
{
|
|
FistStrikeStunChance = rand(10);
|
|
if ( FistStrikeStunChance > 7 )
|
|
PlayDirectionalHit(HitLocation);
|
|
}
|
|
else if (DamageType.name == 'DamTypeChainsaw')
|
|
PlayDirectionalHit(HitLocation);
|
|
else if (DamageType.name == 'DamTypeStunNade')
|
|
PlayDirectionalHit(HitLocation);
|
|
else if (DamageType.name == 'DamTypeCrossbowHeadshot' || DamageType.name == 'DamTypeCrossbuzzsaw'
|
|
|| DamageType.name == 'DamTypeCrossbowHeadShot' )
|
|
PlayDirectionalHit(HitLocation);
|
|
LastPainAnim = Level.TimeSeconds;
|
|
}
|
|
|
|
if( Level.TimeSeconds - LastPainSound < MinTimeBetweenPainSounds )
|
|
return;
|
|
|
|
LastPainSound = Level.TimeSeconds;
|
|
|
|
if ( class<DamTypeBurned>(DamageType) == none && class<DamTypeFlamethrower>(DamageType) == none )
|
|
{
|
|
PlaySound(HitSound[0], SLOT_Pain,1.25,,400);
|
|
}
|
|
}
|
|
|
|
simulated function DoDerezEffect(); // fuck no!
|
|
|
|
simulated function DoDamageFX( Name boneName, int Damage, class<DamageType> DamageType, Rotator r )
|
|
{
|
|
local float DismemberProbability;
|
|
local int RandBone;
|
|
local bool bDidSever;
|
|
|
|
//log("DamageFX bonename = "$boneName$" "$Level.TimeSeconds$" Damage "$Damage);
|
|
|
|
if( bDecapitated && !bPlayBrainSplash )
|
|
{
|
|
if( class<DamTypeMelee>(DamageType) != none )
|
|
{
|
|
HitFX[HitFxTicker].damtype = class'DamTypeMeleeDecapitation';
|
|
}
|
|
else if( class<DamTypeNailGun>(DamageType) != none )
|
|
{
|
|
HitFX[HitFxTicker].damtype = class'DamTypeProjectileDecap';
|
|
}
|
|
else
|
|
{
|
|
HitFX[HitFxTicker].damtype = class'DamTypeDecapitation';
|
|
}
|
|
|
|
if ( DamageType.default.bNeverSevers || class'GameInfo'.static.UseLowGore()
|
|
|| (Level.Game != none && Level.Game.PreventSever(self, boneName, Damage, DamageType)) )
|
|
{
|
|
HitFX[HitFxTicker].bSever = false;
|
|
}
|
|
else
|
|
{
|
|
HitFX[HitFxTicker].bSever = true;
|
|
}
|
|
|
|
HitFX[HitFxTicker].bone = HeadBone;
|
|
HitFX[HitFxTicker].rotDir = r;
|
|
HitFxTicker = HitFxTicker + 1;
|
|
if( HitFxTicker > ArrayCount(HitFX)-1 )
|
|
HitFxTicker = 0;
|
|
|
|
bPlayBrainSplash = true;
|
|
|
|
if( Damage > DamageType.default.HumanObliterationThreshhold && Damage != 1000 )
|
|
{
|
|
// Do nothing
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( FRand() > 0.3f || Damage > 30 || Health <= 0 /*|| DamageType == class 'DamTypeCrossbowHeadshot'*/)
|
|
{
|
|
HitFX[HitFxTicker].damtype = DamageType;
|
|
|
|
if( Health <= 0 /*|| DamageType == class 'DamTypeCrossbowHeadshot'*/)
|
|
{
|
|
switch( boneName )
|
|
{
|
|
case 'neck':
|
|
boneName = HeadBone;
|
|
break;
|
|
|
|
case LeftFootBone:
|
|
case 'lleg':
|
|
boneName = LeftThighBone;
|
|
break;
|
|
|
|
case RightFootBone:
|
|
case 'rleg':
|
|
boneName = RightThighBone;
|
|
break;
|
|
|
|
case RightHandBone:
|
|
case RightShoulderBone:
|
|
case 'rarm':
|
|
boneName = RightFArmBone;
|
|
break;
|
|
|
|
case LeftHandBone:
|
|
case LeftShoulderBone:
|
|
case 'larm':
|
|
boneName = LeftFArmBone;
|
|
break;
|
|
|
|
case 'None':
|
|
case 'spine':
|
|
boneName = FireRootBone;
|
|
break;
|
|
}
|
|
|
|
if( DamageType.default.bAlwaysSevers || (Damage == 1000) )
|
|
{
|
|
HitFX[HitFxTicker].bSever = true;
|
|
bDidSever = true;
|
|
if ( boneName == 'None' )
|
|
{
|
|
boneName = FireRootBone;
|
|
}
|
|
}
|
|
else if( DamageType.Default.GibModifier > 0.0 )
|
|
{
|
|
DismemberProbability = Abs( (Health - Damage*DamageType.Default.GibModifier) / 130.0f );
|
|
|
|
if( FRand() < DismemberProbability )
|
|
{
|
|
HitFX[HitFxTicker].bSever = true;
|
|
bDidSever = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( DamageType.default.bNeverSevers || class'GameInfo'.static.UseLowGore()
|
|
|| (Level.Game != none && Level.Game.PreventSever(self, boneName, Damage, DamageType)) )
|
|
{
|
|
HitFX[HitFxTicker].bSever = false;
|
|
bDidSever = false;
|
|
}
|
|
|
|
if ( HitFX[HitFxTicker].bSever )
|
|
{
|
|
if( !DamageType.default.bLocationalHit && (boneName == 'None' || boneName == FireRootBone ||
|
|
boneName == 'Spine' ))
|
|
{
|
|
RandBone = Rand(4);
|
|
|
|
switch( RandBone )
|
|
{
|
|
case 0:
|
|
boneName = LeftThighBone;
|
|
break;
|
|
case 1:
|
|
boneName = RightThighBone;
|
|
break;
|
|
case 2:
|
|
boneName = LeftFArmBone;
|
|
break;
|
|
case 3:
|
|
boneName = RightFArmBone;
|
|
break;
|
|
case 4:
|
|
boneName = HeadBone;
|
|
break;
|
|
default:
|
|
boneName = LeftThighBone;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( Health < 0 && Damage > DamageType.default.HumanObliterationThreshhold && Damage != 1000 && !class'GameInfo'.static.UseLowGore() )
|
|
{
|
|
boneName = 'obliterate';
|
|
}
|
|
|
|
HitFX[HitFxTicker].bone = boneName;
|
|
HitFX[HitFxTicker].rotDir = r;
|
|
HitFxTicker = HitFxTicker + 1;
|
|
if( HitFxTicker > ArrayCount(HitFX)-1 )
|
|
HitFxTicker = 0;
|
|
|
|
// If this was a really hardcore damage from an explosion, randomly spawn some arms and legs
|
|
if ( bDidSever && !DamageType.default.bLocationalHit && Damage > 200 && Damage != 1000 && !class'GameInfo'.static.UseLowGore() )
|
|
{
|
|
if ((Damage > 400 && FRand() < 0.3) || FRand() < 0.1 )
|
|
{
|
|
DoDamageFX(HeadBone,1000,DamageType,r);
|
|
DoDamageFX(LeftThighBone,1000,DamageType,r);
|
|
DoDamageFX(RightThighBone,1000,DamageType,r);
|
|
DoDamageFX(LeftFArmBone,1000,DamageType,r);
|
|
DoDamageFX(RightFArmBone,1000,DamageType,r);
|
|
}
|
|
if ( FRand() < 0.25 )
|
|
{
|
|
DoDamageFX(LeftThighBone,1000,DamageType,r);
|
|
DoDamageFX(RightThighBone,1000,DamageType,r);
|
|
if ( FRand() < 0.5 )
|
|
{
|
|
DoDamageFX(LeftFArmBone,1000,DamageType,r);
|
|
}
|
|
else
|
|
{
|
|
DoDamageFX(RightFArmBone,1000,DamageType,r);
|
|
}
|
|
}
|
|
else if ( FRand() < 0.35 )
|
|
DoDamageFX(LeftThighBone,1000,DamageType,r);
|
|
else if ( FRand() < 0.5 )
|
|
DoDamageFX(RightThighBone,1000,DamageType,r);
|
|
else if ( FRand() < 0.75 )
|
|
{
|
|
if ( FRand() < 0.5 )
|
|
{
|
|
DoDamageFX(LeftFArmBone,1000,DamageType,r);
|
|
}
|
|
else
|
|
{
|
|
DoDamageFX(RightFArmBone,1000,DamageType,r);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Overriden to allow for specifying velocities for the gibs
|
|
simulated function KFSpawnGiblet( class<Gib> GibClass, Vector Location, Rotator Rotation, float GibPerterbation, optional float GibVelocity )
|
|
{
|
|
local Gib Giblet;
|
|
local Vector Direction, Dummy;
|
|
|
|
if( (GibClass == None) || class'GameInfo'.static.UseLowGore() )
|
|
return;
|
|
|
|
Instigator = self;
|
|
Giblet = Spawn( GibClass,,, Location, Rotation );
|
|
|
|
if( Giblet == None )
|
|
return;
|
|
|
|
Giblet.SetDrawScale(Giblet.DrawScale * (CollisionRadius*CollisionHeight)/1100); // 1100 = 25 * 44
|
|
GibPerterbation *= 32768.0;
|
|
Rotation.Pitch += ( FRand() * 2.0 * GibPerterbation ) - GibPerterbation;
|
|
Rotation.Yaw += ( FRand() * 2.0 * GibPerterbation ) - GibPerterbation;
|
|
Rotation.Roll += ( FRand() * 2.0 * GibPerterbation ) - GibPerterbation;
|
|
|
|
GetAxes( Rotation, Dummy, Dummy, Direction );
|
|
|
|
if( GibVelocity > 0 )
|
|
{
|
|
Giblet.Velocity = Velocity + Normal(Direction) * (GibVelocity + (GibVelocity * (FRand() * 0.25)));
|
|
}
|
|
else
|
|
{
|
|
Giblet.Velocity = Velocity + Normal(Direction) * 512;
|
|
}
|
|
}
|
|
|
|
//Stops the green shit when a player dies.
|
|
simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
|
|
{
|
|
local float frame, rate;
|
|
local name seq;
|
|
local LavaDeath LD;
|
|
local MiscEmmiter BE;
|
|
|
|
AmbientSound = None;
|
|
bCanTeleport = false; // sjs - fix karma going crazy when corpses land on teleporters
|
|
bReplicateMovement = false;
|
|
bTearOff = true;
|
|
bPlayedDeath = true;
|
|
StopBurnFX();
|
|
|
|
if (CurrentCombo != None)
|
|
CurrentCombo.Destroy();
|
|
|
|
HitDamageType = DamageType; // these are replicated to other clients
|
|
TakeHitLocation = HitLoc;
|
|
|
|
bSTUNNED = false;
|
|
bMovable = true;
|
|
|
|
if ( class<DamTypeBurned>(DamageType) != none || class<DamTypeFlamethrower>(DamageType) != none )
|
|
{
|
|
ZombieCrispUp();
|
|
}
|
|
|
|
ProcessHitFX() ;
|
|
|
|
if ( DamageType != None )
|
|
{
|
|
if ( DamageType.default.bSkeletize )
|
|
{
|
|
SetOverlayMaterial(DamageType.Default.DamageOverlayMaterial, 4.0, true);
|
|
if (!bSkeletized)
|
|
{
|
|
if ( (Level.NetMode != NM_DedicatedServer) && (SkeletonMesh != None) )
|
|
{
|
|
if ( DamageType.default.bLeaveBodyEffect )
|
|
{
|
|
BE = spawn(class'MiscEmmiter',self);
|
|
if ( BE != None )
|
|
{
|
|
BE.DamageType = DamageType;
|
|
BE.HitLoc = HitLoc;
|
|
bFrozenBody = true;
|
|
}
|
|
}
|
|
GetAnimParams( 0, seq, frame, rate );
|
|
LinkMesh(SkeletonMesh, true);
|
|
Skins.Length = 0;
|
|
PlayAnim(seq, 0, 0);
|
|
SetAnimFrame(frame);
|
|
}
|
|
if (Physics == PHYS_Walking)
|
|
Velocity = Vect(0,0,0);
|
|
SetTearOffMomemtum(GetTearOffMomemtum() * 0.25);
|
|
bSkeletized = true;
|
|
if ( (Level.NetMode != NM_DedicatedServer) && (DamageType == class'FellLava') )
|
|
{
|
|
LD = spawn(class'LavaDeath', , , Location + vect(0, 0, 10), Rotation );
|
|
if ( LD != None )
|
|
LD.SetBase(self);
|
|
//PlaySound( sound'WeaponSounds.BExplosion5', SLOT_None, 1.5*TransientSoundVolume );
|
|
}
|
|
}
|
|
}
|
|
else if ( DamageType.Default.DeathOverlayMaterial != None )
|
|
SetOverlayMaterial(DamageType.Default.DeathOverlayMaterial, DamageType.default.DeathOverlayTime, true);
|
|
else if ( (DamageType.Default.DamageOverlayMaterial != None) && (Level.DetailMode != DM_Low) && !Level.bDropDetail )
|
|
SetOverlayMaterial(DamageType.Default.DamageOverlayMaterial, 2*DamageType.default.DamageOverlayTime, true);
|
|
}
|
|
|
|
// stop shooting
|
|
AnimBlendParams(1, 0.0);
|
|
FireState = FS_None;
|
|
|
|
// Try to adjust around performance
|
|
//log(Level.DetailMode);
|
|
|
|
LifeSpan = RagdollLifeSpan;
|
|
|
|
GotoState('ZombieDying');
|
|
if ( BE != None )
|
|
return;
|
|
PlayDyingAnimation(DamageType, HitLoc);
|
|
}
|
|
|
|
|
|
State ZombieDying extends Dying
|
|
{
|
|
ignores AnimEnd, Trigger, Bump, HitWall, HeadVolumeChange, PhysicsVolumeChange, Falling, BreathTimer, Died, RangedAttack; //Tick
|
|
|
|
function bool CanGetOutOfWay()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
simulated function Landed(vector HitNormal)
|
|
{
|
|
//SetPhysics(PHYS_None);
|
|
SetCollision(false, false, false);
|
|
if( !bDestroyNextTick )
|
|
{
|
|
Disable('Tick');
|
|
}
|
|
}
|
|
|
|
simulated function Timer()
|
|
{
|
|
local KarmaParamsSkel skelParams;
|
|
|
|
if( bDestroyNextTick )
|
|
{
|
|
// If we've flagged this character to be destroyed next tick, handle that
|
|
if( TimeSetDestroyNextTickTime < Level.TimeSeconds )
|
|
{
|
|
Destroy();
|
|
}
|
|
else
|
|
{
|
|
SetTimer(0.01, false);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( !PlayerCanSeeMe() )
|
|
{
|
|
StartDeRes();
|
|
Destroy();
|
|
}
|
|
// If we are running out of life, but we still haven't come to rest, force the de-res.
|
|
// unless pawn is the viewtarget of a player who used to own it
|
|
else if ( LifeSpan <= DeResTime && bDeRes == false )
|
|
{
|
|
skelParams = KarmaParamsSkel(KParams);
|
|
|
|
skelParams.bKImportantRagdoll = false;
|
|
|
|
// spawn derez
|
|
bDeRes=true;
|
|
}
|
|
else
|
|
{
|
|
SetTimer(1.0, false);
|
|
}
|
|
}
|
|
|
|
simulated function BeginState()
|
|
{
|
|
if( bDestroyNextTick )
|
|
{
|
|
// If we've flagged this character to be destroyed next tick, handle that
|
|
if( TimeSetDestroyNextTickTime < Level.TimeSeconds )
|
|
{
|
|
Destroy();
|
|
}
|
|
else
|
|
{
|
|
SetTimer(0.01, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( bTearOff && (Level.NetMode == NM_DedicatedServer) || class'GameInfo'.static.UseLowGore() )
|
|
LifeSpan = 1.0;
|
|
else
|
|
SetTimer(2.0, false);
|
|
}
|
|
|
|
SetPhysics(PHYS_Falling);
|
|
if ( Controller != None )
|
|
{
|
|
Controller.Destroy();
|
|
}
|
|
}
|
|
|
|
simulated function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class<DamageType> damageType, optional int HitIndex )
|
|
{
|
|
local Vector HitNormal, shotDir;
|
|
local Vector PushLinVel, PushAngVel;
|
|
local Name HitBone;
|
|
local float HitBoneDist;
|
|
local bool bIsHeadshot;
|
|
local vector HitRay;
|
|
|
|
if ( bFrozenBody || bRubbery )
|
|
return;
|
|
|
|
if( Physics == PHYS_KarmaRagdoll )
|
|
{
|
|
// Can't shoot corpses during de-res
|
|
if ( bDeRes )
|
|
return;
|
|
|
|
// Throw the body if its a rocket explosion or shock combo
|
|
if( damageType.Default.bThrowRagdoll )
|
|
{
|
|
shotDir = Normal(Momentum);
|
|
PushLinVel = (RagDeathVel * shotDir) + vect(0, 0, 250);
|
|
PushAngVel = Normal(shotDir Cross vect(0, 0, 1)) * -18000;
|
|
KSetSkelVel( PushLinVel, PushAngVel );
|
|
}
|
|
else if( damageType.Default.bRagdollBullet )
|
|
{
|
|
if ( Momentum == vect(0,0,0) )
|
|
Momentum = HitLocation - InstigatedBy.Location;
|
|
if ( FRand() < 0.65 )
|
|
{
|
|
if ( Velocity.Z <= 0 )
|
|
PushLinVel = vect(0,0,40);
|
|
PushAngVel = Normal(Normal(Momentum) Cross vect(0, 0, 1)) * -8000 ;
|
|
PushAngVel.X *= 0.5;
|
|
PushAngVel.Y *= 0.5;
|
|
PushAngVel.Z *= 4;
|
|
KSetSkelVel( PushLinVel, PushAngVel );
|
|
}
|
|
PushLinVel = RagShootStrength*Normal(Momentum);
|
|
KAddImpulse(PushLinVel, HitLocation);
|
|
if ( (LifeSpan > 0) && (LifeSpan < DeResTime + 2) )
|
|
LifeSpan += 0.2;
|
|
}
|
|
else
|
|
{
|
|
PushLinVel = RagShootStrength*Normal(Momentum);
|
|
KAddImpulse(PushLinVel, HitLocation);
|
|
}
|
|
}
|
|
|
|
if (Damage > 0)
|
|
{
|
|
Health -= Damage;
|
|
|
|
if ( !bDecapitated && class<KFWeaponDamageType>(damageType)!=none &&
|
|
class<KFWeaponDamageType>(damageType).default.bCheckForHeadShots )
|
|
{
|
|
bIsHeadShot = IsHeadShot(HitLocation, normal(Momentum), 1.0);
|
|
}
|
|
|
|
if( bIsHeadShot )
|
|
RemoveHead();
|
|
|
|
HitRay = vect(0,0,0);
|
|
if( InstigatedBy != none )
|
|
HitRay = Normal(HitLocation-(InstigatedBy.Location+(vect(0,0,1)*InstigatedBy.EyeHeight)));
|
|
|
|
CalcHitLoc( HitLocation, HitRay, HitBone, HitBoneDist );
|
|
|
|
if( InstigatedBy != None )
|
|
HitNormal = Normal( Normal(InstigatedBy.Location-HitLocation) + VRand() * 0.2 + vect(0,0,2.8) );
|
|
else
|
|
HitNormal = Normal( Vect(0,0,1) + VRand() * 0.2 + vect(0,0,2.8) );
|
|
|
|
// Actually do blood on a client
|
|
PlayHit(Damage, InstigatedBy, hitLocation, damageType, Momentum);
|
|
|
|
DoDamageFX( HitBone, Damage, DamageType, Rotator(HitNormal) );
|
|
}
|
|
|
|
if ( (DamageType.Default.DamageOverlayMaterial != None) && (Level.DetailMode != DM_Low) && !Level.bDropDetail )
|
|
SetOverlayMaterial(DamageType.Default.DamageOverlayMaterial, DamageType.default.DamageOverlayTime, true);
|
|
}
|
|
}
|
|
|
|
simulated function Timer()
|
|
{
|
|
// bSTUNNED variable actually indicates flinching, not stunning! So don't get confused.
|
|
bSTUNNED = false;
|
|
|
|
// If burn tick count > 0, call TakeFireDamage() function.
|
|
// Otherwise stop burning and turn off the timer
|
|
if (BurnDown > 0)
|
|
{
|
|
// Every tick LastBurnDamage is increased by 3 + random value from 0 to 2 (excluding)
|
|
TakeFireDamage(LastBurnDamage + rand(2) + 3 , LastDamagedBy);
|
|
SetTimer(1.0,false); // Sets timer function to be executed each second
|
|
}
|
|
else
|
|
{
|
|
UnSetBurningBehavior();
|
|
|
|
RemoveFlamingEffects();
|
|
StopBurnFX();
|
|
SetTimer(0, false); // Disable timer
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
switch( HitFX[SimHitFxTicker].bone )
|
|
{
|
|
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].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( 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 ) ;;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle doing the decapitation hit effects
|
|
simulated function DecapFX( Vector DecapLocation, Rotator DecapRotation, bool bSpawnDetachedHead, optional bool bNoBrainBits )
|
|
{
|
|
local float GibPerterbation;
|
|
local BrainSplash SplatExplosion;
|
|
local int i;
|
|
|
|
// Do the cute version of the Decapitation
|
|
if ( class'GameInfo'.static.UseLowGore() )
|
|
{
|
|
CuteDecapFX();
|
|
|
|
return;
|
|
}
|
|
|
|
bNoBrainBitEmitter = bNoBrainBits;
|
|
|
|
GibPerterbation = 0.060000; // damageType.default.GibPerterbation;
|
|
|
|
if(bSpawnDetachedHead)
|
|
{
|
|
SpecialHideHead();
|
|
}
|
|
else
|
|
{
|
|
HideBone(HeadBone);
|
|
}
|
|
|
|
if( bSpawnDetachedHead )
|
|
{
|
|
SpawnSeveredGiblet( DetachedHeadClass, DecapLocation, DecapRotation, GibPerterbation, GetBoneRotation(HeadBone) );
|
|
}
|
|
|
|
// Plug in headless anims if we have them
|
|
for( i = 0; i < 4; i++ )
|
|
{
|
|
if( HeadlessWalkAnims[i] != '' && HasAnim(HeadlessWalkAnims[i]) )
|
|
{
|
|
MovementAnims[i] = HeadlessWalkAnims[i];
|
|
WalkAnims[i] = HeadlessWalkAnims[i];
|
|
}
|
|
}
|
|
|
|
if ( !bSpawnDetachedHead && !bNoBrainBits && EffectIsRelevant(DecapLocation,false) )
|
|
{
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',DecapLocation, self.Rotation, GibPerterbation, 250 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrainb',DecapLocation, self.Rotation, GibPerterbation, 250 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',DecapLocation, self.Rotation, GibPerterbation, 250 ) ;
|
|
}
|
|
SplatExplosion = Spawn(class 'BrainSplash',self,, DecapLocation );
|
|
}
|
|
|
|
// Handle hiding the head when its been melee chopped off
|
|
simulated function SpecialHideHead()
|
|
{
|
|
local int BoneScaleSlot;
|
|
local coords boneCoords;
|
|
|
|
// Only scale the bone down once
|
|
if( SeveredHead == none )
|
|
{
|
|
boneScaleSlot = 4;
|
|
SeveredHead = Spawn(SeveredHeadAttachClass,self);
|
|
SeveredHead.SetDrawScale(SeveredHeadAttachScale);
|
|
boneCoords = GetBoneCoords( 'neck' );
|
|
AttachEmitterEffect( NeckSpurtNoGibEmitterClass, 'neck', boneCoords.Origin, rot(0,0,0) );
|
|
AttachToBone(SeveredHead, 'neck');
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetBoneScale(BoneScaleSlot, 0.0, 'head');
|
|
}
|
|
|
|
simulated function CuteDecapFX()
|
|
{
|
|
local int LeftRight;
|
|
|
|
LeftRight = 1;
|
|
|
|
if ( rand(10) > 5 )
|
|
{
|
|
LeftRight = -1;
|
|
}
|
|
|
|
NeckRot.Yaw = -clamp(rand(24000), 14000, 24000);
|
|
NeckRot.Roll = LeftRight * clamp(rand(8000), 2000, 8000);
|
|
NeckRot.Pitch = LeftRight * clamp(rand(12000), 2000, 12000);
|
|
|
|
SetBoneRotation('neck', NeckRot);
|
|
|
|
RemoveHead();
|
|
}
|
|
|
|
|
|
simulated function SpawnSeveredGiblet( class<SeveredAppendage> GibClass, Vector Location, Rotator Rotation, float GibPerterbation, rotator SpawnRotation )
|
|
{
|
|
local SeveredAppendage Giblet;
|
|
local Vector Direction, Dummy;
|
|
|
|
if( (GibClass == None) || class'GameInfo'.static.UseLowGore() )
|
|
return;
|
|
|
|
Instigator = self;
|
|
Giblet = Spawn( GibClass,,, Location, SpawnRotation );
|
|
if( Giblet == None )
|
|
return;
|
|
Giblet.SpawnTrail();
|
|
|
|
GibPerterbation *= 32768.0;
|
|
Rotation.Pitch += ( FRand() * 2.0 * GibPerterbation ) - GibPerterbation;
|
|
Rotation.Yaw += ( FRand() * 2.0 * GibPerterbation ) - GibPerterbation;
|
|
Rotation.Roll += ( FRand() * 2.0 * GibPerterbation ) - GibPerterbation;
|
|
|
|
GetAxes( Rotation, Dummy, Dummy, Direction );
|
|
|
|
Giblet.Velocity = Velocity + Normal(Direction) * (Giblet.MaxSpeed + (Giblet.MaxSpeed/2) * FRand());
|
|
|
|
// Give a little upward motion to the decapitated head
|
|
if( class<SeveredHead>(GibClass) != none )
|
|
{
|
|
Giblet.Velocity.Z += 50;
|
|
}
|
|
|
|
//Giblet.LifeSpan = self.RagdollLifeSpan;
|
|
}
|
|
|
|
simulated function HideBone(name boneName)
|
|
{
|
|
local int BoneScaleSlot;
|
|
local coords boneCoords;
|
|
local bool bValidBoneToHide;
|
|
|
|
if( boneName == LeftThighBone )
|
|
{
|
|
boneScaleSlot = 0;
|
|
bValidBoneToHide = true;
|
|
if( SeveredLeftLeg == none )
|
|
{
|
|
SeveredLeftLeg = Spawn(SeveredLegAttachClass,self);
|
|
SeveredLeftLeg.SetDrawScale(SeveredLegAttachScale);
|
|
boneCoords = GetBoneCoords( 'lleg' );
|
|
AttachEmitterEffect( LimbSpurtEmitterClass, 'lleg', boneCoords.Origin, rot(0,0,0) );
|
|
AttachToBone(SeveredLeftLeg, 'lleg');
|
|
}
|
|
}
|
|
else if ( boneName == RightThighBone )
|
|
{
|
|
boneScaleSlot = 1;
|
|
bValidBoneToHide = true;
|
|
if( SeveredRightLeg == none )
|
|
{
|
|
SeveredRightLeg = Spawn(SeveredLegAttachClass,self);
|
|
SeveredRightLeg.SetDrawScale(SeveredLegAttachScale);
|
|
boneCoords = GetBoneCoords( 'rleg' );
|
|
AttachEmitterEffect( LimbSpurtEmitterClass, 'rleg', boneCoords.Origin, rot(0,0,0) );
|
|
AttachToBone(SeveredRightLeg, 'rleg');
|
|
}
|
|
}
|
|
else if( boneName == RightFArmBone )
|
|
{
|
|
boneScaleSlot = 2;
|
|
bValidBoneToHide = true;
|
|
if( SeveredRightArm == none )
|
|
{
|
|
SeveredRightArm = Spawn(SeveredArmAttachClass,self);
|
|
SeveredRightArm.SetDrawScale(SeveredArmAttachScale);
|
|
boneCoords = GetBoneCoords( 'rarm' );
|
|
AttachEmitterEffect( LimbSpurtEmitterClass, 'rarm', boneCoords.Origin, rot(0,0,0) );
|
|
AttachToBone(SeveredRightArm, 'rarm');
|
|
}
|
|
}
|
|
else if ( boneName == LeftFArmBone )
|
|
{
|
|
boneScaleSlot = 3;
|
|
bValidBoneToHide = true;
|
|
if( SeveredLeftArm == none )
|
|
{
|
|
SeveredLeftArm = Spawn(SeveredArmAttachClass,self);
|
|
SeveredLeftArm.SetDrawScale(SeveredArmAttachScale);
|
|
boneCoords = GetBoneCoords( 'larm' );
|
|
AttachEmitterEffect( LimbSpurtEmitterClass, 'larm', boneCoords.Origin, rot(0,0,0) );
|
|
AttachToBone(SeveredLeftArm, 'larm');
|
|
}
|
|
}
|
|
else if ( boneName == HeadBone )
|
|
{
|
|
// Only scale the bone down once
|
|
if( SeveredHead == none )
|
|
{
|
|
bValidBoneToHide = true;
|
|
boneScaleSlot = 4;
|
|
SeveredHead = Spawn(SeveredHeadAttachClass,self);
|
|
SeveredHead.SetDrawScale(SeveredHeadAttachScale);
|
|
boneCoords = GetBoneCoords( 'neck' );
|
|
if( bNoBrainBitEmitter )
|
|
{
|
|
AttachEmitterEffect( NeckSpurtNoGibEmitterClass, 'neck', boneCoords.Origin, rot(0,0,0) );
|
|
}
|
|
else
|
|
{
|
|
AttachEmitterEffect( NeckSpurtEmitterClass, 'neck', boneCoords.Origin, rot(0,0,0) );
|
|
}
|
|
AttachToBone(SeveredHead, 'neck');
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if ( boneName == 'spine' )
|
|
{
|
|
bValidBoneToHide = true;
|
|
boneScaleSlot = 5;
|
|
}
|
|
|
|
// Only hide the bone if it is one of the arms, legs, or head, don't hide other misc bones
|
|
if( bValidBoneToHide )
|
|
{
|
|
SetBoneScale(BoneScaleSlot, 0.0, BoneName);
|
|
}
|
|
}
|
|
|
|
// Used to attach an emitter instead of an xemitter
|
|
simulated function AttachEmitterEffect( class<Emitter> EmitterClass, Name BoneName, Vector Location, Rotator Rotation )
|
|
{
|
|
local Actor a;
|
|
local int i;
|
|
|
|
if( BoneName == 'None' )
|
|
return;
|
|
|
|
for( i = 0; i < Attached.Length; i++ )
|
|
{
|
|
if( Attached[i] == None )
|
|
continue;
|
|
|
|
if( Attached[i].AttachmentBone != BoneName )
|
|
continue;
|
|
|
|
if( ClassIsChildOf( EmitterClass, Attached[i].Class ) )
|
|
return;
|
|
}
|
|
|
|
a = Spawn( EmitterClass,,, Location, Rotation );
|
|
|
|
if( !AttachToBone( a, BoneName ) )
|
|
{
|
|
log( "Couldn't attach "$EmitterClass$" to "$BoneName, 'Error' );
|
|
a.Destroy();
|
|
return;
|
|
}
|
|
|
|
for( i = 0; i < Attached.length; i++ )
|
|
{
|
|
if( Attached[i] == a )
|
|
break;
|
|
}
|
|
|
|
a.SetRelativeRotation( Rotation );
|
|
}
|
|
|
|
// Maybe spawn some chunks when the player gets obliterated
|
|
simulated function SpawnGibs(Rotator HitRotation, float ChunkPerterbation)
|
|
{
|
|
bGibbed = true;
|
|
PlayDyingSound();
|
|
|
|
if ( class'GameInfo'.static.UseLowGore() )
|
|
return;
|
|
|
|
if ( FlamingFXs != none )
|
|
{
|
|
FlamingFXs.Emitters[0].SkeletalMeshActor = none;
|
|
FlamingFXs.Destroy();
|
|
}
|
|
|
|
if( ObliteratedEffectClass != none )
|
|
Spawn( ObliteratedEffectClass,,, Location, HitRotation );
|
|
|
|
super.SpawnGibs(HitRotation,ChunkPerterbation);
|
|
|
|
if ( FRand() < 0.1 )
|
|
{
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrainb',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrainb',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
|
|
SpawnSeveredGiblet( DetachedLegClass, Location, HitRotation, ChunkPerterbation, HitRotation );
|
|
SpawnSeveredGiblet( DetachedLegClass, Location, HitRotation, ChunkPerterbation, HitRotation );
|
|
SpawnSeveredGiblet( DetachedArmClass, Location, HitRotation, ChunkPerterbation, HitRotation );
|
|
|
|
if ( DetachedSpecialArmClass != None )
|
|
{
|
|
SpawnSeveredGiblet( DetachedSpecialArmClass, Location, HitRotation, ChunkPerterbation, HitRotation );
|
|
}
|
|
else
|
|
{
|
|
SpawnSeveredGiblet( DetachedArmClass, Location, HitRotation, ChunkPerterbation, HitRotation );
|
|
}
|
|
}
|
|
else if ( FRand() < 0.25 )
|
|
{
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrainb',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrainb',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
|
|
SpawnSeveredGiblet( DetachedLegClass, Location, HitRotation, ChunkPerterbation, HitRotation );
|
|
SpawnSeveredGiblet( DetachedLegClass, Location, HitRotation, ChunkPerterbation, HitRotation );
|
|
if ( FRand() < 0.5 )
|
|
{
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
SpawnSeveredGiblet( DetachedArmClass, Location, HitRotation, ChunkPerterbation, HitRotation );
|
|
}
|
|
}
|
|
else if ( FRand() < 0.35 )
|
|
{
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrainb',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
SpawnSeveredGiblet( DetachedLegClass, Location, HitRotation, ChunkPerterbation, HitRotation );
|
|
}
|
|
else if ( FRand() < 0.5 )
|
|
{
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrainb',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
KFSpawnGiblet( class 'KFMod.KFGibBrain',Location, HitRotation, ChunkPerterbation, 500 ) ;
|
|
SpawnSeveredGiblet( DetachedArmClass, Location, HitRotation, ChunkPerterbation, HitRotation );
|
|
}
|
|
}
|
|
|
|
|
|
simulated function StartDeRes()
|
|
{
|
|
if( Level.NetMode == NM_DedicatedServer )
|
|
return;
|
|
|
|
AmbientGlow=0;
|
|
MaxLights=5;
|
|
|
|
if( Physics == PHYS_KarmaRagdoll )
|
|
{
|
|
// Remove flames
|
|
RemoveFlamingEffects();
|
|
KSetBlockKarma(true);
|
|
// Turn off any overlays
|
|
SetOverlayMaterial(None, 0.0f, true);
|
|
SetCollision(true, true, true);
|
|
}
|
|
}
|
|
|
|
function bool CanAttack(Actor A)
|
|
{
|
|
if (A == none)
|
|
return false;
|
|
if(bSTUNNED)
|
|
return false;
|
|
if(KFDoorMover(A)!=none)
|
|
return true;
|
|
else if(KFHumanPawn(A)!=none && KFHumanPawn(A).Health <= 0)
|
|
return ( VSize(A.Location - Location) < MeleeRange + CollisionRadius);
|
|
else return ( VSize(A.Location - Location) < MeleeRange + CollisionRadius + A.CollisionRadius );
|
|
}
|
|
|
|
function DoorAttack(Actor A)
|
|
{
|
|
if ( bShotAnim || Physics == PHYS_Swimming)
|
|
return;
|
|
else if ( A!=None )
|
|
{
|
|
bShotAnim = true;
|
|
|
|
SetAnimAction('DoorBash');
|
|
GotoState('DoorBashing');
|
|
//PlaySound(sound'Claw2s', SLOT_None); KFTODO: Replace this
|
|
return;
|
|
}
|
|
}
|
|
|
|
function CorpseAttack(Actor A)
|
|
{
|
|
if ( bShotAnim || Physics == PHYS_Swimming)
|
|
return;
|
|
Velocity.X = 0;
|
|
Velocity.Y = 0;
|
|
Acceleration = vect(0,0,0);
|
|
bShotAnim = true;
|
|
SetAnimAction('ZombieFeed');
|
|
Health+=(1+Rand(3));
|
|
Health = Min(Health,Default.Health*1.5);
|
|
}
|
|
function RangedAttack(Actor A)
|
|
{
|
|
if ( bShotAnim || Physics == PHYS_Swimming)
|
|
return;
|
|
else if ( CanAttack(A) )
|
|
{
|
|
bShotAnim = true;
|
|
SetAnimAction('Claw');
|
|
//PlaySound(sound'Claw2s', SLOT_None); KFTODO: Replace this
|
|
Controller.bPreparingMove = true;
|
|
Acceleration = vect(0,0,0);
|
|
return;
|
|
}
|
|
}
|
|
simulated event SetAnimAction(name NewAction)
|
|
{
|
|
local int meleeAnimIndex;
|
|
|
|
if( NewAction=='' )
|
|
Return;
|
|
if(NewAction == 'Claw')
|
|
{
|
|
meleeAnimIndex = Rand(3);
|
|
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 bool AnimNeedsWait(name TestAnim)
|
|
{
|
|
if( ExpectingChannel == 0 )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
simulated function int DoAnimAction( name AnimName )
|
|
{
|
|
if( AnimName=='HitF' || AnimName=='HitF2' || AnimName=='HitF3' || AnimName==KFHitFront || AnimName==KFHitBack || AnimName==KFHitRight
|
|
|| AnimName==KFHitLeft )
|
|
{
|
|
AnimBlendParams(1, 1.0, 0.0,, SpineBone1);
|
|
PlayAnim(AnimName,, 0.1, 1);
|
|
return 1;
|
|
}
|
|
|
|
PlayAnim(AnimName,,0.1);
|
|
return 0;
|
|
}
|
|
|
|
simulated function AnimEnd(int Channel)
|
|
{
|
|
/* local name Sequence;
|
|
local float Frame, Rate;
|
|
|
|
|
|
GetAnimParams( ExpectingChannel, Sequence, Frame, Rate );
|
|
|
|
log(Level.TimeSeconds$" "$self$" "$GetStateName()$" AnimEnd for Exp Chan "$ExpectingChannel$" = "$Sequence$" Channel: "$Channel);
|
|
|
|
GetAnimParams( 0, Sequence, Frame, Rate );
|
|
log(self$" "$GetStateName()$" AnimEnd for Chan 0 = "$Sequence);
|
|
|
|
GetAnimParams( 1, Sequence, Frame, Rate );
|
|
log(self$" "$GetStateName()$" AnimEnd for Chan 1 = "$Sequence);
|
|
|
|
log(self$" "$GetStateName()$" AnimEnd bShotAnim = "$bShotAnim);*/
|
|
|
|
|
|
AnimAction = '';
|
|
if ( bShotAnim && Channel==ExpectingChannel )
|
|
{
|
|
bShotAnim = false;
|
|
if( Controller!=None )
|
|
Controller.bPreparingMove = false;
|
|
}
|
|
if( !bPhysicsAnimUpdate && Channel==0 )
|
|
bPhysicsAnimUpdate = Default.bPhysicsAnimUpdate;
|
|
Super(xPawn).AnimEnd(Channel);
|
|
}
|
|
|
|
simulated function HandleBumpGlass()
|
|
{
|
|
Acceleration = vect(0,0,0);
|
|
Velocity = vect(0,0,0);
|
|
|
|
SetAnimAction(MeleeAnims[0]);
|
|
bShotAnim = true;
|
|
controller.GotoState('WaitForAnim');
|
|
}
|
|
|
|
simulated function StoodUp();
|
|
simulated function FellDown();
|
|
|
|
function ClawDamageTarget()
|
|
{
|
|
local vector PushDir;
|
|
local float UsedMeleeDamage;
|
|
|
|
if( MeleeDamage > 1 )
|
|
{
|
|
UsedMeleeDamage = (MeleeDamage - (MeleeDamage * 0.05)) + (MeleeDamage * (FRand() * 0.1));
|
|
}
|
|
else
|
|
{
|
|
UsedMeleeDamage = MeleeDamage;
|
|
}
|
|
|
|
if(Controller!=none && Controller.Target!=none)
|
|
PushDir = (damageForce * Normal(Controller.Target.Location - Location));
|
|
else PushDir = damageForce * vector(Rotation);
|
|
// Melee damage is +/- 10% of default
|
|
if ( MeleeDamageTarget(UsedMeleeDamage, PushDir) )
|
|
{
|
|
PlaySound(MeleeAttackHitSound, SLOT_Interact, 2.0);
|
|
}
|
|
}
|
|
|
|
function ZombieMoan() // Moved from Controller to here (so we don't need an own controller for each moan type).
|
|
{
|
|
PlaySound(MoanVoice, SLOT_Misc, MoanVolume,,250.0);
|
|
}
|
|
|
|
function RemoveHead()
|
|
{
|
|
local int i;
|
|
|
|
Intelligence = BRAINS_Retarded; // Headless dumbasses!
|
|
|
|
bDecapitated = true;
|
|
DECAP = true;
|
|
DecapTime = Level.TimeSeconds;
|
|
|
|
Velocity = vect(0,0,0);
|
|
SetAnimAction('HitF');
|
|
SetGroundSpeed(GroundSpeed *= 0.80);
|
|
AirSpeed *= 0.8;
|
|
WaterSpeed *= 0.8;
|
|
|
|
// No more raspy breathin'...cuz he has no throat or mouth :S
|
|
AmbientSound = MiscSound;
|
|
|
|
//TODO - do we need to inform the controller that we can't move owing to lack of head,
|
|
// or is that handled elsewhere
|
|
if ( Controller != none )
|
|
{
|
|
MonsterController(Controller).Accuracy = -5; // More chance of missing. (he's headless now, after all) :-D
|
|
}
|
|
|
|
// Head explodes, causing additional hurty.
|
|
if( KFPawn(LastDamagedBy)!=None )
|
|
{
|
|
TakeDamage( LastDamageAmount + 0.25 * HealthMax , LastDamagedBy, LastHitLocation, LastMomentum, LastDamagedByType);
|
|
|
|
if ( BurnDown > 0 )
|
|
{
|
|
KFSteamStatsAndAchievements(KFPawn(LastDamagedBy).PlayerReplicationInfo.SteamStatsAndAchievements).AddBurningDecapKill(class'KFGameType'.static.GetCurrentMapName(Level));
|
|
}
|
|
}
|
|
|
|
if( Health > 0 )
|
|
{
|
|
BleedOutTime = Level.TimeSeconds + BleedOutDuration;
|
|
}
|
|
|
|
//TODO - Find right place for this
|
|
// He's got no head so biting is out.
|
|
if (MeleeAnims[2] == 'Claw3')
|
|
MeleeAnims[2] = 'Claw2';
|
|
if (MeleeAnims[1] == 'Claw3')
|
|
MeleeAnims[1] = 'Claw1';
|
|
|
|
// Plug in headless anims if we have them
|
|
for( i = 0; i < 4; i++ )
|
|
{
|
|
if( HeadlessWalkAnims[i] != '' && HasAnim(HeadlessWalkAnims[i]) )
|
|
{
|
|
MovementAnims[i] = HeadlessWalkAnims[i];
|
|
WalkAnims[i] = HeadlessWalkAnims[i];
|
|
}
|
|
}
|
|
|
|
PlaySound(DecapitationSound, SLOT_Misc,1.30,true,525);
|
|
}
|
|
|
|
function TakeDamage(int Damage, Pawn instigatedBy, Vector hitlocation, Vector momentum, class<DamageType> damageType, optional int HitIndex )
|
|
{
|
|
local bool bIsHeadshot;
|
|
local KFPlayerReplicationInfo KFPRI;
|
|
local float HeadShotCheckScale;
|
|
|
|
LastDamagedBy = instigatedBy;
|
|
LastDamagedByType = damageType;
|
|
HitMomentum = VSize(momentum);
|
|
LastHitLocation = hitlocation;
|
|
LastMomentum = momentum;
|
|
|
|
if ( KFPawn(instigatedBy) != none && instigatedBy.PlayerReplicationInfo != none )
|
|
{
|
|
KFPRI = KFPlayerReplicationInfo(instigatedBy.PlayerReplicationInfo);
|
|
}
|
|
|
|
// Scale damage if the Zed has been zapped
|
|
if( bZapped )
|
|
{
|
|
Damage *= ZappedDamageMod;
|
|
}
|
|
|
|
// Zeds and fire dont mix.
|
|
if ( class<KFWeaponDamageType>(damageType) != none && class<KFWeaponDamageType>(damageType).default.bDealBurningDamage )
|
|
{
|
|
if( BurnDown<=0 || Damage > LastBurnDamage )
|
|
{
|
|
// LastBurnDamage variable is storing last burn damage (unperked) received,
|
|
// which will be used to make additional damage per every burn tick (second).
|
|
LastBurnDamage = Damage;
|
|
|
|
// FireDamageClass variable stores damage type, which started zed's burning
|
|
// and will be passed to this function again every next burn tick (as damageType argument)
|
|
if ( class<DamTypeTrenchgun>(damageType) != none ||
|
|
class<DamTypeFlareRevolver>(damageType) != none ||
|
|
class<DamTypeMAC10MPInc>(damageType) != none)
|
|
{
|
|
FireDamageClass = damageType;
|
|
}
|
|
else
|
|
{
|
|
FireDamageClass = class'DamTypeFlamethrower';
|
|
}
|
|
}
|
|
|
|
if ( class<DamTypeMAC10MPInc>(damageType) == none )
|
|
{
|
|
Damage *= 1.5; // Increase burn damage 1.5 times, except MAC10.
|
|
}
|
|
|
|
// BurnDown variable indicates how many ticks are remaining for zed to burn.
|
|
// It is 0, when zed isn't burning (or stopped burning).
|
|
// So all the code below will be executed only, if zed isn't already burning
|
|
if( BurnDown<=0 )
|
|
{
|
|
if( HeatAmount>4 || Damage >= 15 )
|
|
{
|
|
bBurnified = true;
|
|
BurnDown = 10; // Inits burn tick count to 10
|
|
SetGroundSpeed(GroundSpeed *= 0.80); // Lowers movement speed by 20%
|
|
BurnInstigator = instigatedBy;
|
|
SetTimer(1.0,false); // Sets timer function to be executed each second
|
|
}
|
|
else HeatAmount++;
|
|
}
|
|
}
|
|
|
|
if ( !bDecapitated && class<KFWeaponDamageType>(damageType)!=none &&
|
|
class<KFWeaponDamageType>(damageType).default.bCheckForHeadShots )
|
|
{
|
|
HeadShotCheckScale = 1.0;
|
|
|
|
// Do larger headshot checks if it is a melee attach
|
|
if( class<DamTypeMelee>(damageType) != none )
|
|
{
|
|
HeadShotCheckScale *= 1.25;
|
|
}
|
|
|
|
bIsHeadShot = IsHeadShot(hitlocation, normal(momentum), HeadShotCheckScale);
|
|
bLaserSightedEBRM14Headshotted = bIsHeadshot && M14EBRBattleRifle(instigatedBy.Weapon) != none && M14EBRBattleRifle(instigatedBy.Weapon).bLaserActive;
|
|
}
|
|
else
|
|
{
|
|
bLaserSightedEBRM14Headshotted = bLaserSightedEBRM14Headshotted && bDecapitated;
|
|
}
|
|
|
|
if ( KFPRI != none )
|
|
{
|
|
if ( KFPRI.ClientVeteranSkill != none )
|
|
{
|
|
Damage = KFPRI.ClientVeteranSkill.Static.AddDamage(KFPRI, self, KFPawn(instigatedBy), Damage, DamageType);
|
|
}
|
|
}
|
|
|
|
if ( damageType != none && LastDamagedBy.IsPlayerPawn() && LastDamagedBy.Controller != none )
|
|
{
|
|
if ( KFMonsterController(Controller) != none )
|
|
{
|
|
KFMonsterController(Controller).AddKillAssistant(LastDamagedBy.Controller, FMin(Health, Damage));
|
|
}
|
|
}
|
|
|
|
if ( (bDecapitated || bIsHeadShot) && class<DamTypeBurned>(DamageType) == none && class<DamTypeFlamethrower>(DamageType) == none )
|
|
{
|
|
if(class<KFWeaponDamageType>(damageType)!=none)
|
|
Damage = Damage * class<KFWeaponDamageType>(damageType).default.HeadShotDamageMult;
|
|
|
|
if ( class<DamTypeMelee>(damageType) == none && KFPRI != none &&
|
|
KFPRI.ClientVeteranSkill != none )
|
|
{
|
|
Damage = float(Damage) * KFPRI.ClientVeteranSkill.Static.GetHeadShotDamMulti(KFPRI, KFPawn(instigatedBy), DamageType);
|
|
}
|
|
|
|
LastDamageAmount = Damage;
|
|
|
|
if( !bDecapitated )
|
|
{
|
|
if( bIsHeadShot )
|
|
{
|
|
// Play a sound when someone gets a headshot TODO: Put in the real sound here
|
|
if( bIsHeadShot )
|
|
{
|
|
PlaySound(sound'KF_EnemyGlobalSndTwo.Impact_Skull', SLOT_None,2.0,true,500);
|
|
}
|
|
HeadHealth -= LastDamageAmount;
|
|
if( HeadHealth <= 0 || Damage > Health )
|
|
{
|
|
RemoveHead();
|
|
}
|
|
}
|
|
|
|
// Award headshot here, not when zombie died.
|
|
if( bDecapitated && Class<KFWeaponDamageType>(damageType) != none && instigatedBy != none && KFPlayerController(instigatedBy.Controller) != none )
|
|
{
|
|
bLaserSightedEBRM14Headshotted = M14EBRBattleRifle(instigatedBy.Weapon) != none && M14EBRBattleRifle(instigatedBy.Weapon).bLaserActive;
|
|
Class<KFWeaponDamageType>(damageType).Static.ScoredHeadshot(KFSteamStatsAndAchievements(PlayerController(instigatedBy.Controller).SteamStatsAndAchievements), self.Class, bLaserSightedEBRM14Headshotted);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Client check for Gore FX
|
|
//BodyPartRemoval(Damage,instigatedBy,hitlocation,momentum,damageType);
|
|
|
|
if( Health-Damage > 0 && DamageType!=class'DamTypeFrag' && DamageType!=class'DamTypePipeBomb'
|
|
&& DamageType!=class'DamTypeM79Grenade' && DamageType!=class'DamTypeM32Grenade'
|
|
&& DamageType!=class'DamTypeM203Grenade' && DamageType!=class'DamTypeDwarfAxe'
|
|
&& DamageType!=class'DamTypeSPGrenade' && DamageType!=class'DamTypeSealSquealExplosion'
|
|
&& DamageType!=class'DamTypeSeekerSixRocket')
|
|
{
|
|
Momentum = vect(0,0,0);
|
|
}
|
|
|
|
if(class<DamTypeVomit>(DamageType)!=none) // Same rules apply to zombies as players.
|
|
{
|
|
BileCount=7;
|
|
BileInstigator = instigatedBy;
|
|
LastBileDamagedByType=class<DamTypeVomit>(DamageType);
|
|
if(NextBileTime< Level.TimeSeconds )
|
|
NextBileTime = Level.TimeSeconds+BileFrequency;
|
|
}
|
|
|
|
if ( KFPRI != none && Health-Damage <= 0 && KFPRI.ClientVeteranSkill != none && KFPRI.ClientVeteranSkill.static.KilledShouldExplode(KFPRI, KFPawn(instigatedBy)) )
|
|
{
|
|
Super.takeDamage(Damage + 600, instigatedBy, hitLocation, momentum, damageType);
|
|
HurtRadius(500, 1000, class'DamTypeFrag', 100000, Location);
|
|
}
|
|
else
|
|
{
|
|
Super.takeDamage(Damage, instigatedBy, hitLocation, momentum, damageType);
|
|
}
|
|
|
|
if( bIsHeadShot && Health <= 0 )
|
|
{
|
|
KFGameType(Level.Game).DramaticEvent(0.03);
|
|
}
|
|
|
|
bBackstabbed = false;
|
|
}
|
|
|
|
function PlayDyingSound()
|
|
{
|
|
if( Level.NetMode!=NM_Client )
|
|
{
|
|
if ( bGibbed )
|
|
{
|
|
// Do nothing for now
|
|
PlaySound(GibGroupClass.static.GibSound(), SLOT_Pain,2.0,true,525);
|
|
return;
|
|
}
|
|
|
|
if( bDecapitated )
|
|
{
|
|
|
|
PlaySound(HeadlessDeathSound, SLOT_Pain,1.30,true,525);
|
|
}
|
|
else
|
|
{
|
|
PlaySound(DeathSound[0], SLOT_Pain,1.30,true,525);
|
|
}
|
|
}
|
|
}
|
|
|
|
// New Hit FX for Zombies!
|
|
function PlayHit(float Damage, Pawn InstigatedBy, vector HitLocation, class<DamageType> damageType, vector Momentum, optional int HitIdx )
|
|
{
|
|
local Vector HitNormal;
|
|
local Vector HitRay ;
|
|
local Name HitBone;
|
|
local float HitBoneDist;
|
|
local PlayerController PC;
|
|
local bool bShowEffects, bRecentHit;
|
|
local ProjectileBloodSplat BloodHit;
|
|
local rotator SplatRot;
|
|
|
|
bRecentHit = Level.TimeSeconds - LastPainTime < 0.2;
|
|
|
|
LastDamageAmount = Damage;
|
|
|
|
// Call the modified version of the original Pawn playhit
|
|
OldPlayHit(Damage, InstigatedBy, HitLocation, DamageType,Momentum);
|
|
|
|
if ( Damage <= 0 )
|
|
return;
|
|
|
|
if( Health>0 && Damage>(float(Default.Health)/1.5) )
|
|
FlipOver();
|
|
|
|
PC = PlayerController(Controller);
|
|
bShowEffects = ( (Level.NetMode != NM_Standalone) || (Level.TimeSeconds - LastRenderTime < 2.5)
|
|
|| ((InstigatedBy != None) && (PlayerController(InstigatedBy.Controller) != None))
|
|
|| (PC != None) );
|
|
if ( !bShowEffects )
|
|
return;
|
|
|
|
if ( BurnDown > 0 && !bBurnified )
|
|
{
|
|
bBurnified = true;
|
|
}
|
|
|
|
HitRay = vect(0,0,0);
|
|
if( InstigatedBy != None )
|
|
HitRay = Normal(HitLocation-(InstigatedBy.Location+(vect(0,0,1)*InstigatedBy.EyeHeight)));
|
|
|
|
if( DamageType.default.bLocationalHit )
|
|
{
|
|
CalcHitLoc( HitLocation, HitRay, HitBone, HitBoneDist );
|
|
|
|
// Do a zapped effect is someone shoots us and we're zapped to help show that the zed is taking more damage
|
|
if ( bZapped && DamageType.name != 'DamTypeZEDGun' )
|
|
{
|
|
PlaySound(class'ZedGunProjectile'.default.ExplosionSound,,class'ZedGunProjectile'.default.ExplosionSoundVolume);
|
|
Spawn(class'ZedGunProjectile'.default.ExplosionEmitter,,,HitLocation + HitNormal*20,rotator(HitNormal));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HitLocation = Location ;
|
|
HitBone = FireRootBone;
|
|
HitBoneDist = 0.0f;
|
|
}
|
|
|
|
if( DamageType.default.bAlwaysSevers && DamageType.default.bSpecial )
|
|
HitBone = 'head';
|
|
|
|
if( InstigatedBy != None )
|
|
HitNormal = Normal( Normal(InstigatedBy.Location-HitLocation) + VRand() * 0.2 + vect(0,0,2.8) );
|
|
else
|
|
HitNormal = Normal( Vect(0,0,1) + VRand() * 0.2 + vect(0,0,2.8) );
|
|
|
|
//log("HitLocation "$Hitlocation) ;
|
|
|
|
if ( DamageType.Default.bCausesBlood && (!bRecentHit || (bRecentHit && (FRand() > 0.8))))
|
|
{
|
|
if ( !class'GameInfo'.static.NoBlood() && !class'GameInfo'.static.UseLowGore() )
|
|
{
|
|
if ( Momentum != vect(0,0,0) )
|
|
SplatRot = rotator(Normal(Momentum));
|
|
else
|
|
{
|
|
if ( InstigatedBy != None )
|
|
SplatRot = rotator(Normal(Location - InstigatedBy.Location));
|
|
else
|
|
SplatRot = rotator(Normal(Location - HitLocation));
|
|
}
|
|
|
|
BloodHit = Spawn(ProjectileBloodSplatClass,InstigatedBy,, HitLocation, SplatRot);
|
|
}
|
|
}
|
|
|
|
if( InstigatedBy != none && InstigatedBy.PlayerReplicationInfo != none &&
|
|
KFSteamStatsAndAchievements(InstigatedBy.PlayerReplicationInfo.SteamStatsAndAchievements) != none &&
|
|
Health <= 0 && Damage > DamageType.default.HumanObliterationThreshhold && Damage != 1000 && (!bDecapitated || bPlayBrainSplash) )
|
|
{
|
|
KFSteamStatsAndAchievements(InstigatedBy.PlayerReplicationInfo.SteamStatsAndAchievements).AddGibKill(class<DamTypeM79Grenade>(damageType) != none);
|
|
|
|
if ( self.IsA('ZombieFleshPound') )
|
|
{
|
|
KFSteamStatsAndAchievements(InstigatedBy.PlayerReplicationInfo.SteamStatsAndAchievements).AddFleshpoundGibKill();
|
|
}
|
|
}
|
|
|
|
DoDamageFX( HitBone, Damage, DamageType, Rotator(HitNormal) );
|
|
|
|
if (DamageType.default.DamageOverlayMaterial != None && Damage > 0 ) // additional check in case shield absorbed
|
|
SetOverlayMaterial( DamageType.default.DamageOverlayMaterial, DamageType.default.DamageOverlayTime, false );
|
|
}
|
|
|
|
// Modified version of the original Pawn playhit. Set up because we want our blood puffs to be directional based
|
|
// On the momentum of the bullet, not out from the center of the player
|
|
function OldPlayHit(float Damage, Pawn InstigatedBy, vector HitLocation, class<DamageType> damageType, vector Momentum, optional int HitIndex)
|
|
{
|
|
local Vector HitNormal;
|
|
local vector BloodOffset, Mo;
|
|
local class<Effects> DesiredEffect;
|
|
local class<Emitter> DesiredEmitter;
|
|
local PlayerController Hearer;
|
|
|
|
if ( DamageType == None )
|
|
return;
|
|
if ( (Damage <= 0) && ((Controller == None) || !Controller.bGodMode) )
|
|
return;
|
|
|
|
if (Damage > DamageType.Default.DamageThreshold) //spawn some blood
|
|
{
|
|
HitNormal = Normal(HitLocation - Location);
|
|
|
|
// Play any set effect
|
|
if ( EffectIsRelevant(Location,true) )
|
|
{
|
|
DesiredEffect = DamageType.static.GetPawnDamageEffect(HitLocation, Damage, Momentum, self, (Level.bDropDetail || Level.DetailMode == DM_Low));
|
|
|
|
if ( DesiredEffect != None )
|
|
{
|
|
BloodOffset = 0.2 * CollisionRadius * HitNormal;
|
|
BloodOffset.Z = BloodOffset.Z * 0.5;
|
|
|
|
Mo = Momentum;
|
|
if ( Mo.Z > 0 )
|
|
Mo.Z *= 0.5;
|
|
spawn(DesiredEffect,self,,HitLocation + BloodOffset, rotator(Mo));
|
|
}
|
|
|
|
// Spawn any preset emitter
|
|
// Don't spawn the blood when we're zapped as we're spawning the zapped damage emitter elsewhere
|
|
if( !bZapped || (bZapped && !DamageType.default.bLocationalHit) )
|
|
{
|
|
DesiredEmitter = DamageType.Static.GetPawnDamageEmitter(HitLocation, Damage, Momentum, self, (Level.bDropDetail || Level.DetailMode == DM_Low));
|
|
if (DesiredEmitter != None)
|
|
{
|
|
if( InstigatedBy != none )
|
|
HitNormal = Normal((InstigatedBy.Location+(vect(0,0,1)*InstigatedBy.EyeHeight))-HitLocation);
|
|
|
|
spawn(DesiredEmitter,,,HitLocation+HitNormal + (-HitNormal * CollisionRadius), Rotator(HitNormal));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( Health <= 0 )
|
|
{
|
|
if ( PhysicsVolume.bDestructive && (PhysicsVolume.ExitActor != None) )
|
|
Spawn(PhysicsVolume.ExitActor);
|
|
return;
|
|
}
|
|
|
|
if ( Level.TimeSeconds - LastPainTime > 0.1 )
|
|
{
|
|
if ( InstigatedBy != None && (DamageType != None) && DamageType.default.bDirectDamage )
|
|
Hearer = PlayerController(InstigatedBy.Controller);
|
|
if ( Hearer != None )
|
|
Hearer.bAcuteHearing = true;
|
|
PlayTakeHit(HitLocation,Damage,damageType);
|
|
if ( Hearer != None )
|
|
Hearer.bAcuteHearing = false;
|
|
LastPainTime = Level.TimeSeconds;
|
|
}
|
|
}
|
|
|
|
// Implemented in subclasses - return false if there is some action that we don't want the direction hit to interrupt
|
|
simulated function bool HitCanInterruptAction()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
function PlayDirectionalHit(Vector HitLoc)
|
|
{
|
|
local Vector X,Y,Z, Dir;
|
|
|
|
GetAxes(Rotation, X,Y,Z);
|
|
HitLoc.Z = Location.Z;
|
|
Dir = -Normal(Location - HitLoc);
|
|
|
|
if( !HitCanInterruptAction() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// random
|
|
if ( VSize(Location - HitLoc) < 1.0 )
|
|
Dir = VRand();
|
|
else Dir = -Normal(Location - HitLoc);
|
|
|
|
if ( Dir dot X > 0.7 || Dir == vect(0,0,0))
|
|
{
|
|
if( LastDamagedBy!=none && LastDamageAmount>0 )
|
|
{
|
|
if ( StunsRemaining != 0 && (LastDamageAmount >= (0.5 * default.Health) ||
|
|
(VSize(LastDamagedBy.Location - Location) <= (MeleeRange * 2) && ClassIsChildOf(LastDamagedbyType,class 'DamTypeMelee') &&
|
|
KFPawn(LastDamagedBy) != none && LastDamageAmount > (0.10* default.Health))) )
|
|
{
|
|
SetAnimAction(HitAnims[Rand(3)]);
|
|
bSTUNNED = true;
|
|
SetTimer(StunTime,false);
|
|
StunsRemaining--;
|
|
}
|
|
else
|
|
SetAnimAction(KFHitFront);
|
|
}
|
|
}
|
|
else if ( Dir Dot X < -0.7 )
|
|
SetAnimAction(KFHitBack);
|
|
else if ( Dir Dot Y > 0 )
|
|
SetAnimAction(KFHitRight);
|
|
else SetAnimAction(KFHitLeft);
|
|
}
|
|
|
|
simulated function PlayDirectionalDeath(Vector HitLoc); // Death animation replaced with ragdoll.
|
|
|
|
// Overridden so that anims don't get interrupted on the server if one is already playing
|
|
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);
|
|
}
|
|
//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));
|
|
}
|
|
|
|
//TODO - log this to hell to find the last ANs,
|
|
// and look to consolidate any duplicate code,
|
|
// including multiple !=nones for the same target
|
|
function bool MeleeDamageTarget(int hitdamage, vector pushdir)
|
|
{
|
|
local vector HitLocation, HitNormal;
|
|
local actor HitActor;
|
|
local Name TearBone;
|
|
local float dummy;
|
|
local Emitter BloodHit;
|
|
//local vector TraceDir;
|
|
|
|
if( Level.NetMode==NM_Client || Controller==None )
|
|
Return False; // Never should be done on client.
|
|
if ( Controller.Target!=none && Controller.Target.IsA('KFDoorMover'))
|
|
{
|
|
Controller.Target.TakeDamage(hitdamage, self ,HitLocation,pushdir, CurrentDamType);
|
|
Return True;
|
|
}
|
|
|
|
/*ClearStayingDebugLines();
|
|
|
|
TraceDir = Normal(Controller.Target.Location - Location);
|
|
|
|
DrawStayingDebugLine(Location, Location + (TraceDir * (MeleeRange * 1.4 + Controller.Target.CollisionRadius + CollisionRadius)) , 255,255,0);*/
|
|
|
|
// check if still in melee range
|
|
if ( (Controller.target != None) && (bSTUNNED == false) && (DECAP == false) && (VSize(Controller.Target.Location - Location) <= MeleeRange * 1.4 + Controller.Target.CollisionRadius + CollisionRadius)
|
|
&& ((Physics == PHYS_Flying) || (Physics == PHYS_Swimming) || (Abs(Location.Z - Controller.Target.Location.Z)
|
|
<= FMax(CollisionHeight, Controller.Target.CollisionHeight) + 0.5 * FMin(CollisionHeight, Controller.Target.CollisionHeight))) )
|
|
{
|
|
// See if a trace would hit a pawn (Have to turn of hit point collision so trace doesn't hit the Human Pawn's bullet whiz cylinder)
|
|
bBlockHitPointTraces = false;
|
|
HitActor = Trace(HitLocation, HitNormal, Controller.Target.Location , Location + EyePosition(), true);
|
|
bBlockHitPointTraces = true;
|
|
|
|
// If the trace wouldn't hit a pawn, do the old thing of just checking if there is something blocking the trace
|
|
if( Pawn(HitActor) == none )
|
|
{
|
|
// Have to turn of hit point collision so trace doesn't hit the Human Pawn's bullet whiz cylinder
|
|
bBlockHitPointTraces = false;
|
|
HitActor = Trace(HitLocation, HitNormal, Controller.Target.Location, Location, false);
|
|
bBlockHitPointTraces = true;
|
|
|
|
if ( HitActor != None )
|
|
return false;
|
|
}
|
|
|
|
if ( KFHumanPawn(Controller.Target) != none )
|
|
{
|
|
//TODO - line below was KFPawn. Does this whole block need to be KFPawn, or is it OK as KFHumanPawn?
|
|
KFHumanPawn(Controller.Target).TakeDamage(hitdamage, Instigator ,HitLocation,pushdir, CurrentDamType); //class 'KFmod.ZombieMeleeDamage');
|
|
|
|
if (KFHumanPawn(Controller.Target).Health <=0)
|
|
{
|
|
if ( !class'GameInfo'.static.UseLowGore() )
|
|
{
|
|
BloodHit = Spawn(class'KFMod.FeedingSpray',self,,Controller.Target.Location,rotator(pushdir)); //
|
|
KFHumanPawn(Controller.Target).SpawnGibs(rotator(pushdir), 1);
|
|
TearBone=KFPawn(Controller.Target).GetClosestBone(HitLocation,Velocity,dummy);
|
|
KFHumanPawn(Controller.Target).HideBone(TearBone);
|
|
}
|
|
|
|
// Give us some Health back
|
|
if (Health <= (1.0-FeedThreshold)*HealthMax)
|
|
{
|
|
Health += FeedThreshold*HealthMax * Health/HealthMax;
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (Controller.target != None)
|
|
{
|
|
// Do more damage if you are attacking another zed so that zeds don't just stand there whacking each other forever! - Ramm
|
|
if( KFMonster(Controller.Target) != none )
|
|
{
|
|
hitdamage *= DamageToMonsterScale;
|
|
}
|
|
|
|
Controller.Target.TakeDamage(hitdamage, self ,HitLocation,pushdir, CurrentDamType); //class 'KFmod.ZombieMeleeDamage');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
simulated function PlayDyingAnimation(class<DamageType> DamageType, vector HitLoc)
|
|
{
|
|
local vector shotDir, hitLocRel, deathAngVel, shotStrength;
|
|
local float maxDim;
|
|
local string RagSkelName;
|
|
local KarmaParamsSkel skelParams;
|
|
local bool PlayersRagdoll;
|
|
local PlayerController pc;
|
|
|
|
if( MyExtCollision!=None )
|
|
MyExtCollision.Destroy();
|
|
if ( Level.NetMode != NM_DedicatedServer )
|
|
{
|
|
// Is this the local player's ragdoll?
|
|
if(OldController != None)
|
|
pc = PlayerController(OldController);
|
|
if( pc != None && pc.ViewTarget == self )
|
|
PlayersRagdoll = true;
|
|
|
|
// In low physics detail, if we were not just controlling this pawn,
|
|
// and it has not been rendered in 3 seconds, just destroy it.
|
|
|
|
if( Level.NetMode == NM_ListenServer )
|
|
{
|
|
// For a listen server, use LastSeenOrRelevantTime instead of render time so
|
|
// monsters don't disappear for other players that the host can't see - Ramm
|
|
if( Level.PhysicsDetailLevel != PDL_High && !PlayersRagdoll && (Level.TimeSeconds-LastSeenOrRelevantTime)>3 ||
|
|
bGibbed )
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
else if( Level.PhysicsDetailLevel!=PDL_High && !PlayersRagdoll && (Level.TimeSeconds-LastRenderTime)>3 ||
|
|
bGibbed)
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
// Try and obtain a rag-doll setup. Use optional 'override' one out of player record first, then use the species one.
|
|
if( RagdollOverride != "")
|
|
RagSkelName = RagdollOverride;
|
|
else if(Species != None)
|
|
RagSkelName = Species.static.GetRagSkelName( GetMeshName() );
|
|
else RagSkelName = "Male1"; // Otherwise assume it is Male1 ragdoll were after here.
|
|
|
|
KMakeRagdollAvailable();
|
|
|
|
if( KIsRagdollAvailable() && RagSkelName != "" )
|
|
{
|
|
skelParams = KarmaParamsSkel(KParams);
|
|
skelParams.KSkeleton = RagSkelName;
|
|
|
|
// Stop animation playing.
|
|
StopAnimating(true);
|
|
|
|
// StopAnimating() resets the neck bone rotation, we have to set it again
|
|
// if the zed was decapitated the cute way
|
|
if ( class'GameInfo'.static.UseLowGore() && NeckRot != rot(0,0,0) )
|
|
{
|
|
SetBoneRotation('neck', NeckRot);
|
|
}
|
|
|
|
if( DamageType != none )
|
|
{
|
|
if ( DamageType.default.bLeaveBodyEffect )
|
|
TearOffMomentum = vect(0,0,0);
|
|
|
|
if ( DamageType.default.bKUseOwnDeathVel )
|
|
{
|
|
RagDeathVel = DamageType.default.KDeathVel;
|
|
RagDeathUpKick = DamageType.default.KDeathUpKick;
|
|
RagShootStrength = DamageType.default.KDamageImpulse;
|
|
}
|
|
}
|
|
|
|
// Set the dude moving in direction he was shot in general
|
|
shotDir = Normal(GetTearOffMomemtum());
|
|
shotStrength = RagDeathVel * shotDir;
|
|
|
|
// Calculate angular velocity to impart, based on shot location.
|
|
hitLocRel = TakeHitLocation - Location;
|
|
|
|
if( DamageType.default.bLocationalHit )
|
|
{
|
|
hitLocRel.X *= RagSpinScale;
|
|
hitLocRel.Y *= RagSpinScale;
|
|
|
|
if( Abs(hitLocRel.X) > RagMaxSpinAmount )
|
|
{
|
|
if( hitLocRel.X < 0 )
|
|
{
|
|
hitLocRel.X = FMax((hitLocRel.X * RagSpinScale), (RagMaxSpinAmount * -1));
|
|
}
|
|
else
|
|
{
|
|
hitLocRel.X = FMin((hitLocRel.X * RagSpinScale), RagMaxSpinAmount);
|
|
}
|
|
}
|
|
|
|
if( Abs(hitLocRel.Y) > RagMaxSpinAmount )
|
|
{
|
|
if( hitLocRel.Y < 0 )
|
|
{
|
|
hitLocRel.Y = FMax((hitLocRel.Y * RagSpinScale), (RagMaxSpinAmount * -1));
|
|
}
|
|
else
|
|
{
|
|
hitLocRel.Y = FMin((hitLocRel.Y * RagSpinScale), RagMaxSpinAmount);
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// We scale the hit location out sideways a bit, to get more spin around Z.
|
|
hitLocRel.X *= RagSpinScale;
|
|
hitLocRel.Y *= RagSpinScale;
|
|
}
|
|
|
|
//log("hitLocRel.X = "$hitLocRel.X$" hitLocRel.Y = "$hitLocRel.Y);
|
|
//log("TearOffMomentum = "$VSize(GetTearOffMomemtum()));
|
|
|
|
// If the tear off momentum was very small for some reason, make up some angular velocity for the pawn
|
|
if( VSize(GetTearOffMomemtum()) < 0.01 )
|
|
{
|
|
//Log("TearOffMomentum magnitude of Zero");
|
|
deathAngVel = VRand() * 18000.0;
|
|
}
|
|
else
|
|
{
|
|
deathAngVel = RagInvInertia * (hitLocRel cross shotStrength);
|
|
}
|
|
|
|
// Set initial angular and linear velocity for ragdoll.
|
|
// Scale horizontal velocity for characters - they run really fast!
|
|
if ( DamageType.Default.bRubbery )
|
|
skelParams.KStartLinVel = vect(0,0,0);
|
|
if ( Damagetype.default.bKUseTearOffMomentum )
|
|
skelParams.KStartLinVel = GetTearOffMomemtum() + Velocity;
|
|
else
|
|
{
|
|
skelParams.KStartLinVel.X = 0.6 * Velocity.X;
|
|
skelParams.KStartLinVel.Y = 0.6 * Velocity.Y;
|
|
skelParams.KStartLinVel.Z = 1.0 * Velocity.Z;
|
|
skelParams.KStartLinVel += shotStrength;
|
|
}
|
|
// If not moving downwards - give extra upward kick
|
|
if( !DamageType.default.bLeaveBodyEffect && !DamageType.Default.bRubbery && (Velocity.Z > -10) )
|
|
skelParams.KStartLinVel.Z += RagDeathUpKick;
|
|
|
|
if ( DamageType.Default.bRubbery )
|
|
{
|
|
Velocity = vect(0,0,0);
|
|
skelParams.KStartAngVel = vect(0,0,0);
|
|
}
|
|
else
|
|
{
|
|
skelParams.KStartAngVel = deathAngVel;
|
|
|
|
// Set up deferred shot-bone impulse
|
|
maxDim = Max(CollisionRadius, CollisionHeight);
|
|
|
|
skelParams.KShotStart = TakeHitLocation - (1 * shotDir);
|
|
skelParams.KShotEnd = TakeHitLocation + (2*maxDim*shotDir);
|
|
skelParams.KShotStrength = RagShootStrength;
|
|
}
|
|
|
|
//log("RagDeathVel = "$RagDeathVel$" KShotStrength = "$skelParams.KShotStrength$" RagDeathUpKick = "$RagDeathUpKick);
|
|
|
|
// If this damage type causes convulsions, turn them on here.
|
|
if(DamageType != none && DamageType.default.bCauseConvulsions)
|
|
{
|
|
RagConvulseMaterial=DamageType.default.DamageOverlayMaterial;
|
|
skelParams.bKDoConvulsions = true;
|
|
}
|
|
|
|
// Turn on Karma collision for ragdoll.
|
|
KSetBlockKarma(true);
|
|
|
|
// Set physics mode to ragdoll.
|
|
// This doesn't actaully start it straight away, it's deferred to the first tick.
|
|
SetPhysics(PHYS_KarmaRagdoll);
|
|
|
|
// If viewing this ragdoll, set the flag to indicate that it is 'important'
|
|
if( PlayersRagdoll )
|
|
skelParams.bKImportantRagdoll = true;
|
|
|
|
skelParams.bRubbery = DamageType.Default.bRubbery;
|
|
bRubbery = DamageType.Default.bRubbery;
|
|
|
|
skelParams.KActorGravScale = RagGravScale;
|
|
|
|
return;
|
|
}
|
|
// jag
|
|
}
|
|
// non-ragdoll death fallback
|
|
Velocity += GetTearOffMomemtum();
|
|
BaseEyeHeight = Default.BaseEyeHeight;
|
|
SetTwistLook(0, 0);
|
|
SetInvisibility(0.0);
|
|
// We don't do this - Ramm
|
|
//PlayDirectionalDeath(HitLoc);
|
|
SetPhysics(PHYS_Falling);
|
|
}
|
|
|
|
// Give zombies forward momentum with jumps.
|
|
function bool DoJump( bool bUpdating )
|
|
{
|
|
if ( !bIsCrouched && !bWantsToCrouch && ((Physics == PHYS_Walking) || (Physics == PHYS_Ladder) || (Physics == PHYS_Spider)) )
|
|
{
|
|
PlayOwnedSound(JumpSound, SLOT_Pain, GruntVolume,,80);
|
|
|
|
if ( Role == ROLE_Authority )
|
|
{
|
|
if ( (Level.Game != None) && (Level.Game.GameDifficulty > 2) )
|
|
MakeNoise(0.1 * Level.Game.GameDifficulty);
|
|
if ( bCountJumps && (Inventory != None) )
|
|
Inventory.OwnerEvent('Jumped');
|
|
}
|
|
if ( Physics == PHYS_Spider )
|
|
Velocity = JumpZ * Floor;
|
|
else if ( Physics == PHYS_Ladder )
|
|
Velocity.Z = 0;
|
|
else if ( bIsWalking )
|
|
{
|
|
Velocity.Z = Default.JumpZ;
|
|
Velocity.X = (Default.JumpZ * 0.5);
|
|
}
|
|
else
|
|
{
|
|
Velocity.Z = JumpZ;
|
|
Velocity.X = (JumpZ * 0.5);
|
|
}
|
|
|
|
if ( (Base != None) && !Base.bWorldGeometry )
|
|
{
|
|
Velocity.Z += Base.Velocity.Z;
|
|
Velocity.X += Base.Velocity.X;
|
|
}
|
|
SetPhysics(PHYS_Falling);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// Overridden to handle making attached explosives explode when this pawn dies
|
|
function Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
|
|
{
|
|
local int i;
|
|
|
|
for( i=0; i<Attached.length; i++ )
|
|
{
|
|
if( SealSquealProjectile(Attached[i])!=None )
|
|
{
|
|
SealSquealProjectile(Attached[i]).HandleBasePawnDestroyed();
|
|
}
|
|
}
|
|
|
|
super.Died(Killer,damageType,HitLocation);
|
|
}
|
|
|
|
// Overridden to handle making attached explosives explode when this pawn dies
|
|
simulated function Destroyed()
|
|
{
|
|
local int i;
|
|
|
|
for( i=0; i<Attached.length; i++ )
|
|
{
|
|
if( Emitter(Attached[i])!=None && Attached[i].IsA('DismembermentJet') )
|
|
{
|
|
Emitter(Attached[i]).Kill();
|
|
Attached[i].LifeSpan = 2;
|
|
}
|
|
|
|
// Make attached explosives blow up when this pawn dies
|
|
if( SealSquealProjectile(Attached[i])!=None )
|
|
{
|
|
SealSquealProjectile(Attached[i]).HandleBasePawnDestroyed();
|
|
}
|
|
}
|
|
|
|
if( MyExtCollision!=None )
|
|
MyExtCollision.Destroy();
|
|
if( PlayerShadow != None )
|
|
PlayerShadow.Destroy();
|
|
|
|
if ( FlamingFXs != none )
|
|
{
|
|
FlamingFXs.Emitters[0].SkeletalMeshActor = none;
|
|
FlamingFXs.Destroy();
|
|
}
|
|
|
|
if(RealtimeShadow !=none)
|
|
RealtimeShadow.Destroy();
|
|
|
|
if( SeveredLeftArm != none )
|
|
{
|
|
SeveredLeftArm.Destroy();
|
|
}
|
|
|
|
if( SeveredRightArm != none )
|
|
{
|
|
SeveredRightArm.Destroy();
|
|
}
|
|
|
|
if( SeveredRightLeg != none )
|
|
{
|
|
SeveredRightLeg.Destroy();
|
|
}
|
|
|
|
if( SeveredLeftLeg != none )
|
|
{
|
|
SeveredLeftLeg.Destroy();
|
|
}
|
|
|
|
if( SeveredHead != none )
|
|
{
|
|
SeveredHead.Destroy();
|
|
}
|
|
|
|
if(SpawnVolume != none)
|
|
{
|
|
SpawnVolume.RemoveZEDFromSpawnList(self);
|
|
}
|
|
|
|
RemoveFlamingEffects();
|
|
Super.Destroyed();
|
|
}
|
|
|
|
simulated function ZombieCrispUp()
|
|
{
|
|
bAshen = true;
|
|
bCrispified = true;
|
|
|
|
SetBurningBehavior();
|
|
|
|
if ( Level.NetMode == NM_DedicatedServer || class'GameInfo'.static.UseLowGore() )
|
|
{
|
|
Return;
|
|
}
|
|
|
|
Skins[0]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb';
|
|
Skins[1]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb';
|
|
Skins[2]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb';
|
|
Skins[3]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb';
|
|
}
|
|
|
|
// Set the zed to the on fire behavior
|
|
simulated function SetBurningBehavior()
|
|
{
|
|
if( Role == Role_Authority )
|
|
{
|
|
Intelligence = BRAINS_Retarded; // burning dumbasses!
|
|
|
|
SetGroundSpeed(OriginalGroundSpeed * 0.8);
|
|
AirSpeed *= 0.8;
|
|
WaterSpeed *= 0.8;
|
|
|
|
// Make them less accurate while they are burning
|
|
if( Controller != none )
|
|
{
|
|
MonsterController(Controller).Accuracy = -5; // More chance of missing. (he's burning now, after all) :-D
|
|
}
|
|
}
|
|
|
|
// Set the forward movement anim to a random burning anim
|
|
MovementAnims[0] = BurningWalkFAnims[Rand(3)];
|
|
WalkAnims[0] = BurningWalkFAnims[Rand(3)];
|
|
|
|
// Set the rest of the movement anims to the headless anim (not sure if these ever even get played) - Ramm
|
|
MovementAnims[1] = BurningWalkAnims[0];
|
|
WalkAnims[1] = BurningWalkAnims[0];
|
|
MovementAnims[2] = BurningWalkAnims[1];
|
|
WalkAnims[2] = BurningWalkAnims[1];
|
|
MovementAnims[3] = BurningWalkAnims[2];
|
|
WalkAnims[3] = BurningWalkAnims[2];
|
|
}
|
|
|
|
// Turn off the on-fire behavior
|
|
simulated function UnSetBurningBehavior()
|
|
{
|
|
local int i;
|
|
|
|
// Don't turn off this behavior until the harpoon stun is over
|
|
if( bHarpoonStunned )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( Role == Role_Authority )
|
|
{
|
|
Intelligence = default.Intelligence;
|
|
|
|
if( !bZapped )
|
|
{
|
|
SetGroundSpeed(GetOriginalGroundSpeed());
|
|
AirSpeed = default.AirSpeed;
|
|
WaterSpeed = default.WaterSpeed;
|
|
}
|
|
|
|
// Set normal accuracy
|
|
if ( Controller != none )
|
|
{
|
|
MonsterController(Controller).Accuracy = MonsterController(Controller).default.Accuracy;
|
|
}
|
|
}
|
|
|
|
bAshen = False;
|
|
|
|
// restore regular anims
|
|
for ( i = 0; i < 4; i++ )
|
|
{
|
|
MovementAnims[i] = default.MovementAnims[i];
|
|
WalkAnims[i] = default.WalkAnims[i];
|
|
}
|
|
}
|
|
|
|
function TakeFireDamage(int Damage,pawn Instigator)
|
|
{
|
|
local Vector DummyHitLoc,DummyMomentum;
|
|
|
|
TakeDamage(Damage, BurnInstigator, DummyHitLoc, DummyMomentum, FireDamageClass);
|
|
|
|
if ( BurnDown > 0 )
|
|
{
|
|
// Decrement the number of FireDamage calls left before our Zombie is extinguished :)
|
|
BurnDown --;
|
|
}
|
|
|
|
// Melt em' :)
|
|
if ( BurnDown < CrispUpThreshhold )
|
|
{
|
|
ZombieCrispUp();
|
|
}
|
|
|
|
if ( BurnDown == 0 )
|
|
{
|
|
bBurnified = false;
|
|
if( !bZapped )
|
|
{
|
|
SetGroundSpeed(default.GroundSpeed);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We can add blood streak decals here, as the Actor's body moves in Ragdoll
|
|
event KImpact(actor other, vector pos, vector impactVel, vector impactNorm)
|
|
{
|
|
local int numSounds, soundNum;
|
|
local vector WallHit, WallNormal;
|
|
local Actor WallActor;
|
|
local KFBloodStreakDecal Streak;
|
|
local float VelocitySquared;
|
|
local float RagHitVolume;
|
|
local float StreakDist;
|
|
|
|
numSounds = RagImpactSounds.Length;
|
|
|
|
if ( LastStreakLocation == vect(0,0,0) )
|
|
{
|
|
StreakDist = 0;
|
|
}
|
|
else
|
|
{
|
|
StreakDist = VSizeSquared(LastStreakLocation - pos);
|
|
}
|
|
|
|
LastStreakLocation = pos;
|
|
|
|
WallActor = Trace(WallHit, WallNormal, pos - impactNorm * 16, pos + impactNorm * 16, false);
|
|
|
|
//Added no blood for low gore
|
|
if ( WallActor != None && Level.TimeSeconds > LastStreakTime + BloodStreakInterval && !class'GameInfo'.static.UseLowGore() )
|
|
{
|
|
if ( StreakDist < 1400 ) //0.75m
|
|
{
|
|
//log("Streak dist too small "$ Sqrt(StreakDist)/50$"m");
|
|
return;
|
|
}
|
|
|
|
Streak = spawn(class 'KFMod.KFBloodStreakDecal',,, WallHit, rotator(-WallNormal));
|
|
|
|
LastStreakTime = Level.TimeSeconds;
|
|
}
|
|
|
|
if ( numSounds > 0 && Level.TimeSeconds > RagLastSoundTime + RagImpactSoundInterval )
|
|
{
|
|
VelocitySquared = VSizeSquared(impactVel);
|
|
RagHitVolume = FMin(2.0, (VelocitySquared / 40000));
|
|
soundNum = Rand(numSounds);
|
|
|
|
PlaySound(RagImpactSounds[soundNum], SLOT_None, RagHitVolume);
|
|
RagLastSoundTime = Level.TimeSeconds;
|
|
}
|
|
}
|
|
|
|
simulated function RemoveFlamingEffects()
|
|
{
|
|
local int i;
|
|
|
|
if ( Level.NetMode == NM_DedicatedServer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
for ( i = 0; i < Attached.length; i++ )
|
|
{
|
|
if ( xEmitter(Attached[i]) != none )
|
|
{
|
|
xEmitter(Attached[i]).mRegen = false;
|
|
Attached[i].LifeSpan = 2;
|
|
}
|
|
else if ( Emitter(Attached[i]) != None && !Attached[i].IsA('DismembermentJet') )
|
|
{
|
|
Emitter(Attached[i]).Kill();
|
|
Attached[i].LifeSpan = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated function TurnOff()
|
|
{
|
|
RemoteRole = ROLE_SimulatedProxy;
|
|
}
|
|
|
|
// this could have been responsible for making the zombies "lethargic" in wander state.
|
|
// they should retain the same walk speed at all times.
|
|
event SetWalking(bool bNewIsWalking);
|
|
|
|
function Trigger( actor Other, pawn EventInstigator )
|
|
{
|
|
if( Controller!=None )
|
|
Controller.Trigger(Other,EventInstigator);
|
|
}
|
|
|
|
// Laught at victory of boss battle.
|
|
function bool SetBossLaught()
|
|
{
|
|
Return False; // Normal zeds have no feelings..
|
|
}
|
|
|
|
state DoorBashing
|
|
{
|
|
simulated function bool HitCanInterruptAction()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
function Tick( float Delta )
|
|
{
|
|
Acceleration = vect(0,0,0);
|
|
|
|
global.Tick(Delta);
|
|
}
|
|
|
|
Begin:
|
|
Sleep(GetAnimDuration('DoorBash') + 0.1);
|
|
GotoState('');
|
|
}
|
|
|
|
static simulated function PreCacheAssets(LevelInfo myLevel)
|
|
{
|
|
|
|
//log("*********");
|
|
//log(default.Class);
|
|
//DynamicLoadSounds();
|
|
PreCacheStaticMeshes(myLevel);
|
|
if(myLevel != none)
|
|
PreCacheMaterials(myLevel);
|
|
//DynamicLoadMeshAndSkins();
|
|
|
|
}
|
|
|
|
static simulated function DynamicLoadMeshAndSkins()
|
|
{
|
|
/*
|
|
local int i;
|
|
|
|
if(default.MeshRef != "")
|
|
UpdateDefaultMesh( SkeletalMesh(DynamicLoadObject(default.MeshRef, Class'SkeletalMesh')) );
|
|
|
|
for ( i = 0; i < default.SkinsRef.Length; i++ )
|
|
{
|
|
if(default.SkinsRef[i] != "")
|
|
default.Skins[i] = Material(DynamicLoadObject(default.SkinsRef[i], class'Material'));
|
|
}
|
|
*/
|
|
}
|
|
|
|
static simulated function DynamicLoadSounds()
|
|
{
|
|
/* local int i;
|
|
|
|
if( default.AmbientSoundRef == "" )
|
|
{
|
|
log("don't have sound refs, bailing out");
|
|
return;
|
|
}
|
|
|
|
for(i = 0;i < default.HitSoundRef.length;i++)
|
|
{
|
|
default.HitSound[i] = Sound(DynamicLoadObject(default.HitSoundRef[i], Class'Sound'));
|
|
}
|
|
|
|
for(i = 0;i < default.ChallengeSoundRef.length;i++)
|
|
{
|
|
default.ChallengeSound[i] = Sound(DynamicLoadObject(default.ChallengeSoundRef[i], Class'Sound'));
|
|
}
|
|
|
|
for(i = 0;i < default.DeathSoundRef.length;i++)
|
|
{
|
|
default.DeathSound[i] = Sound(DynamicLoadObject(default.DeathSoundRef[i], Class'Sound'));
|
|
}
|
|
|
|
default.AmbientSound = Sound(DynamicLoadObject(default.AmbientSoundRef, Class'Sound'));
|
|
default.MoanVoice = Sound(DynamicLoadObject(default.MoanVoiceRef, Class'Sound'));
|
|
default.JumpSound = Sound(DynamicLoadObject(default.JumpSoundRef, Class'Sound'));
|
|
|
|
if( default.MeleeAttackHitSoundRef != "" )
|
|
default.MeleeAttackHitSound = Sound(DynamicLoadObject(default.MeleeAttackHitSoundRef, Class'Sound'));
|
|
*/
|
|
}
|
|
|
|
static simulated function PreCacheStaticMeshes(LevelInfo myLevel)
|
|
{//should be derived and used.
|
|
/*
|
|
if( default.DetachedArmClassRef != "" )
|
|
default.DetachedArmClass = class<SeveredAppendage>(DynamicLoadObject(default.DetachedArmClassRef, Class'Class'));
|
|
|
|
if( default.DetachedLegClassRef != "" )
|
|
default.DetachedLegClass = class<SeveredAppendage>(DynamicLoadObject(default.DetachedLegClassRef, Class'Class'));
|
|
|
|
if( default.DetachedHeadClassRef != "" )
|
|
default.DetachedHeadClass = class<SeveredAppendage>(DynamicLoadObject(default.DetachedHeadClassRef, Class'Class'));
|
|
|
|
if( default.DetachedSpecialArmClassRef != "" )
|
|
default.DetachedSpecialArmClass = class<SeveredAppendage>(DynamicLoadObject(default.DetachedSpecialArmClassRef, Class'Class'));
|
|
*/
|
|
if(myLevel == none)
|
|
return;
|
|
|
|
if( default.DetachedArmClass != none )
|
|
myLevel.AddPrecacheStaticMesh(default.DetachedArmClass.default.StaticMesh);
|
|
|
|
if( default.DetachedLegClass != none )
|
|
myLevel.AddPrecacheStaticMesh(default.DetachedLegClass.default.StaticMesh);
|
|
|
|
if( default.DetachedHeadClass != none )
|
|
myLevel.AddPrecacheStaticMesh(default.DetachedHeadClass.default.StaticMesh);
|
|
|
|
if( default.DetachedSpecialArmClass != none )
|
|
myLevel.AddPrecacheStaticMesh(default.DetachedSpecialArmClass.default.StaticMesh);
|
|
}
|
|
|
|
static simulated function PreCacheMaterials(LevelInfo myLevel)
|
|
{//should be derived and used.
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
RootBone="CHR_Pelvis"
|
|
HeadBone=head//"CHR_Head"//head
|
|
SpineBone1="CHR_Spine2"//spine
|
|
SpineBone2="CHR_Spine3"
|
|
FireRootBone="CHR_Spine1"
|
|
|
|
LeftShoulderBone=lshoulder//"CHR_LArmCollar"//lshoulder
|
|
RightShoulderBone=rshoulder//"CHR_RArmCollar"//rshoulder
|
|
LeftThighBone=lthigh//"CHR_LThigh"//lthigh
|
|
RightThighBone=rthigh//"CHR_RThigh"//rthigh
|
|
LeftFArmBone=lfarm//"CHR_LArmForeArm"//lfarm
|
|
RightFArmBone=rfarm//"CHR_RArmForeArm"//rfarm
|
|
LeftFootBone=lfoot//"CHR_LAnkle"//lfoot
|
|
RightFootBone=rfoot//"CHR_RAnkle"//rfoot
|
|
LeftHandBone=lefthand//"CHR_LArmPalm"//lefthand
|
|
RightHandBone=righthand//"CHR_RArmPalm"//righthand
|
|
NeckBone="CHR_Neck"
|
|
ExtCollAttachBoneName="Bip01"
|
|
|
|
HeadlessWalkAnims(0)="WalkF_Headless"
|
|
HeadlessWalkAnims(1)="WalkB_Headless"
|
|
HeadlessWalkAnims(2)="WalkL_Headless"
|
|
HeadlessWalkAnims(3)="WalkR_Headless"
|
|
BurningWalkFAnims(0)="WalkF_Fire"
|
|
BurningWalkFAnims(1)="WalkF_Fire"
|
|
BurningWalkFAnims(2)="WalkF_Fire"
|
|
BurningWalkAnims(0)="WalkB_Fire"
|
|
BurningWalkAnims(1)="WalkL_Fire"
|
|
BurningWalkAnims(2)="WalkR_Fire"
|
|
MeleeAnims(0)="Claw"
|
|
MeleeAnims(1)="Claw2"
|
|
MeleeAnims(2)="Claw3"
|
|
HitAnims(0)="HitF"
|
|
HitAnims(1)="HitF2"
|
|
HitAnims(2)="HitF3"
|
|
KFHitFront="HitReactionF"
|
|
KFHitBack="HitReactionB"
|
|
KFHitLeft="HitReactionL"
|
|
KFHitRight="HitReactionR"
|
|
MaxSpineVariation=1000
|
|
MaxContortionPercentage=0.250000
|
|
MinTimeBetweenPainAnims=0.500000
|
|
playedHit=True
|
|
ZombieDamType(0)=Class'KFMod.ZombieMeleeDamage'
|
|
ZombieDamType(1)=Class'KFMod.ZombieMeleeDamage'
|
|
ZombieDamType(2)=Class'KFMod.ZombieMeleeDamage'
|
|
PuntAnim="PoundPunt"
|
|
BloodStreakInterval=0.25000
|
|
DeathAnim(0)=
|
|
DeathAnim(1)=
|
|
DeathAnim(2)=
|
|
DeathAnim(3)=
|
|
bCanDodge=False
|
|
ChallengeSound(0)=None
|
|
ChallengeSound(1)=None
|
|
ChallengeSound(2)=None
|
|
ChallengeSound(3)=None
|
|
GruntVolume=0.500000
|
|
FootstepVolume=1.000000
|
|
GibGroupClass=Class'KFMod.KFNoGibGroup'
|
|
SoundGroupClass=Class'KFMod.KFMaleZombieSounds'
|
|
IdleHeavyAnim="Idle_LargeZombie"
|
|
IdleRifleAnim="Idle_LargeZombie"
|
|
FireHeavyRapidAnim="MeleeAttack"
|
|
FireHeavyBurstAnim="MeleeAttack"
|
|
FireRifleRapidAnim="MeleeAttack"
|
|
FireRifleBurstAnim="MeleeAttack"
|
|
DeResTime=6.000000
|
|
DeResMat0=Texture'KFCharacters.KFDeRez'
|
|
DeResMat1=Texture'KFCharacters.KFDeRez'
|
|
DeResLiftVel=(Points=(,(InVal=0.000000,OutVal=0.000000),(InVal=0.000000,OutVal=0.000000)))
|
|
DeResLiftSoftness=(Points=((OutVal=0.000000),(InVal=0.000000,OutVal=0.000000),(InVal=0.000000,OutVal=0.000000)))
|
|
DeResLateralFriction=0.000000
|
|
CorpseLifeSpan = 120.0
|
|
RequiredEquipment(0)="none"
|
|
RequiredEquipment(1)="none"
|
|
bCanSwim=False
|
|
bCanStrafe=False
|
|
bSameZoneHearing=True
|
|
bAdjacentZoneHearing=True
|
|
bMuffledHearing=False
|
|
bAroundCornerHearing=True
|
|
bCanUse=False
|
|
PeripheralVision=360.000000
|
|
SkillModifier=5.000000
|
|
MeleeRange=50.000000
|
|
JumpZ=320.000000
|
|
MaxFallSpeed=2500.0//600.000000
|
|
ControllerClass=Class'KFMod.KFMonsterController'
|
|
TurnLeftAnim="TurnLeft"
|
|
TurnRightAnim="TurnRight"
|
|
IdleCrouchAnim="Idle_LargeZombie"
|
|
IdleWeaponAnim="Idle_LargeZombie"
|
|
IdleRestAnim="Idle_LargeZombie"
|
|
bDramaticLighting=False
|
|
AmbientGlow=0
|
|
bFullVolume=False
|
|
SoundRadius=80.0
|
|
SoundVolume=50
|
|
AmbientSoundScaling=6.83
|
|
TransientSoundVolume=1.000000
|
|
bBlockKarma=True
|
|
Mass=100.0//300.000000
|
|
|
|
Begin Object Class=KarmaParamsSkel Name=PawnKParams
|
|
KFriction=1.3
|
|
KRestitution=0.2
|
|
KAngularDamping=0.05
|
|
KLinearDamping=0.15
|
|
KBuoyancy=1
|
|
KStartEnabled=True
|
|
KImpactThreshold=85
|
|
KVelDropBelowThreshold=50
|
|
KConvulseSpacing=(Min=0.5,Max=2.2)
|
|
bHighDetailOnly=False
|
|
End Object
|
|
KParams=KarmaParamsSkel'KFMod.KFMonster.PawnKParams'
|
|
|
|
BurnEffect=class'KFMonsterFlame'
|
|
AltBurnEffect=class'KFAltMonsterFlame'
|
|
|
|
MonsterHeadGiblet = class 'ClotGibHead'
|
|
MonsterThighGiblet = class 'ClotGibThigh'
|
|
MonsterArmGiblet = class 'ClotGibArm'
|
|
MonsterLegGiblet = class 'ClotGibLeg'
|
|
MonsterTorsoGiblet = class 'ClotGibTorso'
|
|
MonsterLowerTorsoGiblet = class 'ClotGibLowerTorso'
|
|
WalkingPct=+1.0
|
|
CrouchedPct=+1.0
|
|
bCanCrouch=false
|
|
FeedThreshold = 0.10
|
|
|
|
CollisionRadius=26.000000
|
|
|
|
bRealtimeShadows= false
|
|
|
|
SightRadius=+20000.0
|
|
Alertness = 1.0
|
|
HearingThreshold=+20000.0
|
|
|
|
HeadRadius=7.0
|
|
|
|
Intelligence=BRAINS_Human
|
|
BileFrequency=0.5
|
|
|
|
RagdollLifeSpan=30.000000
|
|
RagInvInertia=4.0
|
|
RagDeathVel=100.000000
|
|
RagDeathUpKick=0.000000
|
|
RagShootStrength=200
|
|
RagSpinScale=7.5
|
|
RagMaxSpinAmount=100
|
|
RagGravScale=1.0
|
|
RagImpactSoundInterval=0.25
|
|
RagImpactSounds(0)=sound'KF_EnemyGlobalSnd.Zomb_BodyImpact'
|
|
|
|
SeveredHeadAttachScale=1.0
|
|
SeveredLegAttachScale=1.0
|
|
SeveredArmAttachScale=1.0
|
|
SeveredHeadAttachClass = class'SeveredHeadAttachment'
|
|
SeveredArmAttachClass = class'SeveredArmAttachment'
|
|
SeveredLegAttachClass = class'SeveredLegAttachment'
|
|
NeckSpurtEmitterClass=class'DismembermentJetHead'
|
|
NeckSpurtNoGibEmitterClass=class'DismembermentJetDecapitate'
|
|
LimbSpurtEmitterClass=class'DismembermentJetLimb'
|
|
ObliteratedEffectClass=class'PlayerObliteratedEmitter'
|
|
// These two are fallbacks. Should assign these in subclasses
|
|
//DetachedArmClassRef="ROEffects.SeveredArm"
|
|
//DetachedLegClassRef="ROEffects.SeveredLeg"
|
|
//DetachedHeadClassRef="KFMod.SeveredHead"
|
|
ProjectileBloodSplatClass=class'ROEffects.ProjectileBloodSplat'
|
|
HeadLessDeathSound=sound'KF_EnemyGlobalSnd.Zomb_HeadlessDie'
|
|
DecapitationSound=sound'KF_EnemyGlobalSnd.Generic_Decap'
|
|
//MeleeAttackHitSoundRef="KF_EnemyGlobalSnd.Zomb_HitPlayer_Claw"
|
|
|
|
MoanVolume=1.5
|
|
PlayerCountHealthScale=0.0
|
|
BleedOutDuration=5.0
|
|
PoundRageBumpDamScale=1.0
|
|
HiddenGroundSpeed=300.0
|
|
HeadHeight=2.0
|
|
HeadScale=1.1
|
|
CrispUpThreshhold=5
|
|
OnlineHeadshotScale=1.0
|
|
HeadHealth=25
|
|
PlayerNumHeadHealthScale=0.0
|
|
MotionDetectorThreat=1.0
|
|
StunTime=1.0
|
|
StunsRemaining=-1
|
|
ZapDuration=4.0
|
|
ZappedSpeedMod=0.5
|
|
ZapThreshold=0.25
|
|
ZappedDamageMod=2.0
|
|
ZapResistanceScale=2.0
|
|
DamageToMonsterScale=3.0
|
|
HumanBileAggroChance=0.75
|
|
bHarpoonToHeadStuns=true
|
|
bHarpoonToBodyStuns=true
|
|
}
|