NicePack/sources/Zeds/NiceMonster.uc

2361 lines
93 KiB
Ucode

//==============================================================================
// NicePack / NiceMonster
//==============================================================================
// New base class for zeds that makes it easier to implement various changes
// and bugfixes.
// Functionality:
// - Variable zed stun time and unstun at any moment;
// - Temperature system for zeds, that allows ignition be 'accumulated'
// through several shots, rather that instantenious +
// supports freezing mechanic;
// - Increased complexity of some mechanics, like supporting
// float-valued level of headshots instead of simple
// true/false-switch;
// - Fixed decapitation visuals.
//==============================================================================
// 'Nice pack' source
// Do whatever the fuck you want with it
// Author: dkanus
// E-mail: dkanus@gmail.com
//==============================================================================
class NiceMonster extends KFMonster
hidecategories(AnimTweaks,DeRes,Force,Gib,Karma,Udamage,UnrealPawn)
abstract;
#exec OBJ LOAD FILE=KF_EnemyGlobalSndTwo.uax
#exec OBJ LOAD FILE=KFZED_FX_T.utx
var bool initializationComplete;
//==============================================================================
//==============================================================================
// > Affliction system
// This class, like the vanilla one, supports 3 types of afflictions:
// - Stun: fixes zed in place for a period of time or until
// zed-specific unstun conditions are met
// - Flinch: prevents zed from attacking for a short periodof time,
// defined by a variable named 'StunTime' for some stupid-fuck-reason
// - Mini-flinch: forced anim-action that can cancel other actions of zeds
// Describes possible affliction reactions of zeds to damage
enum EPainReaction{
PREACTION_NONE,
PREACTION_MINIFLINCH,
PREACTION_FLINCH,
PREACTION_STUN
};
//==============================================================================
// >> Stun rules can be overwritten for each zed independently,
// but the default behaviour is to check two conditions:
// 1. Check a stun score of the attack against a certain threshold.
// 2. Check if zed wasn't already stunned too many times
// (by default amount of stuns is unlimited,
// but can be configured by changing a variable's value)
// Defines if zed is currently stunned
var bool bIsStunned;
// By how much to change stun duration for the zed?
var float stunDurationMultiplier;
// Stun score (defined as ratio of the default health), required to stun a zed;
// always expected to be positive.
var float stunThreshold;
// How many times this zed can be stunned;
// negative values mean there's no limit
var int remainingStuns;
// Standart stun duration;
// automatically aquired from the length of stun animation
var float stunDuration;
// Stores the time left until the end of the current stun
var float stunCountDown;
// When the last stun occured;
// doesn't update on extending active stuns (restuns)
// Set to negative value by default
var float lastStunTime;
// Variables that define animation frames between which we can loop animation
// and a frame, from which we can insert idle animation
// (in case there isn't enough time for another loop)
var float stunLoopStart, stunLoopEnd, idleInsertFrame;
// Internal variables that define the state of animatin used to depict stun
// before the current tick
var bool bWasIdleStun; // Were we using idleanimation for stun?
var float prevStunAnimFrame; // At which tick we were?
//==============================================================================
// >> Another default stun system allows to stun zeds by repeatedly dealing
// head damage to them fast enough:
// 1. Dealing head damage increases accumulated damage
// 2. If head damage was received for a certain time period, -
// accumulated damage starts to decrease
// 3. If accumulated damage is high enough, passes 'IsStunPossible' check
// - normal duration stun activates
// How much more time will this zed spend concussed?
var float concussionCountdown;
// Accumulated head damage
var float accHeadDamage;
// Rate (per second) at which accumulated head damage diminishes
var float headDamageRecoveryRate;
// Time that needs to pass after last head damage for recovery to kick in
var float headRecoveryTime;
// Count down variable for above mentioned time
var float headRecoveryCountDown;
//==============================================================================
//==============================================================================
// > Temperature system
// Manages temperature (heat level) of the zed by either changing it because
// of elemental damage or bringing it back to zero (normal heat) with time.
// Allows for two afflictions to occure:
// 1. Zed being set on fire, which might cause a change in behaviour
// 2. Zed being frozen, fixing it in place
// Having this flag set to 'true' massively decreases damage
// taken from fire sources
var bool bFireImmune;
// Can this zed be set on fire?
var bool bCanBurn;
// Is zed currently burning?
var bool bOnFire;
// Tracks whether or not zed's behaviour was changed to the burning one
var bool bBurningBehavior;
// Having positive fuel values means that zed's heat will increase while it
// burining and will decrease otherwise;
// Both variables are automatically setup for zed upon spawning
var float flameFuel, initFlameFuel;
// Defines how much fuel (relative to scaled max health) zed will have
var float fuelRatio;
// Current heat level of the zed:
// - Getting it high enough results in zed burning
// - Getting it low enough results in zed being frozen
var float heat;
// Rate at which heat restores itself to default (zero) value
var float heatDissipationRate;
// Affects how often heat value updates and how often fire/cold DoT ticks
var float heatTicksPerSecond;
// Tracks last time heat tick occured
var float lastHeatTick;
var float scorchmarkDamage;
var float MIN_HEAT, MAX_HEAT;
//==============================================================================
//==============================================================================
// > Speed system
// Manages speed modifications from weapon attacks and other effects.
//
// This variable stores with how much stopping power zed was affected.
// Is a value from 0.0 to 1.0 and `1 - stoppingEffect` acts as speed multiplier
// for the zed
var float stoppingEffect;
// How much stopping effect would be recovred in a second
var float stoppingRecoveryRate;
// Maximum `stoppingEffect` value zed can accumulate
var float maxStoppingEffect;
// Minimal stopping threshold for the zed (supposed to be subtracted from
// incoming effects' strenght).
var float minStoppingThreshold;
//==============================================================================
//==============================================================================
// > Miscellaneous variables
//==============================================================================
// >> Head-damage related
// 'true' means any headshot ill destroy head completely
var bool bWeakHead;
// Head radius scale for client-side hitdetection
var float clientHeadshotScale;
// Stores maximum head health zed can have
var float headHealthMax;
//==============================================================================
// >> Auxiliary variables for general purposes
var ScrnGameRules scrnRules;
var float lastTookDamageTime;
// 'CuteDecapFX' already performed decapitation on thiszed
var bool bCuteDecapDone;
// Don't run decap tick (makes sure that head is visually destroyed)
// for this zed
var bool bSkipDecapTick;
//==============================================================================
// >> Auxiliary variables for perks/skills
// Time left before (medic's) madness wears off
var float madnessCountDown;
// Should this zed be decapitated in a melee-like manner?
var bool bMeleeDecapitated;
// More precise momentum replication
var float TearOffMomentumX, TearOffMomentumY, TearOffMomentumZ;
var const material FrozenMaterial;
var bool bFrozenZed, bFrozenZedClient;
var Rotator frozenRotation;
var array<name> frozenAnimations;
var array<float> frozenAnimFrames;
var Pawn frostInstigator;
var float lastFrostDamage;
var class<NiceWeaponDamageType> frostDamageClass;
var float iceCrustStrenght;
var bool isStatue;
var class<Emitter> ShatteredIce;
//==============================================================================
// >> Replacement variables to store current melee damage
var class<NiceWeaponDamageType> niceZombieDamType;
replication{
reliable if(Role < ROLE_Authority)
ServerDropFaster;
reliable if(Role == ROLE_Authority)
bMeleeDecapitated, TearOffMomentumX, TearOffMomentumY, TearOffMomentumZ,
bFrozenZed, frozenRotation;
}
simulated function PostBeginPlay(){
local GameRules rules;
local Vector AttachPos;
Super.PostBeginPlay();
// Auto-fill of some values
stunDuration = GetAnimDuration('KnockDown');
HeadHealthMax = HeadHealth;
InitFlameFuel = FuelRatio * HealthMax;
FlameFuel = InitFlameFuel;
if(Role == ROLE_Authority){ // auto-fill ScrnRules
rules = Level.Game.GameRulesModifiers;
while(rules != none){
if(ScrnGameRules(rules) != none){
scrnRules = ScrnGameRules(rules);
break;
}
rules = rules.NextGameRules;
}
}
// Fool-protection: in case we (someone) forgets to set both variables
clientHeadshotScale = FMax(clientHeadshotScale, OnlineHeadshotScale);
// Add zed-collisions on client too
if(Role < ROLE_Authority){
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;
}
}
initializationComplete = true;
}
//==============================================================================
//==============================================================================
// > Dismemberment-related functions
// Created to move some repeated code from 'HideBone' function
simulated function
AttachSeveredLimb( out SeveredAppendageAttachment replacement,
class<SeveredAppendageAttachment> replacementClass,
float replacementScale,
name attachmentPoint,
name boneName){
local coords boneCoords;
local class<DismembermentJet> emitterClass;
// Leave if replacement limb has already spawned
if(replacement != none) return;
// Decide on the right emitter class
if(boneName == headBone){
if(bNoBrainBitEmitter)
emitterClass = NeckSpurtNoGibEmitterClass;
else
emitterClass = NeckSpurtEmitterClass;
}
else
emitterClass = LimbSpurtEmitterClass;
// Spawn & attach replacement
replacement = Spawn(replacementClass, self);
replacement.SetDrawScale(replacementScale);
boneCoords = GetBoneCoords(attachmentPoint);
AttachEmitterEffect( emitterClass, attachmentPoint,
boneCoords.Origin, rot(0,0,0));
AttachToBone(replacement, attachmentPoint);
}
simulated function HideBone(name boneName){
switch(boneName){
case LeftThighBone:
SetBoneScale(0, 0.0, boneName);
AttachSeveredLimb( SeveredLeftLeg, SeveredLegAttachClass,
SeveredLegAttachScale, 'lleg', boneName);
break;
case RightThighBone:
SetBoneScale(1, 0.0, boneName);
AttachSeveredLimb( SeveredRightLeg, SeveredLegAttachClass,
SeveredLegAttachScale, 'rleg', boneName);
break;
case RightFArmBone:
SetBoneScale(2, 0.0, boneName);
AttachSeveredLimb( SeveredRightArm, SeveredArmAttachClass,
SeveredArmAttachScale, 'rarm', boneName);
break;
case LeftFArmBone:
SetBoneScale(3, 0.0, boneName);
AttachSeveredLimb( SeveredLeftArm, SeveredArmAttachClass,
SeveredArmAttachScale, 'larm', boneName);
break;
case HeadBone:
if(SeveredHead == none)
SetBoneScale(4, 0.0, boneName);
AttachSeveredLimb( SeveredHead, SeveredHeadAttachClass,
SeveredHeadAttachScale, 'neck', boneName);
break;
case 'spine':
SetBoneScale(5, 0.0, boneName);
}
}
simulated function CuteDecapFX(){
local int leftRight;
if(!bCuteDecapDone){
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);
RemoveHead();
}
bCuteDecapDone = true;
SetBoneRotation('neck', NeckRot);
}
simulated function DecapFX( Vector DecapLocation,
Rotator DecapRotation,
bool bSpawnDetachedHead,
optional bool bNoBrainBits){
if(SeveredHead != none)
return;
super.DecapFX( DecapLocation, DecapRotation,
bSpawnDetachedHead, bNoBrainBits);
}
//==============================================================================
//==============================================================================
// > Routins that should be executed every tick
// (and ones closely related to them), moved out from the 'Tick' function
// to make it more comprehensible.
//==============================================================================
// >> Handles medic's madness count down
simulated function MadnessTick(float deltaTime){
if(madnessCountDown > 0)
madnessCountDown -= DeltaTime;
if(madnessCountDown < 0.0)
madnessCountDown = 0.0;
}
//==============================================================================
// >> Makes sure zed loses it's head upon decapitation
simulated function DecapTick(float deltaTime){
local Coords boneCoords;
if(Role == ROLE_Authority) return;
if(bDecapitated && SeveredHead == none && !bSkipDecapTick){
boneCoords = GetBoneCoords(HeadBone);
if(bMeleeDecapitated)
DecapFX(boneCoords.Origin, rot(0, 0, 0), true);
else
DecapFX(boneCoords.Origin, rot(0, 0, 0), false);
}
}
//==============================================================================
// >> Makes zeds REALLY avoid fear spots
simulated function FearTick(float deltaTime){
local Vector fearCenter;
local bool fstFearAffects, sndFearAffects;
if(Role < ROLE_Authority || controller == none || bShotAnim) return;
// What spots saffect this zed and if affected at all
if( controller.fearSpots[0] != none
&& Controller.fearSpots[0].RelevantTo(self))
fstFearAffects = true;
if( controller.fearSpots[1] != none
&& Controller.fearSpots[1].RelevantTo(self))
sndFearAffects = true;
if(!fstFearAffects && !sndFearAffects) return;
// Calculate fear center
if(fstFearAffects)
fearCenter = controller.fearSpots[0].Location;
if(sndFearAffects)
fearCenter = controller.fearSpots[1].Location;
if(fstFearAffects && sndFearAffects)
fearCenter *= 0.5 * fearCenter;
// Accelerate zed in the right direction
acceleration = acceleration * 0.25
+ 0.75 * accelRate * Normal(Location - fearCenter);
}
//==============================================================================
// >> Decrease active stopping power on the zed as the time goes by
simulated function StoppingPowerTick(float deltaTime) {
if (stoppingEffect <= 0.0 || concussionCountdown > 0.0) {
return;
}
stoppingEffect -= deltaTime * stoppingRecoveryRate;
stoppingEffect = FMax(0, stoppingEffect);
}
//==============================================================================
// >> Set of functions to handle animation changes during stun
simulated function CalcRemainigStunStructure( name seqName,
float oFrame,
float oRate,
out int stunLoopsLeft,
out float stunLeftover){
local float loopDuration, temp;
loopDuration = (stunLoopEnd - stunLoopStart) * stunDuration;
if(seqName == IdleRestAnim){
stunLoopsLeft = 0;
stunLeftover = StunCountDown - (1 - idleInsertFrame) * stunDuration;
}
else if(prevStunAnimFrame < stunLoopEnd && loopDuration > 0.0){
temp = StunCountDown - (1 - oFrame) * stunDuration;
if(temp <= 0){
stunLoopsLeft = 0;
stunLeftover = 0.0;
}
else{
stunLoopsLeft = Ceil(temp / loopDuration) - 1;
stunLeftover = temp - stunLoopsLeft * loopDuration;
}
}
else{
stunLoopsLeft = 0;
stunLeftover = StunCountDown - (1 - oFrame) * stunDuration;
}
if(stunLeftover < 0.0)
stunLeftover = 0.0;
}
simulated function UpdateStunAnim( name seqName,
float oFrame,
float oRate,
int stunLoopsLeft,
float stunLeftover){
local bool bIdleFramePassed, bLoopEndFramePassed, bNotIdle;
if(!bIsStunned){
bWasIdleStun = (seqName == IdleRestAnim);
prevStunAnimFrame = oFrame;
return;
}
bNotIdle = (seqName != IdleRestAnim);
bIdleFramePassed = oFrame >= idleInsertFrame
&& prevStunAnimFrame < idleInsertFrame;
bLoopEndFramePassed = oFrame >= stunLoopEnd
&& prevStunAnimFrame < stunLoopEnd;
// Hit on idle flag
// (and have no stun loops left, while there's enough leftovers left)
if( bNotIdle && bIdleFramePassed
&& stunLoopsLeft <= 0 && stunLeftover > 0.2){
PlayAnim(IdleRestAnim,, 0.1);
bWasIdleStun = true;
prevStunAnimFrame = -1.0;
}
// Hit on loopEnd flag detected and there's loops left
else if(bNotIdle && bLoopEndFramePassed && stunLoopsLeft > 0){
SetAnimFrame(stunLoopStart);
bWasIdleStun = false;
prevStunAnimFrame = stunLoopStart;
}
else{
bWasIdleStun = !bNotIdle;
prevStunAnimFrame = oFrame;
}
}
simulated function StunTick(float deltaTime){
local name seqName;
local float oFrame, oRate;
// How many full loops we can play and leftovers after them
local int stunLoopsLeft;
local float stunLeftover;
//// Handle stun count down
if(bIsStunned)
GetAnimParams(0, seqName, oFrame, oRate);
StunCountDown -= DeltaTime;
if(StunCountDown < 0.0)
StunCountDown = 0.0;
if(bIsStunned && StunCountDown <= 0)
Unstun();
// Animation update
// Compute stun loops left and their leftovers
CalcRemainigStunStructure( seqName, oFrame, oRate,
stunLoopsLeft, stunLeftover);
// Make decisions based on current state of stun
UpdateStunAnim( seqName, oFrame, oRate,
stunLoopsLeft, stunLeftover);
}
//==============================================================================
// >> Set of functions to handle stun from head damage accumulation
function AccumulateHeadDamage( float addDamage,
bool bIsHeadshot,
NicePlayerController nicePlayer){
if(bIsHeadshot){
AccHeadDamage += addDamage * 0.5;
HeadRecoveryCountDown = HeadRecoveryTime;
if(AccHeadDamage > (default.HeadHealth / 1.5)
&& (concussionCountdown > 0.0 && IsStunPossible()))
DoStun(nicePlayer.pawn,,,, 1.0);
}
else if(HeadRecoveryCountDown > 0.0)
HeadRecoveryCountDown = FMin( HeadRecoveryCountDown,
HeadRecoveryTime / 2);
}
function HeadDamageRecoveryTick(float delta){
concussionCountdown -= delta;
concussionCountdown = FMax(0.0, concussionCountdown);
headRecoveryCountDown -= delta;
headRecoveryCountDown = FMax(0.0, headRecoveryCountDown);
if(headRecoveryCountDown <= 0.0)
accHeadDamage -= delta * HeadDamageRecoveryRate;
accHeadDamage = FMax(accHeadDamage, 0.0);
}
//==============================================================================
// >> Function that calls actual 'HeatTick' when needed
simulated function FakeHeatTick(float deltaTime){
local int i;
local name seqName;
local float oFrame;
local float oRate;
local NiceMonsterController niceZedController;
if (bOnFire) {
// 100 of the scorchmark damage should restore every 1/10 of a second
scorchmarkDamage += deltaTime * 10 * 100;
scorchmarkDamage = FMin(scorchmarkDamage, 100);
}
if(lastHeatTick + (1 / HeatTicksPerSecond) < Level.TimeSeconds){
if(bOnFire && !bBurningBehavior)
SetBurningBehavior();
if(!bOnFire && bBurningBehavior)
UnSetBurningBehavior();
HeatTick();
lastHeatTick = Level.TimeSeconds;
}
if(bFrozenZedClient != bFrozenZed){
bFrozenZedClient = bFrozenZed;
if(bFrozenZed){
frozenAnimations.length = 0;
frozenAnimFrames.length = 0;
while(IsAnimating(i)){
GetAnimParams(i, seqName, oFrame, oRate);
frozenAnimations[i] = seqName;
frozenAnimFrames[i] = oFrame;
i ++;
}
}
}
if(bFrozenZed){
if(Role < Role_AUTHORITY){
GetAnimParams(0, seqName, oFrame, oRate);
for(i = 0;i < frozenAnimations.length;i ++){
PlayAnim(frozenAnimations[i],,, i);
if(frozenAnimFrames.length > i)
SetAnimFrame(frozenAnimFrames[i], i);
}
}
else
StopAnimating();
StopMovement();
SetRotation(frozenRotation);
niceZedController = NiceMonsterController(controller);
if(niceZedController != none && !controller.IsInState('Freeze')){
controller.GotoState('Freeze');
niceZedController.bUseFreezeHack = true;
niceZedController.focus = none;
niceZedController.focalPoint = location + 512 * vector(rotation);
}
}
}
//==============================================================================
// >> Ticks from TWI's code
// Updates zed's speed if it's not relevant;
// code, specific to standalone game and listen servers was cut out
// Kill zed if it has been bleeding long enough
simulated function BleedOutTick(float deltaTick)
{
if (Role < ROLE_Authority || !bDecapitated)
return;
if (BleedOutTime <= 0 || Level.TimeSeconds < BleedOutTime)
return;
if (LastDamagedBy != none)
Died(LastDamagedBy.Controller, class'DamTypeBleedOut', Location);
// else we can say we killed ourselves, none -> controller (self)
// P.S. we really need to use Suicide here D:
else
Died(controller, class'DamTypeBleedOut', Location);
BleedOutTime = 0;
}
// FX-stuff TWI did in the tick, unchanged
simulated function TWIFXTick(float deltaTime){
if(Level.netMode == NM_DedicatedServer) return;
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;
}
}
simulated function TWIDECAPTick(float deltaTime){
if(!DECAP) return;
if(Level.TimeSeconds <= (DecapTime + 2.0) || Controller == none) return;
DECAP = false;
MonsterController(Controller).ExecuteWhatToDoNext();
}
simulated function BileTick(float deltaTime){
if(BileCount <= 0 || NextBileTime >= level.TimeSeconds) return;
BileCount --;
NextBileTime += BileFrequency;
TakeBileDamage();
}
// TWI's code, separeted into shorter functions and segments
simulated function TWITick(float deltaTime){
// If we've flagged this character to be destroyed next tick, handle that
if(bDestroyNextTick && TimeSetDestroyNextTickTime < Level.TimeSeconds)
Destroy();
// Reset AnimAction
if(bResetAnimAct && ResetAnimActTime < Level.TimeSeconds){
AnimAction = '';
bResetAnimAct = False;
}
// Update look target
if(Controller != none)
LookTarget = Controller.Enemy;
// Some more ticks
BleedOutTick(deltaTime);
TWIFXTick(deltaTime);
TWIDECAPTick(deltaTime);
BileTick(deltaTime);
}
//==============================================================================
// >> Actual tick function, that is much shorter and manageble now
simulated function Tick(float deltaTime){
// NicePack-specific ticks
MadnessTick(deltaTime);
DecapTick(deltaTime);
FearTick(deltaTime);
StunTick(deltaTime);
StoppingPowerTick(deltaTime);
HeadDamageRecoveryTick(deltaTime);
FakeHeatTick(deltaTime);
UpdateGroundSpeed();
// TWI's tick
TWITick(deltaTime);
}
simulated function bool IsFinisher( int damage,
class<NiceWeaponDamageType> niceDmg,
NicePlayerController nicePlayer,
optional bool isHeadshot){
local bool hasTrashCleaner, isReaperActive;
if(nicePlayer == none) return false;
hasTrashCleaner = class'NiceVeterancyTypes'.static.hasSkill(nicePlayer,
class'NiceSkillCommandoTrashCleaner');
isReaperActive = false;
if(nicePlayer.abilityManager != none){
isReaperActive = nicePlayer.abilityManager.IsAbilityActive(
class'NiceSkillSharpshooterReaperA'.default.abilityID);
}
if( (niceDmg == none || !niceDmg.default.bFinisher)
&& (!hasTrashCleaner || default.health >= 500)
&& (!isReaperActive || !isHeadshot) )
return false;
return (isHeadshot && damage >= headHealth)
|| (!isHeadshot && damage >= health);
}
// Checks current zed for head-shot
// Returns result as 'float' value from 0.0 to 1.0,
// where 1.0 means perfect head-shot and 0.0 means a miss
simulated function float IsHeadshotClient( Vector Loc,
Vector Ray,
optional float additionalScale){
local Coords C;
local Vector HeadLoc;
local float distance;
local Vector HeadToLineOrig;
// Let A be such a dot on the bullet trajectory line,
// that vector between A and a head is a normal to the line and, therefore,
// the shortest distance
local Vector AToLineOrig;
local Vector lineDir;
if(HeadBone == '')
return 0.0;
C = GetBoneCoords(HeadBone);
if(additionalScale == 0.0)
additionalScale = 1.0;
HeadLoc = C.Origin + headHeight * headScale * C.XAxis;
HeadToLineOrig = Loc - HeadLoc;
lineDir = Normal(Ray);
// If we project 'HeadToLineOrig' onto the line,
// - line origin ('Loc') will go to itself and Head center ('HeadLoc')
// to the point A
// So we'll get a vector between A and head center
AToLineOrig = (HeadToLineOrig Dot lineDir) * lineDir;
distance = VSize(HeadToLineOrig - AToLineOrig);
if(distance < headRadius * headScale * additionalScale)
return 1.0 - (distance / (headRadius * headScale * additionalScale));
return 0.0;
}
// Calculates distance from `location` to this zed's head.
simulated function float GetDistanceToHead(Vector location) {
local Coords headBoneCoords;
local Vector headLocation;
if(headBone == '') {
return 0.0;
}
headBoneCoords = GetBoneCoords(headBone);
headLocation =
headBoneCoords.Origin + headHeight * headScale * headBoneCoords.XAxis;
return VSize(location - headLocation);
}
// In case of a future modifications:
// check if it's a player doing damage before relying on it
function ModDamage( out int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI,
optional float lockonTime){
local NicePlayerController nicePlayer;
local bool hasGiantSlayer;
local int bonusDamageStacks;
if(KFPRI == none || KFPRI.ClientVeteranSkill == none) return;
// Add perked damage
damage = KFPRI.ClientVeteranSkill.Static.AddDamage( KFPRI, self,
KFPawn(instigatedBy),
damage, damageType);
// Skill bonuses
if(instigatedBy == none)
return;
nicePlayer = NicePlayerController(instigatedBy.controller);
if(nicePlayer == none)
return;
hasGiantSlayer = class'NiceVeterancyTypes'.static.hasSkill(nicePlayer,
class'NiceSkillCommandoGiantSlayer');
if(!hasGiantSlayer)
return;
bonusDamageStacks =
int(health / class'NiceSkillCommandoGiantSlayer'.default.healthStep);
damage *= 1.0f + bonusDamageStacks *
class'NiceSkillCommandoGiantSlayer'.default.bonusDamageMult;
}
function ModRegularDamage( out int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI,
optional float lockonTime){
local bool hasOverkillSkill;
local NicePlayerController nicePlayer;
local class<NiceVeterancyTypes> niceVet;
if(KFPRI == none) return;
if(instigatedBy != none)
nicePlayer = NicePlayerController(instigatedBy.Controller);
niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
// Add perked damage
if(niceVet != none)
damage = niceVet.static.AddRegDamage( KFPRI, self,
KFPawn(instigatedBy), damage,
damageType);
// Skills bonuses
if(nicePlayer == none) return;
hasOverkillSkill = class'NiceVeterancyTypes'.static.
hasSkill(nicePlayer, class'NiceSkillSharpshooterZEDOverkill');
if(headshotLevel > 0.0 && nicePlayer.isZedTimeActive() && hasOverkillSkill)
damage *= class'NiceSkillSharpshooterZEDOverkill'.default.damageBonus;
}
function ModFireDamage( out int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI,
optional float lockonTime){
local class<NiceVeterancyTypes> niceVet;
if(KFPRI == none) return;
niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
// Add perked damage
if(niceVet != none)
damage = niceVet.static.AddFireDamage( KFPRI, self,
KFPawn(instigatedBy), damage,
damageType);
// Cut fire damage against fire-immune zeds
if(bFireImmune)
damage /= 10;
}
function ModHeadDamage( out int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> dmgType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI,
optional float lockonTime)
{
local bool shouldCountHS;
local NicePlayerController nicePlayer;
local class<NiceVeterancyTypes> niceVet;
if (instigatedBy != none)
nicePlayer = NicePlayerController(instigatedBy.Controller);
shouldCountHS = (lockonTime >= dmgType.default.lockonTime)
&& (headshotLevel > dmgType.default.prReqMultiplier);
// Weapon damage bonus
if (dmgType != none && shouldCountHS)
damage *= dmgType.default.HeadShotDamageMult;
// Perk damage bonus
if (KFPRI != none)
{
niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
if (niceVet != none)
damage *= niceVet.static.GetNiceHeadShotDamMulti(KFPRI, self, dmgType);
}
}
// This function must record damage actual value in 'damage' variable and
// return value that will decide stun/flinch
function int ModBodyDamage( out int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI,
optional float lockonTime){
local bool bHasMessy;
local bool bIsHeadShot;
local int painDamage;
local NicePlayerController nicePlayer;
painDamage = damage;
bIsHeadShot = (headshotLevel > 0.0);
if (instigatedBy != none)
nicePlayer = NicePlayerController(instigatedBy.Controller);
if (bFrozenZed && damageType != none && damageType.default.bIsExplosive) {
damage *= 1.5;
}
// On damaging critical spot (so far only head) - do body destruction
if (bIsHeadShot && damageType != none)
damage *= damageType.default.bodyDestructionMult;
// Skill bonuses
if (nicePlayer == none)
return painDamage;
bHasMessy = class'NiceVeterancyTypes'.static.
someoneHasSkill(nicePlayer, class'NiceSkillSharpshooterDieAlready');
return painDamage;
}
// Do effects, based on fire damage dealt to monster
function FireDamageEffects(out int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI){
local float heatDelta, bonusFireDamage;
damage = FMax(0.0, damage);
iceCrustStrenght = FMax(0.0, iceCrustStrenght);
if(bFrozenZed){
damage *= 10;
if(iceCrustStrenght <= damage){
damage -= iceCrustStrenght;
if(iceCrustStrenght >= 0){
iceCrustStrenght = 0;
UnFreeze();
}
}
else{
iceCrustStrenght -= damage;
damage = 0;
}
damage /= 10;
}
if(damage <= 0) {
return;
}
// Turn up the heat!
// (we can only make it twice as hot with that damage,
// but set limit at least at 50, as if we've dealt at least 25 heat damage)
heatDelta = (damage * HeatIncScale())
* FMin(1.0, FMax(0.0, (2 - Abs(heat) / FMax(25, Abs(damage) ))));
if (concussionCountdown > 0) {
heat += heatDelta * 2;
} else {
heat += heatDelta;
}
CapHeat();
// Change damage type if new one was stronger
if(!bOnFire || damage * HeatIncScale() > lastBurnDamage){
fireDamageClass = damageType;
burnInstigator = instigatedBy;
}
// Double heat damage at the cost of the fuel, if zed is already on fire
if (bOnFire) {
bonusFireDamage = FMax(FMin(damage, flameFuel) * 0.25, 0);
flameFuel -= bonusFireDamage;
damage += bonusFireDamage;
}
// Set on fire, if necessary
if(heat > GetIgnitionPoint() && !bOnFire && bCanBurn){
bBurnified = true;
bOnFire = true;
burnInstigator = instigatedBy;
fireDamageClass = damageType;
lastHeatTick = Level.TimeSeconds;
}
}
function FrostEffects( Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI){
local float freezePower;
local float heatChange;
if(damageType == none) return;
freezePower = damageType.default.freezePower;
heatChange = freezePower * (100.0 / default.health);
heat -= heatChange;
heat = FMax(-freezePower, heat);
CapHeat();
if(heat <= 0){
bBurnified = false;
UnSetBurningBehavior();
RemoveFlamingEffects();
StopBurnFX();
bOnFire = false;
//Log("Pre: strenght="$iceCrustStrenght@"/"@(freezePower * GetIceCrustScale()));
iceCrustStrenght += freezePower * GetIceCrustScale();
iceCrustStrenght = FMin(iceCrustStrenght, 150.0);
//Log("Crusts status: freezePower="$freezePower$", scale="$GetIceCrustScale()$", strenght="$iceCrustStrenght);
}
if(!bFrozenZed || freezePower * 0.05 > lastFrostDamage){
frostDamageClass = damageType;
frostInstigator = instigatedBy;
}
if(!bFrozenZed && iceCrustStrenght >= GetFreezingPoint())
Freeze();
}
function BileDamageEffect( int damage,
Pawn instigatedBy,
class<damageType> damageType){
if(class<DamTypeVomit>(damageType) != none){
BileCount = 7;
BileInstigator = instigatedBy;
LastBileDamagedByType=class<DamTypeVomit>(damageType);
if(NextBileTime < Level.TimeSeconds )
NextBileTime = Level.TimeSeconds + BileFrequency;
}
}
function float GetDecapDamageModifier( class<NiceWeaponDamageType> damageType,
NicePlayerController nicePlayer,
KFPlayerReplicationInfo KFPRI)
{
local float damageMod;
local bool shouldDoGoodDecap;
local bool hasTrashCleaner;
local bool isPerkedPickup;
local class<NiceWeaponPickup> pickupClass;
local class<NiceVeterancyTypes> niceVet;
// KFPRI accessed none fix
if (KFPRI != none)
niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
isPerkedPickup = false;
if (niceVet != none)
{
pickupClass = niceVet.static.GetPickupFromDamageType(damageType);
if (pickupClass != none)
isPerkedPickup = niceVet.static.IsPerkedPickup(pickupClass);
}
shouldDoGoodDecap = false;
shouldDoGoodDecap = (damageType.default.decapType == DB_DROP);
shouldDoGoodDecap = shouldDoGoodDecap ||
(damageType.default.decapType == DB_PERKED && isPerkedPickup);
if (shouldDoGoodDecap)
damageMod = damageType.default.goodDecapMod;
else
damageMod = damageType.default.badDecapMod;
if (nicePlayer != none)
hasTrashCleaner = class'NiceVeterancyTypes'.static.
hasSkill(nicePlayer, class'NiceSkillCommandoTrashCleaner');
if (hasTrashCleaner)
{
damageMod = FMin(damageMod, class'NiceSkillCommandoTrashCleaner'.default.decapitationMultiLimit);
}
return damageMod;
}
function DealDecapDamage( int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI,
optional float lockonTime){
local int decapDmg;
local NicePlayerController nicePlayer;
if(instigatedBy != none)
nicePlayer = NicePlayerController(instigatedBy.Controller);
RemoveHead();
if(damageType == none){
ModDamage( decapDmg, instigatedBy, hitLocation, momentum, damageType,
headshotLevel, KFPRI, lockonTime);
ModHeadDamage( decapDmg, instigatedBy, hitLocation, momentum,
damageType, headshotLevel, KFPRI, lockonTime);
}
else
{
decapDmg = Ceil(HealthMax * GetDecapDamageModifier( damageType,
nicePlayer, KFPRI));
}
DealBodyDamage( decapDmg, instigatedBy, hitLocation, momentum, damageType,
headshotLevel, KFPRI, lockonTime);
if(class'NiceVeterancyTypes'.static.
hasSkill(nicePlayer, class'NiceSkillSharpshooterDieAlready'))
ServerDropFaster(NiceHumanPawn(nicePlayer.pawn));
}
function DealHeadDamage( int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI,
optional float lockonTime){
local NicePlayerController nicePlayer;
local KFSteamStatsAndAchievements KFStatsAndAchievements;
if(instigatedBy != none)
nicePlayer = NicePlayerController(instigatedBy.Controller);
// Sound effects
PlaySound( Sound'KF_EnemyGlobalSndTwo.Impact_Skull', SLOT_none,
2.0, true, 500);
// Actual damage effects
// Skull injury killed a zed
if(HeadHealth <= 0) return;
// Additional weakpoint damage to burning zeds from non-flame weapons
if (bOnFire) {
damage += scorchmarkDamage * (1 - damageType.default.heatPart);
scorchmarkDamage *= damageType.default.heatPart;
}
HeadHealth -= damage;
if(nicePlayer != none && IsFinisher(damage, damageType, nicePlayer, true))
HeadHealth -= damage;
// Remove head for the weak creatures
if(bWeakHead && damage > 0 && HeadHealth > 0)
HeadHealth = 0;
if(HeadHealth <= 0 || damage > Health)
DealDecapDamage(damage, instigatedBy, hitLocation, momentum, damageType,
headshotLevel, KFPRI, lockonTime);
// Head damage accumulation
AccumulateHeadDamage(damage, headshotLevel > 0.0, nicePlayer);
// Award head-shot for achievements and stats
if(nicePlayer == none || damageType == none || !bDecapitated) return;
KFStatsAndAchievements =
KFSteamStatsAndAchievements(nicePlayer.SteamStatsAndAchievements);
damageType.static.ScoredNiceHeadshot( KFStatsAndAchievements, self.class,
scrnRules.HardcoreLevel);
}
function Vector RecalculateMomentum(Vector momentum,
Pawn instigatedBy,
class<NiceWeaponDamageType> damageType){
local bool bApplyMomentum;
if(Physics == PHYS_none)
SetMovementPhysics();
if(Physics == PHYS_Walking && damageType.default.bExtraMomentumZ)
momentum.Z = FMax(momentum.Z, 0.4 * VSize(momentum));
if(instigatedBy == self)
momentum *= 0.6;
momentum = momentum / mass;
bApplyMomentum = ShouldApplyMomentum(damageType);
if(Health > 0 && !bApplyMomentum)
momentum = vect(0 ,0, 0);
return momentum;
}
function ManageDeath( Vector hitLocation,
Vector momentum,
Pawn instigatedBy,
class<NiceWeaponDamageType> damageType,
float headshotLevel){
local bool bWorldOrSafeCaused;
local Controller killer;
if(damageType == none) return;
bWorldOrSafeCaused = damageType.default.bCausedByWorld
&& (instigatedBy == none || instigatedBy == self);
if(bWorldOrSafeCaused && LastHitBy != none)
killer = LastHitBy;
else if(instigatedBy != none)
killer = instigatedBy.GetKillerController();
if(killer == none && damageType.Default.bDelayedDamage)
killer = DelayedDamageInstigatorController;
if(bPhysicsAnimUpdate)
SetTearOffMomemtum(momentum);
if(bFrozenZed){
bHidden = true;
Spawn(ShatteredIce,,, location);
}
Died(killer, damageType, hitLocation);
if(headshotLevel > 0.0 && KFGameType(Level.Game) != none)
KFGameType(Level.Game).DramaticEvent(0.03);
}
function DealBodyDamage(int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI,
optional float lockonTime){
local int actualDamage;
local bool delayedDamage;
local NicePlayerController nicePlayer;
if(Health <= 0 || damageType == none || Role < ROLE_Authority) return;
// Find correct instigator and it's controller
delayedDamage = damageType.default.bDelayedDamage
&& DelayedDamageInstigatorController != none;
if((instigatedBy == none || instigatedBy.Controller == none)
&& delayedDamage)
instigatedBy = DelayedDamageInstigatorController.Pawn;
if(instigatedBy != none)
nicePlayer = NicePlayerController(instigatedBy.Controller);
// Apply game rules to damage
actualDamage = Level.Game.ReduceDamage( damage, self, instigatedBy,
hitLocation, Momentum, damageType);
// Reduce health
Health -= actualDamage;
if(IsFinisher(damage, damageType, nicePlayer))
{
Health -= actualDamage;
}
// Update location
if(hitLocation == vect(0,0,0))
hitLocation = Location;
// Update physics/momentum
momentum = RecalculateMomentum(momentum, instigatedBy, damageType);
// Generate effects
PlayHit(actualDamage, instigatedBy, hitLocation, damageType, Momentum);
// Add momentum to survivors / manage death
if(Health > 0){
AddVelocity(momentum);
if(controller != none)
controller.NotifyTakeHit( instigatedBy, hitLocation, actualDamage,
damageType, Momentum);
if(instigatedBy != none && instigatedBy != self)
LastHitBy = instigatedBy.controller;
}
else
ManageDeath(hitLocation, momentum, instigatedBy,
damageType, headshotLevel);
MakeNoise(1.0);
}
function Died( Controller killer,
class<DamageType> damageType,
vector HitLocation)
{
local bool bHasManiac;
local NiceHumanPawn nicePawn;
local class<NiceVeterancyTypes> niceVet;
if (killer != none || (Controller != none && killer != Controller))
{
// Maniac stuff
bHasManiac = class'NiceVeterancyTypes'.static.
HasSkill(NicePlayerController(killer), class'NiceSkillDemoManiac');
nicePawn = NiceHumanPawn(killer.pawn);
if (bHasManiac && nicePawn != none)
nicePawn.maniacTimeout =
class'NiceSkillDemoManiac'.default.reloadBoostTime;
// Enforcer's invincibility
if (nicePawn != none)
{
niceVet = class'NiceVeterancyTypes'.static.
GetVeterancy(nicePawn.playerReplicationInfo);
}
if ( niceVet != none
&& niceVet == class'NiceVetEnforcer'
&& nicePawn.invincibilityTimer > 0)
{
nicePawn.invincibilityTimer += default.healthMax / 500.0;
}
}
super.Died(killer, damageType, HitLocation);
}
simulated function SetTearOffMomemtum(vector NewMomentum){
TearOffMomentum = NewMomentum;
TearOffMomentumX = NewMomentum.X;
TearOffMomentumY = NewMomentum.Y;
TearOffMomentumZ = NewMomentum.Z;
}
simulated function vector GetTearOffMomemtum(){
TearOffMomentum.X = TearOffMomentumX;
TearOffMomentum.Y = TearOffMomentumY;
TearOffMomentum.Z = TearOffMomentumZ;
return TearOffMomentum;
}
function bool ShouldApplyMomentum(class<NiceWeaponDamageType> damageType){
if(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'
&& damageType!=class'NiceDamTypeM41AGrenade'
&& damageType!=class'NiceDamTypeRocket' NICETODO: sort this shit out*/
&& !ClassIsChildOf(damageType, class'NiceDamageTypeVetDemolitions'))
return false;
return true;
}
function EPainReaction GetRightPainReaction(int painDamage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> dmgType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI){
local int stunScore, flinchScore;
local bool bStunPass, bFlinchPass, bMiniFlinshPass;
local class<NiceVeterancyTypes> niceVet;
if (bOnFire) {
return PREACTION_NONE;
}
if(KFPRI != none)
niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
stunScore = painDamage;
flinchScore = painDamage;
if(dmgType != none){
stunScore *= dmgType.default.stunMultiplier;
flinchScore *= dmgType.default.flinchMultiplier;
}
if(niceVet != none){
flinchScore = niceVet.static.
AddFlinchScore( KFPRI, self, KFPawn(instigatedBy),
flinchScore, dmgType);
stunScore = niceVet.static.
AddStunScore( KFPRI, self, KFPawn(instigatedBy),
stunScore, dmgType);
}
bStunPass = CheckStun( stunScore, instigatedBy, hitLocation, momentum,
dmgType, headshotLevel, KFPRI);
bFlinchPass = CheckFlinch( flinchScore, instigatedBy, hitLocation,
momentum, dmgType, headshotLevel, KFPRI);
bMiniFlinshPass = CheckMiniFlinch( flinchScore, instigatedBy,
hitLocation, momentum, dmgType,
headshotLevel, KFPRI);
if(bStunPass) return PREACTION_STUN;
else if(bFlinchPass) return PREACTION_FLINCH;
else if(bMiniFlinshPass) return PREACTION_MINIFLINCH;
return PREACTION_NONE;
}
function DoRightPainReaction( int painDamage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> dmgType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI){
local EPainReaction painReaction;
painReaction = GetRightPainReaction(painDamage, instigatedBy,
hitLocation, momentum, dmgType,
headshotLevel, KFPRI);
switch(painReaction){
case PREACTION_STUN:
DoStun( instigatedBy, hitLocation, momentum, dmgType,
headshotLevel, KFPRI);
break;
case PREACTION_FLINCH:
DoFlinch( instigatedBy, hitLocation, momentum, dmgType,
headshotLevel, KFPRI);
break;
case PREACTION_MINIFLINCH:
DoMiniFlinch( instigatedBy, hitLocation, momentum, dmgType,
headshotLevel, KFPRI);
break;
}
if(Level.TimeSeconds - LastPainTime > 0.1)
LastPainTime = Level.TimeSeconds;
}
// Only called when stun is confirmed, so no need to re-check
function float GetstunDurationMult( Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI){
local class<NiceVeterancyTypes> niceVet;
// Default out
if(KFPRI == none) return stunDurationMultiplier;
niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
if(niceVet == none) return stunDurationMultiplier;
if(damageType == none) return stunDurationMultiplier;
// Perk's bonuses out
return stunDurationMultiplier * damageType.default.stunLengthMultiplier *
niceVet.static.stunDurationMult( KFPRI, self, KFPawn(instigatedBy),
damageType);
}
function bool IsStunPossible(){
return (remainingStuns != 0 || bIsStunned);
}
function bool CheckStun(int stunScore,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI){
if(bFrozenZed) return false;
if(stunScore > float(default.Health) * stunThreshold && IsStunPossible())
return true;
return false;
}
function bool CheckMiniFlinch( int flinchScore,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI){
local bool bOnCooldown;
if(bFrozenZed) return false;
if(instigatedBy == none || damageType == none) return false;
if(flinchScore < 5 || Health <= 0 || StunsRemaining == 0) return false;
bOnCooldown = Level.TimeSeconds - LastPainAnim < MinTimeBetweenPainAnims;
if(!bOnCooldown && HitCanInterruptAction())
return true;
return false;
}
function bool CheckFlinch( int flinchScore,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI){
local bool shouldFlinch;
local bool bCanMiniFlinch;
local Vector X, Y, Z, Dir;
// We must be able to perform at least a mini-flinch for a flinch to work
bCanMiniFlinch = CheckMiniFlinch( flinchScore, instigatedBy,
hitLocation, momentum, damageType,
headshotLevel, KFPRI);
if(!bCanMiniFlinch) return false;
GetAxes(Rotation, X, Y, Z);
hitLocation.Z = Location.Z;
// Actual flinch check
shouldFlinch = false;
// 1. Check direction
if(VSize(Location - hitLocation) < 1.0)
shouldFlinch = true;
else{
Dir = -Normal(Location - hitLocation);
shouldFlinch = Dir dot X > 0.7;
}
// 2. Can we still flinch? ('StunsRemaining' is amount of flinches
// remaining, cause stupid naming); note that negative value of
// 'StunsRemaining' means infinite flinches
shouldFlinch = shouldFlinch && (StunsRemaining != 0);
// 3. Do we have high enough 'flinchScore'?
if(ClassIsChildOf(damageType, class'NiceDamageTypeVetBerserker'))
shouldFlinch = shouldFlinch && flinchScore >= (0.1 * default.Health);
else
shouldFlinch = shouldFlinch && flinchScore >= (0.5 * default.Health);
return shouldFlinch;
}
function StopMovement(){
if(physics == PHYS_Falling)
SetPhysics(PHYS_Walking);
if(health > 0){
acceleration.X = 0;
acceleration.Y = 0;
velocity.X = 0;
velocity.Y = 0;
}
}
// Do the stun; no check, no conditions, just stun
function DoStun(optional Pawn instigatedBy,
optional Vector hitLocation,
optional Vector momentum,
optional class<NiceWeaponDamageType> damageType,
optional float headshotLevel,
optional KFPlayerReplicationInfo KFPRI){
local int i;
local float stunDurationMult;
local NicePack niceMut;
local NiceMonsterController niceController;
niceMut = class'NicePack'.static.Myself(Level);
niceController = NiceMonsterController(controller);
if(niceMut == none || niceController == none) return;
// Freeze zed and stop it from rotating
StopMovement();
niceController.GoToState('Freeze');
niceController.bUseFreezeHack = true;
niceController.focus = none;
niceController.focalPoint = location + 512 * vector(rotation);
// Reduce this value only if player was the one to make a flinch/stun and
// zed isn't currently stunned
if(remainingStuns > 0 && !bIsStunned && KFHumanPawn(InstigatedBy) != none)
remainingStuns --;
if(bIsStunned)
LastStunTime = Level.TimeSeconds;
else
SetAnimAction('KnockDown');
// Stunned flags
bSTUNNED = true;
bShotAnim = true;
bIsStunned = true;
stunDurationMult = GetStunDurationMult( instigatedBy, hitLocation, momentum,
damageType, headshotLevel, KFPRI);
stunCountDown = FMax(stunCountDown, stunDuration * stunDurationMult);
// Tell clients about a stun
for(i = 0;i < niceMut.playersList.Length;i ++)
if(niceMut.playersList[i] != none)
niceMut.playersList[i].ClientSetZedStun(self, true, stunCountDown);
}
simulated function Unstun(){
local int i;
local NicePack niceMut;
if(Health <= 0.0) return;
bSTUNNED = false;
bIsStunned = false;
bShotAnim = false;
bWaitForAnim = false;
bWasIdleStun = false;
prevStunAnimFrame = 0.0;
SetAnimFrame(1.0);
if(Role < Role_AUTHORITY) return;
if(Controller != none)
Controller.GoToState('ZombieHunt');
// Tell clients about a unstun
niceMut = class'NicePack'.static.Myself(Level);
if(niceMut == none)
return;
for(i = 0;i < niceMut.playersList.Length;i ++)
if(niceMut.playersList[i] != none)
niceMut.playersList[i].ClientSetZedStun(self, false, 0.0);
}
simulated function StunRefreshClient(bool bEnableStun){
local name seqName;
local bool leftLoop;
local float oFrame, oRate;
if(bEnableStun){
leftLoop = prevStunAnimFrame >= FMax(stunLoopEnd, idleInsertFrame);
// If we've already left the loop
// or were in the idle => restart animation
if(leftLoop || bWasIdleStun)
PlayAnim('KnockDown',, 0.1);
// Other than that - just register stun
bIsStunned = true;
}
else{
GetAnimParams(0, seqName, oFrame, oRate);
if(seqName == 'KnockDown' || seqName == IdleRestAnim)
SetAnimFrame(1.0);
bIsStunned = false;
}
}
// Do the flinch; no check, no conditions, just stun
function DoFlinch( optional Pawn instigatedBy,
optional Vector hitLocation,
optional Vector momentum,
optional class<NiceWeaponDamageType> damageType,
optional float headshotLevel,
optional KFPlayerReplicationInfo KFPRI){
SetAnimAction(HitAnims[Rand(3)]);
LastPainAnim = Level.TimeSeconds;
bSTUNNED = true;
SetTimer(StunTime, false);
// Reduce this value only if play was the one to make a flinch/stun
if(StunsRemaining > 0 && KFHumanPawn(InstigatedBy) != none)
StunsRemaining --;
PainSoundEffect(instigatedBy, hitLocation, momentum, damageType,
headshotLevel, KFPRI);
}
// Do the mini-flinch; no check, no conditions, just stun
function DoMiniFlinch( optional Pawn instigatedBy,
optional Vector hitLocation,
optional Vector momentum,
optional class<NiceWeaponDamageType> damageType,
optional float headshotLevel,
optional KFPlayerReplicationInfo KFPRI){
local Vector X,Y,Z, Dir;
GetAxes(Rotation, X, Y, Z);
hitLocation.Z = Location.Z;
Dir = -Normal(Location - hitLocation);
if(Dir dot X > 0.7 || VSize(Location - hitLocation) < 1.0)
SetAnimAction(KFHitFront);
else if(Dir Dot X < -0.7)
SetAnimAction(KFHitBack);
else if(Dir Dot Y > 0)
SetAnimAction(KFHitRight);
else
SetAnimAction(KFHitLeft);
LastPainAnim = Level.TimeSeconds;
PainSoundEffect(instigatedBy, hitLocation, momentum, damageType,
headshotLevel, KFPRI);
}
// Plays sound effect for flinch and updates last sound time
function PainSoundEffect( Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
KFPlayerReplicationInfo KFPRI){
local PlayerController Hearer;
if(damageType.default.bDirectDamage)
Hearer = PlayerController(instigatedBy.Controller);
if(Hearer != none)
Hearer.bAcuteHearing = true;
if(Level.TimeSeconds - LastPainSound < MinTimeBetweenPainSounds){
LastPainSound = Level.TimeSeconds;
if(class<NiceDamTypeFire>(damageType) == none)
PlaySound(HitSound[0], SLOT_Pain, 1.25,, 400);
}
if(Hearer != none)
Hearer.bAcuteHearing = false;
}
function UpdateLastDamageVars( Pawn instigatedBy,
int damage,
class<NiceWeaponDamageType> damageType,
Vector hitLocation,
Vector momentum){
lastTookDamageTime = Level.TimeSeconds;
lastDamageAmount = damage;
lastDamagedBy = instigatedBy;
lastDamagedByType = damageType;
hitMomentum = VSize(momentum);
lasthitLocation = hitLocation;
lastMomentum = momentum;
}
// Breaks damage into different elemental components and
// applies damage mods to them
function ExtractElementalDamage(out int regDamage,
out int heatDamage,
int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
KFPlayerReplicationInfo KFPRI,
float headshotLevel,
float lockonTime){
ModDamage( damage, instigatedBy, hitLocation, momentum, damageType,
headshotLevel, KFPRI, lockonTime);
// Divide damage into different components (so far only regular and fire)
if(damageType != none){
RegDamage = damage * (1 - damageType.default.heatPart);
HeatDamage = damage * damageType.default.heatPart;
}
else{
RegDamage = damage;
HeatDamage = 0.0;
}
// Mod different component's damages
ModRegularDamage( RegDamage, instigatedBy, hitLocation, momentum,
damageType, headshotLevel, KFPRI, lockonTime);
ModFireDamage( HeatDamage, instigatedBy, hitLocation, momentum,
damageType, headshotLevel, KFPRI, lockonTime);
}
// Extracts damage to different body components and applies damage mods to them
function ExtractPartsDamage(out int bodyDamage,
out int headDamage,
out int painDamage,
int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
KFPlayerReplicationInfo KFPRI,
float headshotLevel,
float lockonTime){
bodyDamage = damage;
headDamage = 0.0;
// Mod head health on head-shots only
if(headshotLevel > 0.0 && HeadHealth > 0){
headDamage = bodyDamage;
ModHeadDamage( headDamage, instigatedBy, hitLocation, momentum,
damageType, headshotLevel, KFPRI, lockonTime);
}
// Make sure whole body damage is always at least the
// highest single component damage
bodyDamage = Max(bodyDamage, headDamage);
// Always mod body health
painDamage = ModBodyDamage( bodyDamage, instigatedBy, hitLocation, momentum,
damageType, headshotLevel, KFPRI, lockonTime);
// Limit pain damage by a head damage
painDamage = Max(painDamage, headDamage);
}
function AddKillAssistant(Pawn assistant, float damage){
local KFMonsterController myController;
myController = KFMonsterController(Controller);
if(assistant == none || myController == none) return;
if(!assistant.IsPlayerPawn()) return;
KFMonsterController(Controller).
AddKillAssistant(assistant.controller, FMin(health, damage));
}
function DealPartsDamage( int bodyDamage,
int headDamage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
KFPlayerReplicationInfo KFPRI,
float headshotLevel,
float lockonTime){
if(headDamage > 0 && headshotLevel > 0.0 && !bDecapitated)
DealHeadDamage(headDamage, instigatedBy, hitLocation, momentum,
damageType, headshotLevel, KFPRI, lockonTime);
if(bodyDamage > 0)
DealBodyDamage(bodyDamage, instigatedBy, hitLocation, momentum,
damageType, headshotLevel, KFPRI, lockonTime);
}
simulated event SetAnimAction(name NewAction){
if(bFrozenZed)
return;
super.SetAnimAction(NewAction);
}
function Freeze(){
SetOverlayMaterial(FrozenMaterial, 999, true);
AnimAction = '';
bShotAnim = true;
bWaitForAnim = true;
StopMovement();
Disable('AnimEnd');
StopAnimating();
if(Controller != none){
Controller.FocalPoint = Location + 512*vector(Rotation);
Controller.Enemy = none;
Controller.Focus = none;
if(!Controller.IsInState('Freeze'))
Controller.GoToState('Freeze');
KFMonsterController(Controller).bUseFreezeHack = true;
}
bFrozenZed = true;
frozenRotation = rotation;
}
function UnFreeze(){
if(controller == none || Health <= 0) return;
SetOverlayMaterial(none, 0.1, true);
bShotAnim = false;
bWaitForAnim = false;
Enable('AnimEnd');
AnimEnd(0);
AnimEnd(1);
controller.GotoState('ZombieHunt');
GroundSpeed = GetOriginalGroundSpeed();
bFrozenZed = false;
}
function TakeDamageClient( int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
float headshotLevel,
float lockonTime){
local KFPlayerReplicationInfo KFPRI;
// Elemental damage components
local int regDamage;
local int heatDamage;
// Body part damage components
local int headDamage;
local int bodyDamage;
local int painDamage;
if(instigatedBy != none)
KFPRI = KFPlayerReplicationInfo(instigatedBy.PlayerReplicationInfo);
if(headHealth <= 0)
headshotLevel = 0.0;
// Handle special weapon effects
HandleStoppingPower(damageType, headshotLevel);
// Handle elemental damage components
ExtractElementalDamage(regDamage, heatDamage, damage,
instigatedBy, hitLocation, momentum,
damageType, KFPRI, headshotLevel, lockonTime);
FireDamageEffects(HeatDamage, instigatedBy, hitLocation,
momentum, damageType, headshotLevel, KFPRI);
FrostEffects( instigatedBy, hitLocation, momentum,
damageType, headshotLevel, KFPRI);
// Handle body parts damage components
ExtractPartsDamage( bodyDamage, headDamage, painDamage,
RegDamage + HeatDamage, instigatedBy,
hitLocation, momentum, damageType, KFPRI,
headshotLevel, lockonTime);
DoRightPainReaction( painDamage, instigatedBy, hitLocation, momentum,
damageType, headshotLevel, KFPRI);
DealPartsDamage( bodyDamage, headDamage,
instigatedBy, hitLocation, momentum, damageType,
KFPRI, headshotLevel, lockonTime);
AddKillAssistant(instigatedBy, bodyDamage);
// Rewrite values of last deal damage, instigator, etc.
UpdateLastDamageVars( instigatedBy, bodyDamage, damageType,
hitLocation, momentum);
// Reset flags: NICETODO: remove this fucking bullshit
// like why the fuck is it being done HERE? Makes no fucking sense
bBackstabbed = false;
}
function HandleStoppingPower(
class<NiceWeaponDamageType> damageType,
float headshotLevel
) {
local float strength;
local float actualMinStoppingThreshold, actualMaxStoppingEffect;
strength = damageType.default.stoppingPower;
if (headshotLevel > 0.0) {
strength *= 2;
}
if (concussionCountdown > 0) {
actualMinStoppingThreshold = FMax(0.0, minStoppingThreshold - 0.2);
actualMaxStoppingEffect = FMin(0.9, maxStoppingEffect + 0.2);
}
else {
actualMinStoppingThreshold = minStoppingThreshold;
actualMaxStoppingEffect = maxStoppingEffect;
}
strength = FMax(0, strength - actualMinStoppingThreshold);
stoppingEffect = FMin(actualMaxStoppingEffect, stoppingEffect + strength);
}
function TakeDamage(int damage,
Pawn instigatedBy,
Vector hitLocation,
Vector momentum,
class<damageType> damageType,
optional int HitIndex){
local bool isHeadDamage;
local bool isInstigatorMad;
local class<KFWeaponDamageType> kfDmgType;
local class<NiceWeaponDamageType> niceDmgType;
// Figure out what damage type to use
kfDmgType = class<KFWeaponDamageType>(damageType);
niceDmgType = class<NiceWeaponDamageType>(damageType);
if(niceDmgType == none){
if(kfDmgType != none && kfDmgType.default.bDealBurningDamage)
niceDmgType = class'NiceEnviromentalDamageFire';
else
niceDmgType = class'NiceEnviromentalDamage';
}
// Increase damage from mad zeds, 'cause they aren't kidding
if(NiceMonster(instigatedBy) != none)
isInstigatorMad = NiceMonster(instigatedBy).madnessCountDown > 0.0;
if(isInstigatorMad)
damage *= damageToMonsterScale;
if(class<DamTypeVomit>(damageType) != none)
BileDamageEffect(damage, instigatedBy, damageType);
if(kfDmgType != none){
if(!bDecapitated && kfDmgType.default.bCheckForHeadShots)
isHeadDamage = IsHeadShot(hitLocation, normal(momentum), 1.0);
}
if(isHeadDamage)
TakeDamageClient( damage, instigatedBy, hitLocation, momentum,
niceDmgType, 1.0, 0.0);
else
TakeDamageClient( damage, instigatedBy, hitLocation, momentum,
niceDmgType, 0.0, 0.0);
if(isInstigatorMad){
madnessCountDown =
FMax( madnessCountDown,
class'NiceSkillMedicZEDFrenzy'.default.madnessTime * 0.25);
if(KFMonsterController(Controller) != none)
KFMonsterController(Controller).FindNewEnemy();
}
}
function TakeFireDamage(int damage, Pawn instigator){
local Vector DummyHitLoc, DummyMomentum;
super(Skaarj).TakeDamage( damage, instigator, dummyHitLoc,
dummyMomentum, fireDamageClass);
lastBurnDamage = damage;
// Melt em' :)
if(FlameFuel <= 0)
ZombieCrispUp();
}
function TakeFrostDamage(int damage, Pawn instigator){
local Vector dummyHitLoc, dummyMomentum;
if(damage > health)
damage = health - 1;
if(damage > 0)
super(Skaarj).TakeDamage( damage, instigator, dummyHitLoc,
dummyMomentum, frostDamageClass);
lastFrostDamage = damage;
}
simulated function ZombieCrispUp(){
bAshen = true;
bCrispified = true;
if(Level.netMode == NM_DedicatedServer) return;
if(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';
}
simulated function HeatTick(){
local float iceDamage;
local float heatRelativeLevel, heatMultiplier;
heatRelativeLevel = FMin(heat, flameFuel) / initFlameFuel;
if (heatRelativeLevel < 0.05) {
heatMultiplier = 1.5;
} else {
heatMultiplier = 1.1;
}
// Update heat value
if(!bOnFire || flameFuel <= 0)
heat *= heatDissipationRate;
else {
if(flameFuel < heat) {
heat = flameFuel * heatMultiplier + (heat - flameFuel) * heatDissipationRate;
} else {
heat = heat * heatMultiplier;
}
}
flameFuel = FMax(0, flameFuel - heat);
CapHeat();
if(Abs(heat) < 1)
heat = 0.0;
// Update on-fire status
if(bOnFire){
if(heat > 0) {
TakeFireDamage(heat, burnInstigator);
}
else {
bBurnified = false;
UnSetBurningBehavior();
RemoveFlamingEffects();
StopBurnFX();
bOnFire = false;
}
}
// Update frozen status (always deal frost damage)
iceCrustStrenght = FMax(iceCrustStrenght, heat);
if(heat >= -10 && health > 1)
iceCrustStrenght -= 5 * (heat + 10);
iceCrustStrenght = FMax(0.0, iceCrustStrenght);
if(bFrozenZed){
iceDamage = -heat * 0.25;
if(iceDamage > 10)
TakeFrostDamage(iceDamage + rand(5), frostInstigator);
if(iceCrustStrenght <= 0){
UnFreeze();
heat = 0;
}
}
}
simulated function SetBurningBehavior(){
bBurningBehavior = true;
if(default.Health >= 1000) {
return;
}
MovementAnims[0] = BurningWalkFAnims[Rand(3)];
WalkAnims[0] = BurningWalkFAnims[Rand(3)];
MovementAnims[1] = BurningWalkAnims[0];
WalkAnims[1] = BurningWalkAnims[0];
MovementAnims[2] = BurningWalkAnims[1];
WalkAnims[2] = BurningWalkAnims[1];
MovementAnims[3] = BurningWalkAnims[2];
WalkAnims[3] = BurningWalkAnims[2];
}
simulated function UnSetBurningBehavior(){
local int i;
bBurningBehavior = false;
if(Role == Role_Authority){
Intelligence = default.Intelligence;
if(!bZapped){
AirSpeed = default.AirSpeed;
WaterSpeed = default.WaterSpeed;
}
}
if(bCrispified)
bAshen = True;
for(i = 0; i < 4; i++){
MovementAnims[i] = default.MovementAnims[i];
WalkAnims[i] = default.WalkAnims[i];
}
}
simulated function SetGroundSpeed(float newGroundSpeed) {
if (initializationComplete) {
UpdateGroundSpeed();
} else {
// Let `KFMonster`'s code setup original speed for this zed.
// This is a hack to be removed later, when we'll be untangling
// initialization code
super.SetGroundSpeed(newGroundSpeed);
}
}
simulated function UpdateGroundSpeed() {
if (TryNonRelevantSpeedup()) {
return;
}
groundSpeed = GetOriginalGroundSpeed();
groundSpeed *= (1.0 - stoppingEffect);
if (bDecapitated) {
groundSpeed *= 0.8;
}
if (bCrispified) {
groundSpeed *= 1.25;
}
}
simulated function ModDamageFromZed(
out int damage,
class<DamageType> damageType
) {
if (bCrispified) {
damage *= 2;
}
}
// If this function returns `true`, then we shouldn't touch speed further,
// because a speed hack was used for the zed
simulated function bool TryNonRelevantSpeedup(){
if(level.netMode == NM_Client || !CanSpeedAdjust()) {
return false;
}
if(level.timeSeconds - lastReplicateTime > 0.5) {
groundSpeed = default.groundSpeed * (300.0 / default.groundSpeed);
return true;
}
lastSeenOrRelevantTime = level.timeSeconds;
return false;
}
simulated function ServerDropFaster(NiceHumanPawn nicePawn){
if(nicePawn == none) return;
if(Health > 0)
BleedOutTime = Level.TimeSeconds
+ class'NiceSkillSharpshooterDieAlready'.default.bleedOutTime[nicePawn.calibrationScore - 1];
}
simulated function RemoveHead(){
local int i;
local class<KFWeaponDamageType> kfDmgType;
Intelligence = BRAINS_Retarded;
bDecapitated = true;
DECAP = true;
DecapTime = Level.TimeSeconds;
kfDmgType = class<KFWeaponDamageType>(lastDamagedByType);
if(kfDmgType != none && kfDmgType.default.bIsMeleeDamage)
bMeleeDecapitated = true;
SetAnimAction('HitF');
UpdateGroundSpeed();
// No more raspy breathin'...cuz he has no throat or mouth :S
AmbientSound = MiscSound;
if(Health > 0)
BleedOutTime = Level.TimeSeconds + BleedOutDuration;
if(MeleeAnims[1] == 'Claw3')
MeleeAnims[1] = 'Claw1';
if(MeleeAnims[2] == 'Claw3')
MeleeAnims[2] = 'Claw2';
// 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);
if(NiceMonsterController(Controller) != none)
NiceMonsterController(Controller).FindNewEnemy();
}
function bool FlipOverWithIntsigator(Pawn InstigatedBy){
return FlipOver();
}
// Calculates bone which bone we've hit
// as well as updates hit location and hit normal;
// extracted from TWI code without much changes
function CalculateHitBone( out Vector hitLocation,
out Vector hitNormal,
out name hitBone,
Pawn instigatedBy,
class<damageType> damageType){
local Vector hitRay;
local Vector instigatorEyes;
local float hitBoneDist;
// We have 'hitLocation' that designates where we approximately hit zed
// (it's collision cylinder).
// We want to know which bone instigator hit,
// so here we compute direction in wich bullet flew.
hitRay = vect(0,0,0);
if(instigatedBy != none){
instigatorEyes = instigatedBy.location
+ vect(0,0,1) * instigatedBy.EyeHeight;
hitRay = hitLocation - instigatorEyes;
hitRay = Normal(hitLocation);
}
// Now we have a vector that gives us bullet's
// trajectory direction ('hitRay')
// and a point it passes through ('hitRay'),
// so we can use magic funtion to find out which bone that bullet hit
if(damageType.default.bLocationalHit)
CalcHitLoc(hitLocation, hitRay, hitBone, hitBoneDist);
else{
// ...or just ignore all that and chose whatever
hitLocation = location;
hitBone = fireRootBone;
}
// Now get let's come up with some 'hitNormal'
if(instigatedBy != none){
hitNormal = Normal(instigatedBy.location - hitLocation);
hitNormal += vect(0, 0, 2.8);
hitNormal += VRand() * 0.2;
hitNormal = Normal(hitNormal);
}
else
hitNormal = Normal(Vect(0, 0, 1) + VRand() * 0.2 + vect(0, 0, 2.8));
}
function SplatterBlood( Pawn instigatedBy,
Vector hitLocation,
class<damageType> damageType,
Vector momentum){
local bool bNotRecentHit;
local bool bBloodDisabled;
local rotator splatRot;
// Is this hit recent?
// Even if recent - randomly count ome hits as non-recent
bNotRecentHit = Level.TimeSeconds - LastPainTime >= 0.2;
bNotRecentHit = bNotRecentHit || FRand() > 0.8;
// Is blood allowed?
bBloodDisabled = class'GameInfo'.static.NoBlood();
bBloodDisabled = bBloodDisabled || class'GameInfo'.static.UseLowGore();
// Generate some blood
if(damageType.default.bCausesBlood && !bBloodDisabled && bNotRecentHit){
// Get correct-looking rotatin for our blood splat,
// if possible for momentum
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));
}
Spawn(ProjectileBloodSplatClass, instigatedBy,, hitLocation, splatRot);
}
}
// Overloaded to suck less ass,
// for example containing only visual side of effects.
// Removed zapped effect, since zeds can't be zapped in NicePack.
simulated function PlayHit( float damage,
Pawn instigatedBy,
Vector hitLocation,
class<damageType> damageType,
Vector momentum,
optional int HitIdx){
local Vector hitNormal;
local name hitBone;
// Call the modified version of the original Pawn playhit
OldPlayHit(damage, instigatedBy, hitLocation, damageType, momentum);
if(damage <= 0) return;
CalculateHitBone(hitLocation, hitNormal, hitBone, instigatedBy, damageType);
SplatterBlood(instigatedBy, hitLocation, damageType, momentum);
DoDamageFX(hitBone, damage, damageType, Rotator(hitNormal));
if(damageType.default.DamageOverlayMaterial != none && damage > 0)
SetOverlayMaterial( damageType.default.damageOverlayMaterial,
damageType.default.damageOverlayTime, false);
}
// I've gotta come clean - I'm not sure what this one does exactly
function SpawnPVolumeExitActor(){
local bool bPVCanExitActor;
if(PhysicsVolume != none){
bPVCanExitActor = PhysicsVolume.bDestructive;
bPVCanExitActor = bPVCanExitActor && PhysicsVolume.bDestructive;
bPVCanExitActor = bPVCanExitActor && PhysicsVolume.ExitActor != none;
}
if(health <= 0 && bPVCanExitActor)
Spawn(PhysicsVolume.ExitActor);
}
function OldSpawnEffect(Vector hitLocation,
Vector hitNormal,
Vector momentum,
class<Effects> effectClass){
local Vector bloodOffset;
if(effectClass == none) return;
bloodOffset = 0.2 * collisionRadius * hitNormal;
bloodOffset.Z = 0.5 * bloodOffset.Z;
if(momentum.Z > 0)
momentum.Z *= 0.5;
Spawn(effectClass, self,, hitLocation + bloodOffset, rotator(momentum));
}
function OldSpawnEmitter( Vector hitLocation,
Vector hitNormal,
Pawn instigatedBy,
class<Emitter> emitterClass){
local Vector emitterOffset;
local Vector instigatorEyes;
if(emitterClass == none) return;
emitterOffset = hitNormal - hitNormal * CollisionRadius;
instigatorEyes = instigatedBy.location
+ vect(0,0,1) * instigatedBy.EyeHeight;
if(instigatedBy != none)
hitNormal = Normal(instigatorEyes - hitLocation);
Spawn(emitterClass,,, hitLocation + emitterOffset, Rotator(hitNormal));
}
// Now only visual part of effects
function OldPlayHit(float damage,
Pawn instigatedBy,
Vector hitLocation,
class<damageType> damageType,
Vector momentum,
optional int HitIndex){
local bool bLowDetails;
local bool bShouldPlayEffect;
local Vector hitNormal;
local class<Effects> desiredEffect;
local class<Emitter> desiredEmitter;
if(damageType == none || damage <= 0) return;
SpawnPVolumeExitActor();
// Comment in 'DamageType' says that 'DamageThreshold' is
// how much damage much occur before playing effects.
bShouldPlayEffect = damage > damageType.default.damageThreshold;
bShouldPlayEffect = bShouldPlayEffect && EffectIsRelevant(location, true);
if(!bShouldPlayEffect) return;
hitNormal = Normal(hitLocation - Location);
bLowDetails = Level.bDropDetail || Level.detailMode == DM_Low;
desiredEffect = damageType.static.GetPawnDamageEffect( hitLocation, damage,
momentum,
self, bLowDetails);
desiredEmitter = damageType.Static.GetPawnDamageEmitter( hitLocation,
damage,
momentum, self,
bLowDetails);
OldSpawnEffect(hitLocation, hitNormal, momentum, desiredEffect);
OldSpawnEmitter(hitLocation, hitNormal, instigatedBy, desiredEmitter);
}
simulated function PlayTakeHit( Vector hitLocation,
int damage,
class<damageType> damageType){}
function float GetIgnitionPoint(){
return 10;
}
function float GetFreezingPoint(){
return 100.0;
}
function float GetIceCrustScale(){
return 25000 / (default.health * default.health);
}
function float HeatIncScale(){
return 100.0 / default.health;
}
function CapHeat(){
heat = FMin(heat, MAX_HEAT);
heat = FMax(heat, MIN_HEAT);
}
function bool TryMeleeReachTarget(out Vector hitLocation){
local Actor hitActor;
local Vector hitNormal;
// See if a trace would hit a pawn
// (have to turn off hit point collision so trace doesn't hit the
// HumanPawn's bullet whiz cylinder)
bBlockHitPointTraces = false;
hitActor = Trace( hitLocation, hitNormal, controller.target.location,
location + EyePosition(), true);
bBlockHitPointTraces = true;
if(Pawn(hitActor) != none) return true;
// If the trace wouldn't hit a pawn, do the old thing of just checking if
// there is something blocking the trace
bBlockHitPointTraces = false;
hitActor = Trace( hitLocation, hitNormal, controller.target.location,
location, false);
bBlockHitPointTraces = true;
return (hitActor == none); // Nothing in the way means no problems
}
function MeleeGoreDeadPlayer( KFHumanPawn kfHumanPawn,
Vector hitLocation,
Vector pushDir){
local float dummy;
local name tearBone;
if(kfHumanPawn == none || class'GameInfo'.static.UseLowGore()) return;
Spawn( class'KFMod.FeedingSpray', self,,
kfHumanPawn.location, rotator(pushDir));
kfHumanPawn.SpawnGibs(rotator(pushDir), 1);
tearBone = kfHumanPawn.GetClosestBone(hitLocation, velocity, dummy);
kfHumanPawn.HideBone(tearBone);
}
function bool MeleeDamageTarget(int hitDamage, Vector pushDir){
local bool bTargetIsDoor;
local bool bInMeleeRange, bCanMeleeReach;
local float meleeDistance, distanceFromTarget;
local Vector hitLocation;
local KFHumanPawn kfHumanPawn;
if(Level.netMode == NM_Client) return false;
if(controller == none || controller.target == none) return false;
ModDamageFromZed(hitDamage, niceZombieDamType);
// Melee for doors
kfHumanPawn = KFHumanPawn(controller.target);
bTargetIsDoor = controller.target.IsA('KFDoorMover');
if(bTargetIsDoor){
controller.target.TakeDamage( hitDamage, self, hitLocation,
pushDir, niceZombieDamType);
return true;
}
// Check if still in melee range
meleeDistance = meleeRange * 1.4;
meleeDistance += controller.target.collisionRadius + collisionRadius;
distanceFromTarget = VSize(controller.target.location - location);
bInMeleeRange = distanceFromTarget <= meleeDistance;
bCanMeleeReach = TryMeleeReachTarget(hitLocation);
if(!bInMeleeRange || !bCanMeleeReach || bSTUNNED) return false;
// Melee for non-human actors
if(kfHumanPawn == none){
controller.target.TakeDamage( hitDamage, self, hitLocation,
pushDir, niceZombieDamType);
return true;
}
// Melee for human pawns
kfHumanPawn.TakeDamage( hitDamage, instigator,
hitLocation, pushDir, niceZombieDamType);
if(kfHumanPawn != none && kfHumanPawn.Health <= 0){
MeleeGoreDeadPlayer(kfHumanPawn, hitLocation, pushDir);
// Give us some health back
if(health <= (1.0 - feedThreshold) * healthMax)
health += feedThreshold * healthMax * health / healthMax;
}
return true;
}
state ZombieDying
{
ignores AnimEnd, Trigger, Bump, HitWall, HeadVolumeChange,
PhysicsVolumeChange, Falling, BreathTimer, Died, RangedAttack;
// #1 disable zed collisions on death
simulated function BeginState()
{
super.BeginState();
DisableCollisions();
}
simulated function Landed(vector HitNormal){
SetCollision(false, false, false);
if(!bDestroyNextTick)
Disable('Tick');
}
simulated function TakeDamageClient(int damage,
Pawn InstigatedBy,
Vector hitLocation,
Vector momentum,
class<NiceWeaponDamageType> damageType,
optional float headshotLevel,
optional float lockonTime){
local Vector shotDir;
local Vector pushLinVel, pushAngVel;
if(bFrozenBody || bRubbery || damage <= 0) return;
if(headshotLevel > 0.0)
RemoveHead();
PlayHit(damage, InstigatedBy, hitLocation, damageType, momentum);
// Can't shoot corpses during de-res
if(Physics != PHYS_KarmaRagdoll || bDeRes) return;
// Throw the body if its a rocket explosion or shock combo
if(momentum == vect(0,0,0))
momentum = hitLocation - instigatedBy.Location;
shotDir = Normal(momentum);
if(damageType.default.bThrowRagdoll){
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(FRand() < 0.65){
if(velocity.Z <= 0)
pushLinVel = vect(0,0,40);
pushAngVel = Normal(shotDir Cross vect(0, 0, 1)) * (-8000);
pushAngVel.X *= 0.5;
pushAngVel.Y *= 0.5;
pushAngVel.Z *= 4;
KSetSkelVel(pushLinVel, pushAngVel);
}
pushLinVel = RagShootStrength * shotDir;
KAddImpulse(pushLinVel, hitLocation);
if((LifeSpan > 0) && (LifeSpan < DeResTime + 2))
LifeSpan += 0.2;
}
else{
pushLinVel = RagShootStrength * shotDir;
KAddImpulse(pushLinVel, hitLocation);
}
}
}
// #2 disable zed collisions on death
simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
super.PlayDying(DamageType, HitLoc);
DisableCollisions();
}
// disables all collisions
simulated function DisableCollisions()
{
bBlockActors = false;
bBlockPlayers = false;
bBlockProjectiles = false;
bProjTarget = false;
bBlockZeroExtentTraces = false;
bBlockNonZeroExtentTraces = false;
bBlockHitPointTraces = false;
}
// Setters for extra collision cylinders
simulated function ToggleAuxCollision(bool newbCollision)
{
if (MyExtCollision == none)
{
log(">> NiceMonster -> ToggleAuxCollision(" $ newbCollision $ ") -> MyExtCollision was none!!!");
return;
}
if (!newbCollision)
{
SavedExtCollision = MyExtCollision.bCollideActors;
MyExtCollision.SetCollision(false);
}
else
{
MyExtCollision.SetCollision(SavedExtCollision);
}
}
defaultproperties
{
stunDurationMultiplier=0.5
StunThreshold=0.666000
remainingStuns=-1
lastStunTime=-1.000000
headDamageRecoveryRate=100.000000
headRecoveryTime=1.000000
bCanBurn=True
fuelRatio=0.750000
heatDissipationRate=0.666000
heatTicksPerSecond=3.000000
clientHeadshotScale=1.000000
FrozenMaterial=Texture'HTec_A.Overlay.IceOverlay'
ShatteredIce=class'NiceIceChunkEmitter'
niceZombieDamType=class'NiceZedMeleeDamageType'
ZappedSpeedMod=0.300000
DamageToMonsterScale=5.000000
RagdollLifeSpan=120.000000
ControllerClass=class'NiceMonsterController'
stoppingEffect=0.0
stoppingRecoveryRate=0.025
maxStoppingEffect=0.25
minStoppingThreshold=0.0
MIN_HEAT = -150.0
MAX_HEAT = 200.0
Begin Object Class=KarmaParamsSkel Name=KarmaParamsSkelN
KConvulseSpacing=(Max=2.200000)
KLinearDamping=0.150000
KAngularDamping=0.050000
KBuoyancy=1.000000
KStartEnabled=True
KVelDropBelowThreshold=50.000000
bHighDetailOnly=False
KFriction=1.300000
KRestitution=0.200000
KImpactThreshold=85.000000
End Object
KParams=KarmaParamsSkelN
}