//=============================================================================
// KFSM_GrappleAttack_Hans
//=============================================================================
// Hans's energy drain attack - he grabs his victim (similar to the clot
// grab attack) and siphons health from the victim.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2016 Tripwire Interactive LLC
//=============================================================================
class KFSM_GrappleAttack_Hans extends KFSM_GrappleCombined;

/** Alternate (lunge) grab anim name */
var name GrabStartAnimNameLunge;

var bool bAlreadyDetachedFollower;
var KFPawn CachedFollower;

/** How often, in seconds, damage/healing should be done */
var float DrainInterval;

/** How long to wait after grabbing the player before draining */
var float DelayBeforeDrain;

/** How much to damage/heal per drain DrainInterval */
var int HealthGainPerDrainInterval;
var int DamageDrainRemaining;
var int NumDrainsRemaining;

var int FollowerStartingHealth;

/** The threshold (multiple of Enemy.MaxHealth) at which to drain health */
var float EnemyDrawLifeThreshold;

/** How much life to draw from an enemy when drawing life per difficulty level */
var float MaxEnemyLifeDrawThresholdNormal;
var float MaxEnemyLifeDrawThresholdHard;
var float MaxEnemyLifeDrawThresholdSuicidal;
var float MaxEnemyLifeDrawThresholdHellOnEarth;
/** When taking damage from multiple sources never drain health beyond this point */
var float MinEnemyLifeDrawThreshold;

/** How long Hans should wait to do an attack on the player he just drained when he is finished */
var float PostDrainAttackCooldown;

/** Smoke explosion */
var class<KFExplosionActor> LifeDrainSmokeExplosionActorClass;
var KFGameExplosion LifeDrainSmokeExplosionTemplate;

/** Invulnerability shield */
var ParticleSystem InvulnerableEnergyFX;
var ParticleSystemComponent InvulnerableEnergyPSC;
var name InvulnerableEnergySocketName;

/** Determine if this is a lunge grab or regular grab */
static function byte PackFlagsBase( KFPawn P )
{
    if( P.MyKFAIC != none && P.MyKFAIC.Enemy != none )
    {
        if( VSizeSQ(P.MyKFAIC.Enemy.Location - P.Location) > class'KFAIController_Hans'.default.MinDistanceToPerformGrabAttack*0.33f )
        {
            return 128;
        }
    }

    return 0;
}

function SpecialMoveStarted( bool bForced, Name PrevMove )
{
    local KFPawn_ZedHansBase HansPawn;

    super.SpecialMoveStarted( bForced, PrevMove );

    if( PawnOwner.Role == ROLE_Authority )
    {
        HansPawn = KFPawn_ZedHansBase(PawnOwner);
        if( HansPawn != none )
        {
            HansPawn.PlayGrabDialog();
        }
    }

    if( KFPOwner != none && KFPOwner.MyKFAIC != none )
    {
        // Determine what life draw threshold to use
        if( KFPOwner.MyKFAIC.Skill == class'KFGameDifficultyInfo'.static.GetDifficultyValue(0) ) // Normal
        {
            EnemyDrawLifeThreshold = MaxEnemyLifeDrawThresholdNormal;
        }
        else if( KFPOwner.MyKFAIC.Skill <= class'KFGameDifficultyInfo'.static.GetDifficultyValue(1) ) // Hard
        {
            EnemyDrawLifeThreshold = MaxEnemyLifeDrawThresholdHard;
        }
        else if( KFPOwner.MyKFAIC.Skill <= class'KFGameDifficultyInfo'.static.GetDifficultyValue(2) ) // Suicidal
        {
            EnemyDrawLifeThreshold = MaxEnemyLifeDrawThresholdSuicidal;
        }
        else // Hell on Earth
        {
            EnemyDrawLifeThreshold = MaxEnemyLifeDrawThresholdHellOnEarth;
        }
    }
}

/** Play an animation and enable the OnAnimEnd notification */
function PlayGrabAnim()
{
    if( KFPOwner.SpecialMoveFlags == 128 )
    {
        bUseRootMotion = true;
        GrabStartAnimName = default.GrabStartAnimNameLunge;
    }
    else
    {
        bUseRootMotion = default.bUseRootMotion;
        GrabStartAnimName = default.GrabStartAnimName;
    }

    super.PlayGrabAnim();
}

function PlayGrappleLoopAnim()
{
    local float Duration, InterruptTime, ActualDrainTime, DrainsPerSecond;

    // Get our anim duration
    Duration = PlaySpecialMoveAnim( GrappleAnims[0], EAS_FullBody );

    // Set our release timer
    bAlreadyDetachedFollower = false;
    InterruptTime = KFSkeletalMeshComponent(KFPOwner.Mesh).GetAniminterruptTime( GrappleAnims[0] );
    KFPOwner.SetTimer( InterruptTime, false, nameof(Timer_DetachFollower), self );

    if( KFPOwner.Role == ROLE_Authority )
    {
        // Set our special move flag so clients start the grab as well
        KFPOwner.SpecialMoveFlags = EGS_GrabSuccess;

        PostDrainAttackCooldown = (Duration - InterruptTime) + 0.5f;
        ActualDrainTime = InterruptTime - DelayBeforeDrain;
        DrainsPerSecond = 1.f / DrainInterval;
        // Number of times the drain timer will be called
        NumDrainsRemaining = ActualDrainTime / DrainInterval;

        // Determine the rate to draw life at
        if( Follower.GetHealthPercentage() > EnemyDrawLifeThreshold )
        {
            // Damage the player only in the period between the initial drain delay and the follower detach time
            DamageDrainRemaining = Max(Follower.Health - (float(Follower.HealthMax) * EnemyDrawLifeThreshold), 0);
        }
        else
        {
            DamageDrainRemaining = 0;
        }

        // Heal only in the period between the initial drain delay and the follower detach time
        HealthGainPerDrainInterval  = Max( (KFPawn_ZedHansBase(KFPOwner).GetHealAmountForThisPhase() / ActualDrainTime) / DrainsPerSecond, 1 );
    }
}

/** Called when grapple is successful and interaction pawn is attached */
function BeginGrapple(optional KFPawn Victim)
{
    local KFPawn_ZedHansBase HansPawn;
    local KFAIController KFAIC;
    local KFExplosionActor ExplosionActor;

    super.BeginGrapple( Victim );

    HansPawn = KFPawn_ZedHansBase(PawnOwner);
    if( Follower != none && HansPawn != none && HansPawn.Controller != none)
    {
        CachedFollower = Follower;

        // Don't let other zeds target this player
        Follower.ExclusiveTargetingController = HansPawn.Controller;

        // If other zeds are targeting this player, make them target someone else or wander around
        foreach Follower.WorldInfo.AllControllers( class'KFAIController', KFAIC )
        {
            if( KFAIC != none && KFAIC != HansPawn.Controller
                && KFAIC.Enemy == Follower )
            {
                KFAIC.Enemy = none;
                KFAIC.FindNewEnemy();

                // Have the enemies buzz off for a few if they can't find an enemy
                if( KFAIC.Enemy == none )
                {
                    KFAIC.DoWander( Follower, 5.0, true );
                }
                // Wander anyway if they are real close, just to get them away from Hans - Ramm, maybe add back later if needed
                //else if( VSizeSq(KFAIC.Pawn.Location - Follower.Location) < 250000 ) // 5 meters
                //{
                //    KFAIC.DoWander( Follower, 1.0, true );
                //}
            }
        }

        HansPawn.PlayHealDialog();

        // Set drain health timer
        KFPOwner.SetTimer( DelayBeforeDrain, true, nameof(Timer_DrainHealth), self );
    }

    // spawn smoke cloud
    ExplosionActor = KFPOwner.Spawn(LifeDrainSmokeExplosionActorClass, KFPOwner,, KFPOwner.mesh.GetBoneLocation('Root'), rotator(vect(0,0,1)));
    if( ExplosionActor != none )
    {
        ExplosionActor.Explode( LifeDrainSmokeExplosionTemplate );
    }

    if( KFPOwner.WorldInfo.NetMode != NM_DedicatedServer )
    {
        InvulnerableEnergyPSC = KFPOwner.WorldInfo.MyEmitterPool.SpawnEmitterMeshAttachment(InvulnerableEnergyFX, KFPOwner.Mesh, InvulnerableEnergySocketName, true);
    }    
}

/** Toggle attachment */
function SpecialMoveFlagsUpdated()
{
    if( KFPOwner.SpecialMoveFlags != 128 )
    {
        super.SpecialMoveFlagsUpdated();
    }
}

function SpecialMoveEnded( Name PrevMove, Name NextMove )
{
    local KFAISpawnManager SpawnManager;
    local KFPawn_ZedHansBase HansPawn;

    KFPOwner.ClearTimer( nameof(Timer_DrainHealth), self );
    KFPOwner.ClearTimer( nameof(Timer_DetachFollower), self );

    if( KFPOwner != none && KFPOwner.MyKFAIC != none && KFPOwner.MyKFAIC != none
        && KFAIController_Hans(KFPOwner.MyKFAIC) != none )
    {
        HansPawn = KFPawn_ZedHansBase(KFPOwner);

        // stop hunting players and sucking health if you got enough health
        if( HansPawn != none )
        {        
            if( HansPawn.AmountHealedThisPhase > HansPawn.GetHealAmountForThisPhase() * 0.75f )
            { 
                HansPawn.SetHuntAndHealMode( false );
            }
        }
        SpawnManager = KFGameInfo(PawnOwner.WorldInfo.Game).SpawnManager; 
        if ( SpawnManager != none )
        {
            SpawnManager.StopSummoningBossMinions();
        }

        if( CachedFollower != none && HansPawn != none && KFPOwner.MyKFAIC != none)
        {
            // If there is no enemy taunt instead of just standing around
            if( KFPOwner.MyKFAIC.Enemy == None && HansPawn.CanDoSpecialMove(SM_Taunt) )
            {
                class'AICommand_TauntEnemy'.static.Taunt( KFPOwner.MyKFAIC, CachedFollower, TAUNT_Standard );
            }
        }
    }

    if( InvulnerableEnergyPSC != none )
    {
        KFPOwner.DetachEmitter( InvulnerableEnergyPSC );
    }
    CachedFollower = none;

    super.SpecialMoveEnded( PrevMove, NextMove );
}

function Timer_DrainHealth()
{
    local KFPawn_ZedHansBase HansPawn;
    local int Damage;

    if( KFPOwner != none && KFPOwner.Health > 0 && Follower != none && Follower.Health >= 0 && Follower.IsDoingSpecialMove(SM_HansGrappleVictim) )
    {
        if ( NumDrainsRemaining > 0 )
        {
            // Round off an integer damage value from the total damage left
            Damage = Round(float(DamageDrainRemaining) / NumDrainsRemaining);
            DamageDrainRemaining -= Damage;
            NumDrainsRemaining--;

            // Allow life to drop below EnemyDrawLifeThreshold, but don't deal killing blow
            if( Damage > 0 && (float(Follower.Health - Damage) / Follower.HealthMax) > MinEnemyLifeDrawThreshold )
            {
                Follower.TakeDamage( Damage, KFPOwner.Controller, Follower.Location, vect(0,0,0), class'KFDT_DrainHealth' );
            }
        }

        KFPOwner.HealDamage( HealthGainPerDrainInterval, KFPOwner.Controller, class'KFDT_Healing' );

        HansPawn = KFPawn_ZedHansBase(KFPOwner);
        if( HansPawn != none )
        {
            HansPawn.AmountHealedThisPhase += HealthGainPerDrainInterval;
            // Don't let Hans try and toss smoke right after healing
            HansPawn.LastSmokeTossTime = HansPawn.WorldInfo.TimeSeconds;
        }        
    }
    else
    {
        KFPOwner.ClearTimer( nameof(Timer_DrainHealth), self );
    }
}

function Timer_DetachFollower()
{
    // Set flag so follower doesn't try to end special move after detaching
    bAlreadyDetachedFollower = true;

    // Clear timers
    KFPOwner.ClearTimer( nameof(Timer_DrainHealth), self );
    KFPOwner.ClearTimer( nameof(CheckReadyToStartInteraction), self );
    KFPOwner.ClearTimer( nameof(InteractionStartTimedOut), self );
    KFPOwner.ClearTimer( nameof(RetryCollisionTimer), self );

    if( Follower != none )
    {
        if( KFPOwner.Role == ROLE_Authority )
        {
            // Have Hans and other zeds not attack this player after he just drained them
            Follower.AIIgnoreEndTime = Follower.WorldInfo.TimeSeconds + PostDrainAttackCooldown;

            if( KFPOwner.MyKFAIC != none )
            {
                KFPOwner.MyKFAIC.Enemy = none;
                KFPOwner.MyKFAIC.FindNewEnemy();
            }
        }

        // Zero movement variables on Follower to stop any animation-driven motion
        if( bAlignPawns && !KFPOwner.IsHumanControlled() )
        {
            Follower.ZeroMovementVariables();
        }

        // End special move on Follower
        Follower.EndSpecialMove();
    }

    if( ExecutionCameraAnimInst_Follower != None )
    {
        if( PCOwner != None )
        {
            PCOwner.PlayerCamera.StopCameraAnim( ExecutionCameraAnimInst_Follower );
        }
        ExecutionCameraAnimInst_Follower = None;
    }

    // Clear reference to Interaction Pawn.
    Follower = None;
}

// Use timer & animlength instead of animend
function AnimEndNotify( AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime )
{
    ActiveSlotNode = None;

    // By default end this special move.
    KFPOwner.EndSpecialMove();
}

/** Notification when Follower is leaving his FollowerSpecialMove */
function OnFollowerLeavingSpecialMove()
{
    if( bAlreadyDetachedFollower )
    {
        return;
    }

    KFPOwner.EndSpecialMove();
}

/** Disable grab interruption */
function NotifyOwnerTakeHit(class<KFDamageType> DamageType, vector HitLoc, vector HitDir, Controller InstigatedBy);

DefaultProperties
{
    FollowerSpecialMove=SM_HansGrappleVictim
    AlignDistance=108.f
    AlignFollowerInterpSpeed=22.f
    bStopAlignFollowerRotationAtGoal=false
    bCanOnlyWanderAtEnd=true
    MaxGrabDistance=250.f
    bCanBeBlocked=false
    bCanBeInterrupted=false

    // ---------------------------------------------
    // Animations
    GrabStartAnimName=Atk_Paralyze_V1
    GrabStartAnimNameLunge=Atk_Lunge_Paralyze_V1
    GrappleAnims=(Atk_DrawLife_V1)

    DelayBeforeDrain=0.25f
    DrainInterval=0.25f

    MaxEnemyLifeDrawThresholdNormal=0.70
    MaxEnemyLifeDrawThresholdHard=0.45   //0.6
    MaxEnemyLifeDrawThresholdSuicidal=0.35  //0.5
    MaxEnemyLifeDrawThresholdHellOnEarth=0.25  //0.25
    MinEnemyLifeDrawThreshold=0.1

    // smoke cloud explosion template
    Begin Object Class=KFGameExplosion Name=ExploTemplate0
        Damage=0
        DamageRadius=0
        KnockDownRadius=0
        CringeRadius=0
        bCausesFracture=false

        // effects
        ExplosionEffects=KFImpactEffectInfo'ZED_Hans_EMIT.SmokeGrenade_Explosion'
        ExplosionSound=AkEvent'WW_WEP_EXP_Grenade_Medic.Play_WEP_EXP_Grenade_Medic_Explosion'
    End Object
    LifeDrainSmokeExplosionTemplate=ExploTemplate0

    // smoke cloud explosion actor
    LifeDrainSmokeExplosionActorClass=class'KFExplosion_HansSmokeGrenade'

    // invulnerable energy
    InvulnerableEnergyFX=ParticleSystem'ZED_Hans_EMIT.FX_Hans_invulnerable_Energy'
    InvulnerableEnergySocketName=Root
}