// Zombie Monster for KF Invasion gametype
class NiceZombieSiren extends NiceZombieSirenBase;
var float           screamLength;
var float           screamStartTime;
var int             currScreamTiming;
var int             currentScreamID;
var array<float>    screamTimings;
simulated function PostBeginPlay()
{
    super.PostBeginPlay();
    screamLength = GetAnimDuration('Siren_Scream');
}
simulated event SetAnimAction(name NewAction)
{
    local int meleeAnimIndex;
    if( NewAction=='' )
       Return;
    if(NewAction == 'Claw')
    {
       meleeAnimIndex = Rand(3);
       NewAction = meleeAnims[meleeAnimIndex];
    }
    ExpectingChannel = DoAnimAction(NewAction);
    if( AnimNeedsWait(NewAction) )
    {
       bWaitForAnim = true;
    }
    else
    {
       bWaitForAnim = false;
    }
    if( Level.NetMode!=NM_Client )
    {
       AnimAction = NewAction;
       bResetAnimAct = True;
       ResetAnimActTime = Level.TimeSeconds+0.3;
    }
}
simulated function bool AnimNeedsWait(name TestAnim)
{
    return false;
}
function bool FlipOver()
{
    return true;
}
function DoorAttack(Actor A)
{
    if ( bShotAnim || Physics == PHYS_Swimming || bDecapitated || A==none )
       return;
    bShotAnim = true;
    SetAnimAction('Siren_Scream');
}
function MakeNewScreamBall(){
    local int i;
    local NicePack niceMut;
    if(screamStartTime > 0){
       niceMut = class'NicePack'.static.Myself(Level);
       if(niceMut != none){
           for(i = 0;i < niceMut.playersList.Length;i ++)
               if(niceMut.playersList[i] != none && screamStartTime > 0)
                   niceMut.playersList[i].SpawnSirenBall(self);
       }
    }    
}
function DiscardCurrentScreamBall(){
    local int i;
    local NicePack niceMut;
    if(screamStartTime > 0){
       niceMut = class'NicePack'.static.Myself(Level);
       if(niceMut != none){
           for(i = 0;i < niceMut.playersList.Length;i ++)
               if(niceMut.playersList[i] != none)
                   niceMut.playersList[i].ClientRemoveSirenBall(currentScreamID);
       }
       screamStartTime = -1.0;
       currScreamTiming = -1;
    }    
}

function RangedAttack(Actor A)
{
    local int LastFireTime;
    local float Dist;

    if (bShotAnim)
        return;
    Dist = VSize(A.Location - Location);

    if (Physics == PHYS_Swimming)
    {
        SetAnimAction('Claw');
        bShotAnim = true;
        LastFireTime = Level.TimeSeconds;
    }
    else if (Dist < MeleeRange + CollisionRadius + A.CollisionRadius && A != Self)
    {
        bShotAnim = true;
        LastFireTime = Level.TimeSeconds;
        SetAnimAction('Claw');
        Controller.bPreparingMove = true;
        Acceleration = vect(0,0,0);
    }
    // ok se we DO NOT want to scream at other nice zeds
    else if (!bNotAHuman() && Dist <= ScreamRadius && !bDecapitated && !bZapped)
    {
        bShotAnim = true;
        SetAnimAction('Siren_Scream');
        if (screamStartTime > 0)
           DiscardCurrentScreamBall();
        currScreamTiming = 0;
        screamStartTime = Level.TimeSeconds;
        // Only stop moving if we are close
        if (Dist < ScreamRadius * 0.25)
        {
            Controller.bPreparingMove = true;
            Acceleration = vect(0,0,0);
        }
        else
        {
            Acceleration = AccelRate * Normal(A.Location - Location);
        }
        Acceleration.Z = FMin(Acceleration.Z, 0.0f);
    }
}

final private function bool bNotAHuman()
{
    if (Controller == none || Controller.Enemy == none)
        return false;

    // log("Zed enemy classname was " $ Controller.Enemy.class);
    return ClassIsChildOf(Controller.Enemy.class, class'NiceMonster');
}

simulated function int DoAnimAction( name AnimName )
{
    if( AnimName=='Siren_Scream' || AnimName=='Siren_Bite' || AnimName=='Siren_Bite2' )
    {
       AnimBlendParams(1, 1.0, 0.0,, SpineBone1);
       PlayAnim(AnimName,, 0.1, 1);
       return 1;
    }
    PlayAnim(AnimName,,0.1);
    Return 0;
}
// Scream Time
simulated function SpawnTwoShots(){
    if(bZapped)
       return;
    if(Health > 0 && HeadHealth > 0 && !bIsStunned)
       DoShakeEffect();
    if( Level.NetMode!=NM_Client )
    {
       // Deal Actual Damage.
       if(controller!=none && KFDoorMover(Controller.Target) != none)
           Controller.Target.TakeDamage(ScreamDamage*0.6,Self,Location,vect(0,0,0),ScreamDamageType);
       else HurtRadius(ScreamDamage ,ScreamRadius, ScreamDamageType, ScreamForce, Location);
       if(screamStartTime > 0)
           currScreamTiming ++;
       else
           Log("ERROR: unexpected siren scream happend!");
    }
}
// Shake nearby players screens
simulated function DoShakeEffect()
{
    local PlayerController PC;
    local NicePlayerController nicePlayer;
    local float Dist, scale, BlurScale;
    //viewshake
    if (Level.NetMode != NM_DedicatedServer)
    {
        PC = Level.GetLocalPlayerController();
        nicePlayer = NicePlayerController(PC);
        if (PC != none && PC.ViewTarget != none)
        {
            Dist = VSize(Location - PC.ViewTarget.Location);
            if (Dist < ScreamRadius )
            {
                scale = (ScreamRadius - Dist) / (ScreamRadius);
                scale *= ShakeEffectScalar;
                if(nicePlayer != none)
                    scale *= nicePlayer.sirenScreamMod;
        
                BlurScale = scale;

                // Reduce blur if there is something between us and the siren
                if( !FastTrace(PC.ViewTarget.Location,Location) )
                {
                   scale *= 0.25;
                   BlurScale = scale;
                }
                else
                {
                    if(nicePlayer != none)
                        scale = Lerp(scale, MinShakeEffectScale * nicePlayer.sirenScreamMod, 1.0);
                    else
                        scale = Lerp(scale, MinShakeEffectScale, 1.0);
                }

                PC.SetAmbientShake(Level.TimeSeconds + ShakeFadeTime, ShakeTime, OffsetMag * Scale, OffsetRate, RotMag * Scale, RotRate);

                if( KFHumanPawn(PC.ViewTarget) != none )
                {
                   KFHumanPawn(PC.ViewTarget).AddBlur(ShakeTime, BlurScale * ScreamBlurScale);
                }

                // 10% chance of player saying something about our scream
                if ( Level != none && Level.Game != none && !KFGameType(Level.Game).bDidSirenScreamMessage && FRand() < 0.10 )
                {
                   PC.Speech('AUTO', 16, "");
                   KFGameType(Level.Game).bDidSirenScreamMessage = true;
                }
            }
        }
    }
}

simulated function HurtRadius(float DamageAmount, float DamageRadius, class<DamageType> DamageType, float Momentum, vector HitLocation)
{
    local actor Victims;
    local float InitMomentum;
    local float damageScale, dist;
    local vector dir;
    local int UsedDamageAmount;
    local KFHumanPawn humanPawn;
    local class<NiceVeterancyTypes> niceVet;
    local NiceZombieClot niceClot;

    if (bHurtEntry || Health <= 0 || HeadHealth <= 0 || bIsStunned)
       return;
    bHurtEntry = true;

    InitMomentum = Momentum;
    if (screamStartTime > 0 && currScreamTiming == 0)
       MakeNewScreamBall();

    // was VisibleCollidingActors
    foreach CollidingActors(class'Actor', Victims, DamageRadius, HitLocation)
    {
        // some optimizations
        // https://wiki.beyondunreal.com/Legacy:Code_Optimization#Optimize_iterator_use
        if (Victims.bStatic || Victims.Physics == PHYS_None || !FastTrace(Victims.Location, Location) )
            continue; // skip this actor

        niceClot = NiceZombieClot(victims);
        if (niceClot != none) {
            niceClot.sirenBoostTimeout = 10.0;
        }
        Momentum = InitMomentum;
        // don't let blast damage affect fluid - VisibleCollisingActors doesn't really work for them - jag
        // Or Karma actors in this case. Self inflicted Death due to flying chairs is uncool for a zombie of your stature.
        if ((Victims != self) && !Victims.IsA('FluidSurfaceInfo') && !Victims.IsA('KFMonster') && !Victims.IsA('ExtendedZCollision'))
        {
            dir = Victims.Location - HitLocation;
            dist = FMax(1,VSize(dir));
            dir = dir/dist;
            damageScale = 1 - FMax(0,(dist - Victims.CollisionRadius)/DamageRadius);
            humanPawn = KFHumanPawn(Victims);
            if (humanPawn == none)    // If it aint human, don't pull the vortex crap on it.
                Momentum = 0;
            else
            {   // Also don't do it if we're sharpshooter with a right skill
                niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(humanPawn.PlayerReplicationInfo);
                if (niceVet != none && !niceVet.static.CanBePulled(KFPlayerReplicationInfo(humanPawn.PlayerReplicationInfo)))
                    Momentum = 0;
            }

            if (Victims.IsA('KFGlassMover')) // Hack for shattering in interesting ways.
                UsedDamageAmount = 100000;  // Siren always shatters glass
            else
                UsedDamageAmount = DamageAmount;
            ModDamageFromZed(UsedDamageAmount, DamageType);
            Victims.TakeDamage(damageScale * UsedDamageAmount,Instigator, Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dir, (damageScale * Momentum * dir), DamageType);

            if (Instigator != none && Vehicle(Victims) != none && Vehicle(Victims).Health > 0)
                Vehicle(Victims).DriverRadiusDamage(UsedDamageAmount, DamageRadius, Instigator.Controller, DamageType, Momentum, HitLocation);
        }
    }

    bHurtEntry = false;
}

// When siren loses her head she's got nothin' Kill her.
function RemoveHead(){
    Super.RemoveHead();
}
simulated function UpdateGroundSpeed() {
    super.UpdateGroundSpeed();
    if (bShotAnim) {
        groundSpeed *= 0.65;
    }
}
simulated function Tick( float Delta )
{
    local float currScreamTime;
    Super.Tick(Delta);
    if( bAboutToDie && Level.TimeSeconds>DeathTimer )
    {
       if( Health>0 && Level.NetMode!=NM_Client )
           KilledBy(LastDamagedBy);
       bAboutToDie = False;
    }
    if( Role == ROLE_Authority )
    {
       if( bShotAnim )
       {
           if( LookTarget!=none )
           {
               Acceleration = AccelRate * Normal(LookTarget.Location - Location);
           }
       }
    }
    if(Role == ROLE_Authority && screamStartTime > 0){
       currScreamTime = Level.TimeSeconds - screamStartTime;
       if(currScreamTiming >= screamTimings.Length ||
           currScreamTime - 0.1 > screamTimings[currScreamTiming] * screamLength){
           DiscardCurrentScreamBall();
       }
    }
    if(bOnFire && !bShotAnim)
       RangedAttack(Self);
}
function PlayDyingSound()
{
    if( !bAboutToDie )
       Super.PlayDyingSound();
}
simulated function ProcessHitFX()
{
    local Coords boneCoords;
    local class<xEmitter> HitEffects[4];
    local int i,j;
    local float GibPerterbation;
    if( (Level.NetMode == NM_DedicatedServer) || bSkeletized || (Mesh == SkeletonMesh))
    {
       SimHitFxTicker = HitFxTicker;
       return;
    }
    for ( SimHitFxTicker = SimHitFxTicker; SimHitFxTicker != HitFxTicker; SimHitFxTicker = (SimHitFxTicker + 1) % ArrayCount(HitFX) )
    {
       j++;
       if ( j > 30 )
       {
           SimHitFxTicker = HitFxTicker;
           return;
       }

       if( (HitFX[SimHitFxTicker].damtype == none) || (Level.bDropDetail && (Level.TimeSeconds - LastRenderTime > 3) && !IsHumanControlled()) )
           continue;

       //log("Processing effects for damtype "$HitFX[SimHitFxTicker].damtype);

       if( HitFX[SimHitFxTicker].bone == 'obliterate' && !class'GameInfo'.static.UseLowGore())
       {
           SpawnGibs( HitFX[SimHitFxTicker].rotDir, 1);
           bGibbed = true;
           // Wait a tick on a listen server so the obliteration can replicate before the pawn is destroyed
           if( Level.NetMode == NM_ListenServer )
           {
               bDestroyNextTick = true;
               TimeSetDestroyNextTickTime = Level.TimeSeconds;
           }
           else
           {
               Destroy();
           }
           return;
       }

       boneCoords = GetBoneCoords( HitFX[SimHitFxTicker].bone );

       if ( !Level.bDropDetail && !class'GameInfo'.static.NoBlood() && !bSkeletized && !class'GameInfo'.static.UseLowGore())
       {
           //AttachEmitterEffect( BleedingEmitterClass, HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir );

           HitFX[SimHitFxTicker].damtype.static.GetHitEffects( HitEffects, Health );

           if( !PhysicsVolume.bWaterVolume ) // don't attach effects under water
           {
               for( i = 0; i < ArrayCount(HitEffects); i++ )
               {
                   if( HitEffects[i] == none )
                       continue;

                     AttachEffect( HitEffects[i], HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir );
               }
           }
       }
       if ( class'GameInfo'.static.UseLowGore() )
           HitFX[SimHitFxTicker].bSever = false;

       if( HitFX[SimHitFxTicker].bSever )
       {
           GibPerterbation = HitFX[SimHitFxTicker].damtype.default.GibPerterbation;

           switch( HitFX[SimHitFxTicker].bone )
           {
               case 'obliterate':
                   break;

               case LeftThighBone:
                   if( !bLeftLegGibbed )
                   {
                       SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) );
                       KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                       KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                       KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                       bLeftLegGibbed=true;
                   }
                   break;

               case RightThighBone:
                   if( !bRightLegGibbed )
                   {
                       SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) );
                       KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                       KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                       KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;
                       bRightLegGibbed=true;
                   }
                   break;

               case LeftFArmBone:
                   break;

               case RightFArmBone:
                   break;

               case 'head':
                   if( !bHeadGibbed )
                   {
                       if ( HitFX[SimHitFxTicker].damtype == class'DamTypeDecapitation' )
                       {
                           DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false);
                       }
                       else if( HitFX[SimHitFxTicker].damtype == class'DamTypeProjectileDecap' )
                       {
                           DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false, true);
                       }
                       else if( HitFX[SimHitFxTicker].damtype == class'DamTypeMeleeDecapitation' )
                       {
                           DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, true);
                       }

                         bHeadGibbed=true;
                     }
                   break;
           }

           if( HitFX[SimHitFXTicker].bone != 'Spine' && HitFX[SimHitFXTicker].bone != FireRootBone &&
               HitFX[SimHitFXTicker].bone != LeftFArmBone && HitFX[SimHitFXTicker].bone != RightFArmBone &&
               HitFX[SimHitFXTicker].bone != 'head' && Health <=0 )
               HideBone(HitFX[SimHitFxTicker].bone);
       }
    }
}
static simulated function PreCacheMaterials(LevelInfo myLevel)
{//should be derived and used.
    myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.siren_cmb');
    myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.siren_env_cmb');
    myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.siren_diffuse');
    myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.siren_hair');
    myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.siren_hair_fb');
}


// DEAD sirens can NOT spawn scream emitter 
simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
  super.PlayDying(DamageType, HitLoc);

  // yea, stop all animations on dead zed
  StopAnimating();
}


state ZombieDying
{
ignores AnimEnd, Trigger, Bump, HitWall, HeadVolumeChange, PhysicsVolumeChange, Falling, BreathTimer, Died, RangedAttack;

  simulated function BeginState()
  {
    // yea, stop all animations on dead zed
    StopAnimating();

    super.BeginState();
  }
}


defaultproperties
{
    screamTimings(0)=0.420000
    screamTimings(1)=0.510000
    screamTimings(2)=0.590000
    screamTimings(3)=0.670000
    screamTimings(4)=0.760000
    screamTimings(5)=0.840000
    stunLoopStart=0.200000
    stunLoopEnd=0.820000
    idleInsertFrame=0.920000
    EventClasses(0)="NicePack.NiceZombieSiren"
    MoanVoice=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Talk'
    JumpSound=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Jump'
    DetachedLegClass=Class'KFChar.SeveredLegSiren'
    DetachedHeadClass=Class'KFChar.SeveredHeadSiren'
    HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Pain'
    DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Death'
    ControllerClass=class'NiceZombieSirenController'
    AmbientSound=Sound'KF_BaseSiren.Siren_IdleLoop'
    Mesh=SkeletalMesh'KF_Freaks_Trip.Siren_Freak'
    Skins(0)=FinalBlend'KF_Specimens_Trip_T.siren_hair_fb'
    Skins(1)=Combiner'KF_Specimens_Trip_T.siren_cmb'
}
