//=============================================================================
// KFMG_RiggedTargetGame
//=============================================================================
// Extension of target game that uses a rigged animation and series of other
//      subactors to handle the individual target hits.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// - Dan Weiss
//=============================================================================

class KFMG_RiggedTargetGame extends KFMG_TargetGame;

/** Archetype of target actor that will be spawned per-bone */
var() Actor TargetArchetype;

/** SKL actor used to drive animation of the minigame */
var() repnotify KFMGA_TargetGame MinigameRig;

/** How many targets have not been hit */
var int TargetsRemaining;

/** Which animation index to use (determined by server) */
var repnotify int AnimationIndex;

/** Which rig index to use for grabbing animation data (always 0 in base game, 0...n in multilevel extension) */
var repnotify int RigIndex;

/** Local track variable for the state of the game.  Primarily used for repnotify check to make sure all info has come down */
var bool bLocalStarted;

/** End game state - 0 victory, 1 defeat */
var repnotify int EndGameState;

enum eAnimationState
{
    EAS_Closed, //Inactive state
    EAS_Open,   //Transition to active
    EAS_Active, //Active state
    EAS_Idle,   //Pre-close transition
    EAS_Close,  //Transition to inactive state
};

/** Time to take to blend to new animations (if using an AnimTree) */
var() array<float> BlendToTimes;

replication
{
    if (bNetDirty)
        AnimationIndex, RigIndex, EndGameState;

    if (bNetInitial)
        MinigameRig;
}

simulated event ReplicatedEvent(name VarName)
{
    super.ReplicatedEvent(VarName);

    if (VarName == 'EndGameState')
    {
        if (EndGameState >= 0)
        {
            FinalizeGame();
        }
    }
    else if (VarName == 'AnimationIndex')
    {
        if (!bLocalStarted && AnimationIndex >= 0 && RigIndex >= 0)
        {
            StartupGame();
        }
    }
    else if (VarName == 'RigIndex')
    {
        if (!bLocalStarted && AnimationIndex >= 0 && RigIndex >= 0)
        {
            StartupGame();
        }
    }
    else if (VarName == 'MinigameRig')
    {
        MinigameRig.ParentGame = self;
    }
}

/** Spawn a target per-bone and hide actor.  They will be reset and unhidden on
*     activation of the mini-game.
*/
event PostBeginPlay()
{
    local name BoneName;
    local Actor NewTarget;
    local KFMGA_Target RiggedTarget;
    super.PostBeginPlay();

    MinigameRig.ParentGame = self;
    if (MinigameRig != none && TargetArchetype != none)
    {
        foreach MinigameRig.TargetBones(BoneName)
        {
            NewTarget = spawn(TargetArchetype.class, self, , , , TargetArchetype);
            if (NewTarget != none)
            {
                MinigameTargets.AddItem(NewTarget);
                NewTarget.SetBase(MinigameRig, , MinigameRig.SkeletalMeshComponent, BoneName);

                RiggedTarget = KFMGA_Target(NewTarget);
                if (RiggedTarget != none)
                {
                    RiggedTarget.SetInactive();
                    RiggedTarget.SpawnerOwner = self;
                    RiggedTarget.SpawnerOwnerIndex = MinigameTargets.Length - 1;
                }
            }
        }
    }
}

simulated function Reset()
{
    local int i;

    super.Reset();

    SwitchAnim(EAS_Closed);
    if (!MinigameRig.SkeletalMeshComponent.Animations.IsA('AnimTree'))
    {
        MinigameRig.SkeletalMeshComponent.StopAnim();
    }

    //Enable server optimization for the minigame rig while it's not running
    MinigameRig.SetTickIsDisabled(true);

    bLocalStarted = false;

    for (i = 0; i < MinigameTargets.Length; ++i)
    {
        MinigameTargets[i].Reset();
    }
}

//Activate target game.  All authority stuff should be done here.  Generic stuff should be done in StartupGame
function Activated(KFTrigger_MinigameButton ActivationSource)
{
    if (RigIndex >= 0 && MinigameRig != none && (MinigameRig.MinigameAnimData.Length > 0 || MinigameRig.SkeletalMeshComponent.AnimTreeTemplate != none) && MinigameTargets.Length == MinigameRig.TargetBones.Length)
    {
        super.Activated(ActivationSource);

        TargetsRemaining = MinigameTargets.Length;
        AnimationIndex = MinigameRig.MinigameAnimData[RigIndex].MinigameAnimations.Length > 0 ? Rand(MinigameRig.MinigameAnimData[RigIndex].MinigameAnimations.Length) : -1;
        StartupGame();
        EndGameState = -1;
    }
}

/** Handles animation functionality for a rigged target game.  If the base is an animtree, sets a blend list.
 *      If the rig uses straight animations, calls a playanim.
 */
simulated function SwitchAnim(eAnimationState SelectedState)
{
    local name AnimName;
    local bool bLoop;
    local AnimNodeBlendList BlendList;

    //Set blend node in the animtree
    if (MinigameRig.SkeletalMeshComponent.Animations.IsA('AnimTree'))
    {
        BlendList = AnimNodeBlendList(MinigameRig.SkeletalMeshComponent.FindAnimNode('StateBlend'));
        if (BlendList != none)
        {
            BlendList.SetActiveChild(SelectedState, BlendToTimes[SelectedState]);
        }
    }
    //Play animations directly
    else
    {
        bLoop = false;
        switch(SelectedState)
        {
        case EAS_Closed:
            AnimName = MinigameRig.IdleClosedAnim;
            bLoop = true;
            break;
        case EAS_Open:
            AnimName = MinigameRig.GameStartAnim;
            break;
        case EAS_Active:
            AnimName = MinigameRig.MinigameAnimData[RigIndex].MinigameAnimations[AnimationIndex];
            bLoop = true;
            break;
        case EAS_Idle:
            AnimName = MinigameRig.IdleOpenAnim;
            break;
        case EAS_Close:
            AnimName = MinigameRig.GameEndAnim;
            break;
        }

        if (AnimName != 'none')
        {
            MinigameRig.SkeletalMeshComponent.PlayAnim(AnimName, , bLoop);
        }        
    }
}

simulated function StartupGame()
{
    local float StartupTime;

    //Disable server optimization for the minigame rig
    MinigameRig.SetTickIsDisabled(false);
    StartupTime = MinigameRig.SkeletalMeshComponent.GetAnimLength(MinigameRig.GameStartAnim);

    if (MinigameRig.SkeletalMeshComponent.AnimTreeTemplate != none && !MinigameRig.SkeletalMeshComponent.Animations.IsA('AnimTree'))
    {
        MinigameRig.SkeletalMeshComponent.SetAnimTreeTemplate(MinigameRig.SkeletalMeshComponent.AnimTreeTemplate);
    }

    if (StartupTime > 0)
    {
        SetTimer(StartupTime, false, 'DelayedStart');
        if (WorldInfo.NetMode != NM_DedicatedServer)
        {
            SwitchAnim(EAS_Open);
        }        
    }
    else
    {
        DelayedStart();
    }

    EndGameState = -1;
    bLocalStarted = true;
}

simulated function DelayedStart()
{
    local int i;
    SwitchAnim(EAS_Active);

    //For each of the targets, reset and set the base to a specified bone.  Gives us clean a target per-bone
    //      without the need to spin up a new actor per-bone every time we start the minigame.
    for (i = 0; i < MinigameTargets.Length; ++i)
    {
        MinigameTargets[i].SetBase(MinigameRig, , MinigameRig.SkeletalMeshComponent, MinigameRig.TargetBones[i]);
        MinigameTargets[i].Reset();
        if (KFMGA_Target(MinigameTargets[i]) != none)
        {
            KFMGA_Target(MinigameTargets[i]).AttachBoneName = MinigameRig.TargetBones[i];
            KFMGA_Target(MinigameTargets[i]).SetActive();
        }
    }
}

/** Called by target when it's been hit by a valid damage source */
function TargetHit(Actor Target, Controller HitInstigator)
{
    if (bGameRunning && MinigameTargets.Find(Target) != INDEX_NONE)
    {
        TargetsRemaining--;

        if (HitInstigator != none && KillerControllers.Find(HitInstigator) == INDEX_NONE)
        {
            KillerControllers.AddItem(HitInstigator);
        }

        if (TargetsRemaining <= 0)
        {
            MinigameComplete(true);
        }
    }
}

/** Called on clients to fixup the object's base after first replication */
simulated function UpdateBase(KFMGA_Target Target)
{
    Target.SetBase(MinigameRig, , MinigameRig.SkeletalMeshComponent, MinigameRig.TargetBones[Target.SpawnerOwnerIndex]);
}

/** Called on rigged games to check whether a target at a specific bone can be played */
simulated event bool CanPlayAkEvent(Actor inOwner, name BoneName)
{
    local KFMGA_Target Target;
    local int i;

    for (i = 0; i < MinigameTargets.Length; ++i)
    {
        Target = KFMGA_Target(MinigameTargets[i]);
        if (Target != none && Target.AttachBoneName == BoneName)
        {
            return Target.IsAlive();
        }
    }

    return false;
}

function MinigameComplete(bool bVictory)
{
    EndGameState = bVictory ? 0 : 1;
    super.MinigameComplete(bVictory);
}

/** Do any cleanup that happens for either victory or defeat */
simulated function FinalizeGame()
{
    local Actor Target;

    super.FinalizeGame();

    AnimationIndex = -1;
    foreach MinigameTargets(Target)
    {
        if (KFMGA_Target(Target) != none)
        {
            KFMGA_Target(Target).SetInactive();
        }
    }

    HandleDelayedShutdown();
}

simulated function HandleDelayedShutdown()
{
    local float DelayTime;

    switch (EndGameState)
    {
    case 0: //victory
        DelayTime = MinigameRig.PostVictoryIdleTime;     
        break;
    case 1: //defeat
        DelayTime = MinigameRig.PostDefeatIdleTime;    
        break;
    }

    if (DelayTime > 0)
    {
        SetTimer(DelayTime, false, 'Finalize');
        if (WorldInfo.NetMode != NM_DedicatedServer)
        {
            SwitchAnim(EAS_Idle);
        }
    }
    else
    {
        Finalize();
    }   
}

simulated function Finalize()
{
    local int i;
    local float ResetTime;

    for (i = 0; i < MinigameTargets.Length; ++i)
    {
        if (KFMGA_Target(MinigameTargets[i]) != none)
        {
            KFMGA_Target(MinigameTargets[i]).Finalize();
        }
    }

    ResetTime = MinigameRig.SkeletalMeshComponent.GetAnimLength(MinigameRig.GameEndAnim);
    if (ResetTime > 0.f)
    {
        SetTimer(ResetTime, false, 'Reset');
    }
    else
    {
        Reset();
    }

    if (WorldInfo.NetMode != NM_DedicatedServer)
    {
        SwitchAnim(EAS_Close);
    }
}

defaultproperties
{
    AnimationIndex = -1

    BlendToTimes=(0.f,0.f,0.f,0.5f,0.f)
}