743 lines
26 KiB
Ucode
743 lines
26 KiB
Ucode
class NiceZombieTeslaHusk extends NiceZombieHusk;
|
|
#exec OBJ LOAD FILE=KF_EnemiesFinalSnd_CIRCUS.uax
|
|
#exec OBJ LOAD FILE=ScrnZedPack_A.ukx
|
|
#exec OBJ LOAD FILE=ScrnZedPack_T.utx
|
|
#exec OBJ LOAD FILE=ScrnZedPack_SM.usx
|
|
|
|
var class<TeslaBeam> BeamClass;
|
|
var TeslaBeam PrimaryBeam;
|
|
var float MaxPrimaryBeamRange; // how far Husk can shoot with his tesla gun?
|
|
var float MaxChildBeamRange; // max distance from primary target for a chain reaction
|
|
var float ChainedBeamRangeExtension; // if pawn is already chained, chain max range is multiplied by this value
|
|
// used for optimization
|
|
var private float MaxPrimaryBeamRangeSquared, MaxChildBeamRangeSquared;
|
|
var float Energy;
|
|
var float EnergyMax;
|
|
var float EnergyRestoreRate;
|
|
var() float DischargeDamage; // beam damage per second
|
|
var() float ChainDamageMult; // how much damage drops on the next chain
|
|
var() class<DamageType> MyDamageType;
|
|
var() byte MaxChainLevel;
|
|
var() int MaxChainActors;
|
|
var ZedBeamSparks DecapitationSparks;
|
|
var bool bChainThoughZED; // chain tesla beam though a zed, which is between target and the husk, to extend firing range
|
|
var transient float NextChainZedSearchTime;
|
|
var transient float ChainRebuildTimer; // how many seconds between rebuilding chained actors
|
|
var float DischargeRate; // time between damaging chained pawns. Do not set it too low due to rounding precission of DischargeDamage
|
|
var transient float LastDischargeTime;
|
|
// Tesla Husks are able to heal each other.
|
|
var() float HealRate; // Percent of HealthMax to heal per second
|
|
var() float HealEnergyDrain; // amount of energy required to heal 1%
|
|
var transient float NextHealAttemptTime, LastHealTime;
|
|
|
|
struct SActorDamages
|
|
{
|
|
var Actor Victim;
|
|
var int Damage;
|
|
};
|
|
var protected array<SActorDamages> DischargedActors;
|
|
simulated function PostBeginPlay()
|
|
{
|
|
// Difficulty Scaling
|
|
if(Level.Game != none && !bDiffAdjusted)
|
|
{
|
|
if(Level.Game.GameDifficulty >= 5.0){
|
|
DischargeDamage *= 1.75;
|
|
EnergyMax *= 1.75;
|
|
EnergyRestoreRate *= 1.75;
|
|
MaxPrimaryBeamRange *= 1.75;
|
|
MaxChildBeamRange *= 1.75;
|
|
}
|
|
else{
|
|
DischargeDamage *= 1.0;
|
|
EnergyMax *= 1.0;
|
|
EnergyRestoreRate *= 1.0;
|
|
MaxPrimaryBeamRange *= 1.0;
|
|
MaxChildBeamRange *= 1.0;
|
|
}
|
|
}
|
|
Energy = EnergyMax;
|
|
MaxPrimaryBeamRangeSquared = MaxPrimaryBeamRange * MaxPrimaryBeamRange;
|
|
MaxChildBeamRangeSquared = MaxChildBeamRange * MaxChildBeamRange;
|
|
NextHealAttemptTime = Level.TimeSeconds + 5.0;
|
|
super.PostBeginPlay();
|
|
BurnDamageScale = 1.0; // no fire damage resistance
|
|
}
|
|
simulated function HideBone(name boneName)
|
|
{
|
|
local int BoneScaleSlot;
|
|
local bool bValidBoneToHide;
|
|
if( boneName == LeftThighBone )
|
|
{
|
|
boneScaleSlot = 0;
|
|
bValidBoneToHide = true;
|
|
}
|
|
else if ( boneName == RightThighBone )
|
|
{
|
|
boneScaleSlot = 1;
|
|
bValidBoneToHide = true;
|
|
}
|
|
else if( boneName == RightFArmBone )
|
|
{
|
|
boneScaleSlot = 2;
|
|
bValidBoneToHide = true;
|
|
}
|
|
else if ( boneName == LeftFArmBone )
|
|
{
|
|
boneScaleSlot = 3;
|
|
bValidBoneToHide = true;
|
|
}
|
|
else if ( boneName == HeadBone )
|
|
{
|
|
// Only scale the bone down once
|
|
if( SeveredHead == none )
|
|
{
|
|
bValidBoneToHide = true;
|
|
boneScaleSlot = 4;
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
function TeslaBeam SpawnTeslaBeam()
|
|
{
|
|
local TeslaBeam beam;
|
|
beam = spawn(BeamClass, self);
|
|
if ( beam != none ) {
|
|
beam.Instigator = self;
|
|
}
|
|
return beam;
|
|
}
|
|
|
|
function TeslaBeam SpawnPrimaryBeam()
|
|
{
|
|
if ( PrimaryBeam == none ) {
|
|
PrimaryBeam = SpawnTeslaBeam();
|
|
PrimaryBeam.StartActor = self;
|
|
PrimaryBeam.StartBoneName = 'Barrel';
|
|
}
|
|
return PrimaryBeam;
|
|
}
|
|
function FreePrimaryBeam()
|
|
{
|
|
if ( PrimaryBeam != none ) {
|
|
//PrimaryBeam.DestroyChildBeams();
|
|
PrimaryBeam.EndActor = none;
|
|
PrimaryBeam.EffectEndTime = Level.TimeSeconds - 1;
|
|
PrimaryBeam.Instigator = none; // mark to delete
|
|
PrimaryBeam = none;
|
|
}
|
|
}
|
|
function RangedAttack(Actor A)
|
|
{
|
|
local float Dist;
|
|
local NiceMonster M;
|
|
if ( bShotAnim || bDecapitated )
|
|
return;
|
|
Dist = VSize(A.Location - Location);
|
|
if ( Physics == PHYS_Swimming )
|
|
{
|
|
SetAnimAction('Claw');
|
|
bShotAnim = true;
|
|
}
|
|
else if ( Energy < 100 && Dist < MeleeRange + CollisionRadius + A.CollisionRadius ) // do melee hits only when low on energy
|
|
{
|
|
bShotAnim = true;
|
|
SetAnimAction('Claw');
|
|
//PlaySound(sound'Claw2s', SLOT_Interact); KFTODO: Replace this
|
|
Controller.bPreparingMove = true;
|
|
Acceleration = vect(0,0,0);
|
|
}
|
|
else if ( Energy > 50 && Dist < MaxPrimaryBeamRange*(0.7 + 0.3*frand()) )
|
|
{
|
|
bShotAnim = true;
|
|
|
|
SetAnimAction('ShootBurns');
|
|
Controller.bPreparingMove = true;
|
|
Acceleration = vect(0,0,0);
|
|
|
|
NextFireProjectileTime = Level.TimeSeconds + ProjectileFireInterval + (FRand() * 2.0);
|
|
}
|
|
else if ( bChainThoughZED && Energy > 100 && Controller.Enemy == A && KFPawn(A) != none
|
|
&& NextChainZedSearchTime < Level.TimeSeconds
|
|
&& Dist < MaxPrimaryBeamRange + MaxChildBeamRange )
|
|
{
|
|
NextChainZedSearchTime = Level.TimeSeconds + 3.0;
|
|
foreach VisibleCollidingActors(class'NiceMonster', M, MaxPrimaryBeamRange*0.9) {
|
|
if ( !M.bDeleteMe && M.Health > 0 && NiceZombieTeslaHusk(M) == none
|
|
&& VSizeSquared(M.Location - A.Location) < MaxChildBeamRangeSquared*0.9
|
|
&& M.FastTrace(A.Location, M.Location) )
|
|
{
|
|
Controller.Target = M;
|
|
RangedAttack(M);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function SpawnTwoShots()
|
|
{
|
|
SpawnPrimaryBeam();
|
|
if ( PrimaryBeam == none ) {
|
|
log("Unable to spawn Primary Beam!", 'TeslaHusk');
|
|
return;
|
|
}
|
|
PrimaryBeam.EndActor = Controller.Target;
|
|
if( Controller!=none && KFDoorMover(Controller.Target)!=none )
|
|
{
|
|
PrimaryBeam.EffectEndTime = Level.TimeSeconds + 1.0;
|
|
Controller.Target.TakeDamage(22,Self,Location,vect(0,0,0), MyDamageType);
|
|
return;
|
|
}
|
|
PrimaryBeam.EffectEndTime = Level.TimeSeconds + 2.5;
|
|
GotoState('Shooting');
|
|
}
|
|
simulated function Tick( float Delta )
|
|
{
|
|
if ( Role < ROLE_Authority ) {
|
|
if ( bDecapitated && Health > 0 && DecapitationSparks == none ) {
|
|
SpawnDecapitationEffects();
|
|
}
|
|
}
|
|
Super(NiceMonster).Tick(Delta);
|
|
if ( Role == ROLE_Authority ) {
|
|
if ( Energy < EnergyMax )
|
|
Energy += EnergyRestoreRate * Delta;
|
|
if ( Energy > 150 && NextHealAttemptTime < Level.TimeSeconds ) {
|
|
NextHealAttemptTime = Level.TimeSeconds + 3.0;
|
|
TryHealing();
|
|
}
|
|
}
|
|
}
|
|
function TryHealing()
|
|
{
|
|
local Controller C;
|
|
local NiceMonsterController MyMC;
|
|
local NiceZombieTeslaHusk BestPatient; local bool bAnyHusks;
|
|
MyMC = NiceMonsterController(Controller);
|
|
if ( MyMC == none )
|
|
return; // just in case
|
|
if ( MyMC.Enemy != none && VSizeSquared(MyMC.Enemy.Location - Location) < MaxPrimaryBeamRangeSquared )
|
|
return; // no healing when need to fight
|
|
for ( C=Level.ControllerList; C!=none; C=C.NextController ) {
|
|
if ( NiceZombieTeslaHusk(C.Pawn) != none && C.Pawn != self && C.Pawn.Health > 0 ) {
|
|
bAnyHusks = true;
|
|
if ( (BestPatient == none || BestPatient.Health > C.Pawn.Health)
|
|
&& VSizeSquared(C.Pawn.Location - Location) < MaxPrimaryBeamRangeSquared )
|
|
BestPatient = NiceZombieTeslaHusk(C.Pawn);
|
|
}
|
|
}
|
|
if ( !bAnyHusks )
|
|
NextHealAttemptTime = Level.TimeSeconds + 10.0; // no other husks on the map - so no need to do searching so often
|
|
else if ( BestPatient != none ) {
|
|
if ( BestPatient.Health < BestPatient.HealthMax * 0.90 ) {
|
|
MyMC.Target = BestPatient;
|
|
MyMC.ChangeEnemy(BestPatient, MyMC.CanSee(BestPatient));
|
|
MyMC.GotoState('RangedAttack');
|
|
LastHealTime = Level.TimeSeconds + 3.0; // do not heal until beam is spawned
|
|
GotoState('Healing'); }
|
|
else
|
|
NextHealAttemptTime = Level.TimeSeconds + 1.0; // if there are other Tesla Husks nearby, then check their health each second
|
|
}
|
|
else
|
|
NextHealAttemptTime = Level.TimeSeconds + 3.0; // there are other Tesla Husks on the map, but not too close
|
|
}
|
|
function Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
|
|
{
|
|
if ( PrimaryBeam != none )
|
|
PrimaryBeam.Instigator = none;
|
|
if ( DecapitationSparks != none )
|
|
DecapitationSparks.Destroy();
|
|
AmbientSound = none;
|
|
super.Died(Killer, damageType, HitLocation);
|
|
}
|
|
simulated event PlayDying(class<DamageType> DamageType, vector HitLoc)
|
|
{
|
|
super(NiceMonster).PlayDying(DamageType, HitLoc);
|
|
if ( DecapitationSparks != none )
|
|
DecapitationSparks.Destroy(); AmbientSound = none;
|
|
}
|
|
// instead of roaming around, activate self-destruct
|
|
function RemoveHead()
|
|
{
|
|
Intelligence = BRAINS_Retarded; // Headless dumbasses!
|
|
bDecapitated = true;
|
|
DECAP = true;
|
|
DecapTime = Level.TimeSeconds;
|
|
bShotAnim = true;
|
|
SetAnimAction('KnockDown');
|
|
Acceleration = vect(0, 0, 0);
|
|
Velocity.X = 0;
|
|
Velocity.Y = 0;
|
|
Controller.GoToState('WaitForAnim');
|
|
NiceMonsterController(Controller).bUseFreezeHack = True;
|
|
SetGroundSpeed(0);
|
|
AirSpeed = 0;
|
|
WaterSpeed = 0;
|
|
// No more raspy breathin'...cuz he has no throat or mouth :S
|
|
AmbientSound = MiscSound;
|
|
// super.TakeDamage(LastDamageAmount) isn't called yet, so set self-destruct sequence only if
|
|
// zed can survive the hit
|
|
if( Health > LastDamageAmount && Energy > 50 ) {
|
|
SpawnDecapitationEffects();
|
|
BleedOutTime = Level.TimeSeconds + BleedOutDuration;
|
|
GotoState('SelfDestruct');
|
|
}
|
|
|
|
PlaySound(DecapitationSound, SLOT_Misc,1.30,true,525);
|
|
}
|
|
simulated function Destroyed()
|
|
{
|
|
if ( PrimaryBeam != none )
|
|
PrimaryBeam.Destroy();
|
|
if ( DecapitationSparks != none )
|
|
DecapitationSparks.Destroy();
|
|
super.Destroyed();
|
|
}
|
|
simulated function SpawnDecapitationEffects()
|
|
{
|
|
if (Level.NetMode == NM_DedicatedServer)
|
|
return;
|
|
DecapitationSparks = spawn(class'ZedBeamSparks', self,, GetBoneCoords(HeadBone).Origin, Rotation);
|
|
}
|
|
simulated function int DoAnimAction( name AnimName )
|
|
{
|
|
// faked animation with forces to play the end of shoot animation (where Huks is putting his gun down)
|
|
if ( AnimName == 'ShootBurnsEnd' ) {
|
|
PlayAnim('ShootBurns');
|
|
SetAnimFrame(120, 0, 1);
|
|
return 0;
|
|
}
|
|
return super.DoAnimAction(AnimName);
|
|
}
|
|
function float RangedAttackTime()
|
|
{
|
|
return 5.0;
|
|
}
|
|
// 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) -5.0*C.XAxis - 2.0*C.YAxis;
|
|
}
|
|
//ServerHeadLocation = HeadLoc;
|
|
// Express snipe trace line in terms of B + tM
|
|
B = loc;
|
|
M = ray * (2.0 * CollisionHeight + 2.0 * CollisionRadius);
|
|
// Find Point-Line Squared Distance
|
|
diff = HeadLoc - B;
|
|
t = M Dot diff;
|
|
if (t > 0)
|
|
{
|
|
DotMM = M dot M;
|
|
if (t < DotMM)
|
|
{
|
|
t = t / DotMM;
|
|
diff = diff - (t * M);
|
|
}
|
|
else
|
|
{
|
|
t = 1;
|
|
diff -= M;
|
|
}
|
|
}
|
|
else
|
|
t = 0;
|
|
Distance = Sqrt(diff Dot diff);
|
|
return (Distance < (HeadRadius * HeadScale * AdditionalScale));
|
|
}
|
|
state Healing
|
|
{
|
|
ignores TryHealing;
|
|
function BeginState()
|
|
{
|
|
//log("Entering Healing State @ " $ Level.TimeSeconds, 'TeslaHusk');
|
|
LastHealTime = Level.TimeSeconds + 3.0; // do not heal until beam is spawned
|
|
}
|
|
function EndState()
|
|
{
|
|
local name SeqName;
|
|
local float AnimFrame, AnimRate;
|
|
|
|
//log("Exiting Healing State @ " $ Level.TimeSeconds, 'TeslaHusk');
|
|
NextHealAttemptTime = Level.TimeSeconds + 5.0;
|
|
FreePrimaryBeam();
|
|
|
|
// end shooting animation
|
|
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
|
|
if ( SeqName == 'ShootBurns' && AnimFrame < 120 )
|
|
SetAnimAction('ShootBurnsEnd'); // faked anim
|
|
Controller.Enemy = none;
|
|
NiceMonsterController(Controller).FindNewEnemy();
|
|
}
|
|
function SpawnTwoShots()
|
|
{
|
|
SpawnPrimaryBeam();
|
|
if ( Controller != none )
|
|
PrimaryBeam.EndActor = Controller.Target;
|
|
PrimaryBeam.EffectEndTime = Level.TimeSeconds + 2.5;
|
|
LastHealTime = Level.TimeSeconds; // start healing
|
|
}
|
|
function bool FlipOver()
|
|
{
|
|
if ( global.FlipOver() ) {
|
|
GotoState('');
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function DoStun(optional Pawn instigatedBy, optional Vector hitLocation, optional Vector momentum, optional class<NiceWeaponDamageType> damageType, optional float headshotLevel, optional KFPlayerReplicationInfo KFPRI){
|
|
super.DoStun(instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI);
|
|
GotoState('');
|
|
}
|
|
function Tick(float DeltaTime)
|
|
{
|
|
local NiceZombieTeslaHusk Patient;
|
|
local int HealthToAdd;
|
|
super.Tick(DeltaTime);
|
|
if ( Level.TimeSeconds >= LastHealTime+0.25 ) {
|
|
if ( PrimaryBeam == none || Energy <= 0 || Level.TimeSeconds > PrimaryBeam.EffectEndTime ) {
|
|
GotoState('');
|
|
return;
|
|
}
|
|
|
|
NextHealAttemptTime = Level.TimeSeconds + 0.25;
|
|
Patient = NiceZombieTeslaHusk(PrimaryBeam.EndActor);
|
|
if ( Patient == none || Patient.Health <= 0 || Patient.Health >= Patient.HealthMax )
|
|
GotoState('');
|
|
else {
|
|
HealthToAdd = min(Patient.HealthMax*HealRate*(Level.TimeSeconds-LastHealTime), Patient.HealthMax - Patient.Health);
|
|
if ( HealthToAdd*HealEnergyDrain > Energy )
|
|
HealthToAdd = Energy / HealEnergyDrain;
|
|
Patient.Health += HealthToAdd;
|
|
Energy -= HealthToAdd*HealEnergyDrain;
|
|
LastHealTime = Level.TimeSeconds;
|
|
}
|
|
}
|
|
}}
|
|
|
|
// todo: make custom controller, moving states there
|
|
state Shooting
|
|
{
|
|
ignores TryHealing;
|
|
function BeginState()
|
|
{
|
|
//log("Entering Shooting State @ " $ Level.TimeSeconds, 'TeslaHusk');
|
|
if ( PrimaryBeam == none ) {
|
|
SpawnPrimaryBeam();
|
|
PrimaryBeam.EndActor = Controller.Target;
|
|
}
|
|
|
|
PrimaryBeam.EffectEndTime = Level.TimeSeconds + 2.5;
|
|
BuildChain();
|
|
LastDischargeTime = Level.TimeSeconds;
|
|
Discharge(DischargeRate);
|
|
}
|
|
function EndState()
|
|
{
|
|
local name SeqName;
|
|
local float AnimFrame, AnimRate;
|
|
//log("Exiting Shooting State @ " $ Level.TimeSeconds, 'TeslaHusk');
|
|
|
|
FreePrimaryBeam();
|
|
// end shooting animation
|
|
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
|
|
if ( SeqName == 'ShootBurns' && AnimFrame < 120 )
|
|
SetAnimAction('ShootBurnsEnd'); // faked anim
|
|
}
|
|
function BuildChain()
|
|
{
|
|
local int ChainsRemaining;
|
|
ChainRebuildTimer = default.ChainRebuildTimer;
|
|
ChainsRemaining = MaxChainActors;
|
|
SpawnChildBeams(PrimaryBeam, 1, ChainsRemaining); }
|
|
function SpawnChildBeams(TeslaBeam MasterBeam, byte level, out int ChainsRemaining)
|
|
{
|
|
local Pawn P;
|
|
local TeslaBeam ChildBeam;
|
|
local int i;
|
|
if ( MasterBeam == none || level > MaxChainLevel || ChainsRemaining <= 0 )
|
|
return;
|
|
for ( i=0; i<MasterBeam.ChildBeams.length; ++i ) {
|
|
if ( MasterBeam.ChildBeams[i] == none )
|
|
MasterBeam.ChildBeams.remove(i--, 1);
|
|
}
|
|
if ( MasterBeam.EndActor == none || MasterBeam.EndActor.bDeleteMe )
|
|
return;
|
|
foreach MasterBeam.EndActor.VisibleCollidingActors(class'Pawn', P, MaxChildBeamRange, MasterBeam.EndActor.Location) {
|
|
if ( !P.bDeleteMe && P != MasterBeam.StartActor && P != MasterBeam.EndActor
|
|
&& NiceZombieTeslaHusk(P) == none && !PrimaryBeam.IsChainedTo(P) )
|
|
{
|
|
i = MasterBeam.ChildIndex(P);
|
|
if ( i != -1 ) {
|
|
ChildBeam = MasterBeam.ChildBeams[i];
|
|
ChildBeam.EffectEndTime = PrimaryBeam.EffectEndTime;
|
|
}
|
|
else {
|
|
ChildBeam = SpawnTeslaBeam();
|
|
if ( ChildBeam != none ) {
|
|
ChildBeam.StartActor = MasterBeam.EndActor;
|
|
ChildBeam.EndActor = P;
|
|
ChildBeam.EffectEndTime = PrimaryBeam.EffectEndTime;
|
|
MasterBeam.ChildBeams[MasterBeam.ChildBeams.length] = ChildBeam;
|
|
}
|
|
}
|
|
if ( --ChainsRemaining <= 0 )
|
|
return;
|
|
}
|
|
}
|
|
for ( i=0; i<MasterBeam.ChildBeams.length && ChainsRemaining > 0; ++i ) {
|
|
SpawnChildBeams(MasterBeam.ChildBeams[i], level+1, ChainsRemaining);
|
|
}
|
|
}
|
|
function ChainDamage(TeslaBeam Beam, float Damage, byte ChainLevel)
|
|
{
|
|
local int i;
|
|
local float DmgMult;
|
|
if ( Beam == none || Beam.EndActor == none || Energy < 0 || Controller == none)
|
|
return;
|
|
Damage = clamp(Damage, 1, Energy);
|
|
// look if actor already received a damage during this discharge
|
|
for ( i=0; i<DischargedActors.length; ++i ) {
|
|
if ( DischargedActors[i].Victim == Beam.EndActor ) {
|
|
if ( DischargedActors[i].Damage >= Damage )
|
|
return; // Victim already received enough damage
|
|
else {
|
|
Damage -= DischargedActors[i].Damage;
|
|
DischargedActors[i].Damage += Damage;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( i == DischargedActors.length ) {
|
|
// new victim
|
|
DischargedActors.insert(i, 1);
|
|
DischargedActors[i].Victim = Beam.EndActor;
|
|
DischargedActors[i].Damage = Damage;
|
|
}
|
|
// deal higher damage to monsters, but do not drain energy quicker
|
|
DmgMult = 1.0;
|
|
if ( NiceMonster(Beam.EndActor) != none ) {
|
|
if ( ZombieFleshpound(Beam.EndActor) != none )
|
|
DmgMult = 20.0;
|
|
else
|
|
DmgMult = 3.0;
|
|
}
|
|
// if pawn is already chained, then damage him until he gets out of the primary range
|
|
Beam.SetBeamLocation(); // ensure that StartEffect and EndEffect are set
|
|
if ( VSizeSquared(Beam.EndEffect - Beam.StartEffect) <= MaxPrimaryBeamRangeSquared * ChainedBeamRangeExtension
|
|
&& FastTrace(Beam.EndEffect, Beam.StartEffect) )
|
|
{
|
|
Beam.EndActor.TakeDamage(Damage*DmgMult, self, Beam.EndActor.Location, Beam.SetBeamRotation(), MyDamageType);
|
|
if(Controller == none)
|
|
return;
|
|
Energy -= Damage;
|
|
Damage *= ChainDamageMult;
|
|
ChainLevel++;
|
|
if ( Damage > 0 && ChainLevel <= MaxChainLevel) {
|
|
for ( i=0; i<Beam.ChildBeams.length && Energy > 0; ++i )
|
|
ChainDamage(Beam.ChildBeams[i], Damage, ChainLevel);
|
|
}
|
|
}
|
|
else {
|
|
Beam.Instigator = none; // delete beam
|
|
}
|
|
}
|
|
function Discharge(float DeltaTime)
|
|
{
|
|
LastDischargeTime = Level.TimeSeconds;
|
|
DischargedActors.length = 0;
|
|
ChainDamage(PrimaryBeam, DischargeDamage*DeltaTime, 0);
|
|
if (Energy <= 0)
|
|
GotoState(''); // out of energy - stop shooting
|
|
}
|
|
function bool FlipOver()
|
|
{
|
|
if ( global.FlipOver() ) {
|
|
GotoState('');
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function DoStun(optional Pawn instigatedBy, optional Vector hitLocation, optional Vector momentum, optional class<NiceWeaponDamageType> damageType, optional float headshotLevel, optional KFPlayerReplicationInfo KFPRI){
|
|
super.DoStun(instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI);
|
|
GotoState('');
|
|
}
|
|
function Tick(float DeltaTime)
|
|
{
|
|
super.Tick(DeltaTime);
|
|
|
|
//log("Shooting.Tick @ " $ Level.TimeSeconds $ ":" @"Energy="$Energy @"PrimaryBeam="$PrimaryBeam, 'TeslaHusk');
|
|
ChainRebuildTimer -= DeltaTime;
|
|
if ( Energy <= 0 || PrimaryBeam == none || PrimaryBeam.EndActor == none || Level.TimeSeconds > PrimaryBeam.EffectEndTime ) {
|
|
GotoState('');
|
|
}
|
|
else if ( LastDischargeTime + DischargeRate <= Level.TimeSeconds ) {
|
|
if ( ChainRebuildTimer <= 0 )
|
|
BuildChain();
|
|
Discharge(Level.TimeSeconds - LastDischargeTime); // damage chained actors
|
|
}
|
|
}
|
|
}
|
|
state SelfDestruct
|
|
{
|
|
ignores PlayDirectionalHit, RangedAttack, MeleeDamageTarget;
|
|
function BeginState()
|
|
{
|
|
local NiceTeslaEMPNade MyNade;
|
|
MyNade = spawn(class'NiceTeslaEMPNade', self,, Location);
|
|
if ( MyNade != none ) {
|
|
AttachToBone(MyNade, RootBone);
|
|
MyNade.SetTimer(3.0, false);
|
|
MyNade.bTimerSet = true;
|
|
MyNade.Damage = max(20, Energy * 0.45);
|
|
}
|
|
else {
|
|
// wtf?
|
|
Died(LastDamagedBy.Controller,class'DamTypeBleedOut',Location);
|
|
}
|
|
}
|
|
function bool CheckMiniFlinch(int flinchScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class<NiceWeaponDamageType> damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){
|
|
return false;
|
|
}
|
|
function bool IsStunPossible(){
|
|
return false;
|
|
}
|
|
function bool CheckStun(int stunScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class<NiceWeaponDamageType> damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){
|
|
return false;
|
|
}
|
|
function bool CanGetOutOfWay(){
|
|
return false;
|
|
}
|
|
}
|
|
static simulated function PreCacheMaterials(LevelInfo myLevel)
|
|
{
|
|
myLevel.AddPrecacheMaterial(Shader'ScrnZedPack_T.TeslaHusk.TeslaHusk_SHADER');
|
|
myLevel.AddPrecacheMaterial(FinalBlend'KFZED_FX_T.Energy.ZED_FX_Beam_FB');
|
|
}
|
|
defaultproperties
|
|
{
|
|
BeamClass=Class'ScrnZedPack.TeslaBeam'
|
|
MaxPrimaryBeamRange=300.000000
|
|
MaxChildBeamRange=150.000000
|
|
ChainedBeamRangeExtension=1.200000
|
|
Energy=200.000000
|
|
EnergyMax=200.000000
|
|
EnergyRestoreRate=20.000000
|
|
DischargeDamage=20.000000
|
|
ChainDamageMult=0.700000
|
|
MyDamageType=Class'ScrnZedPack.DamTypeTesla'
|
|
MaxChainLevel=5
|
|
MaxChainActors=20
|
|
DischargeRate=0.250000
|
|
HealRate=0.250000
|
|
HealEnergyDrain=0.200000
|
|
ProjectileFireInterval=5.000000
|
|
BurnDamageScale=1.000000
|
|
bFireImmune=False
|
|
MoanVoice=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Talk'
|
|
BleedOutDuration=5.000000
|
|
MiscSound=Sound'KF_FY_ZEDV2SND.foley.WEP_ZEDV2_Projectile_Loop'
|
|
DecapitationSound=Sound'KF_FY_ZEDV2SND.Fire.WEP_ZEDV2_Secondary_Explode'
|
|
MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Bloat.Bloat_HitPlayer'
|
|
JumpSound=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Jump'
|
|
ColHeight=30.000000
|
|
ProjectileBloodSplatClass=None
|
|
DetachedArmClass=Class'ScrnZedPack.SeveredArmTeslaHusk'
|
|
DetachedLegClass=Class'ScrnZedPack.SeveredLegTeslaHusk'
|
|
DetachedHeadClass=Class'ScrnZedPack.SeveredHeadTeslaHusk'
|
|
DetachedSpecialArmClass=Class'ScrnZedPack.SeveredGunTeslaHusk'
|
|
OnlineHeadshotOffset=(X=22.000000,Z=50.000000)
|
|
HitSound(0)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Pain'
|
|
DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Death'
|
|
ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Challenge'
|
|
ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Challenge'
|
|
ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Challenge'
|
|
ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Challenge'
|
|
ScoringValue=25
|
|
GroundSpeed=100.000000
|
|
WaterSpeed=80.000000
|
|
HealthMax=800.000000
|
|
Health=800
|
|
HeadHeight=-7.500000
|
|
MenuName="Tesla Husk"
|
|
AmbientSound=Sound'KF_BaseHusk_CIRCUS.Husk_IdleLoop'
|
|
Mesh=SkeletalMesh'ScrnZedPack_A.TeslaHuskMesh'
|
|
Skins(0)=Shader'ScrnZedPack_T.TeslaHusk.TeslaHusk_SHADER'
|
|
}
|