//=============================================================================
// KFMG_BloatDunk
//=============================================================================
// Extension of target game with specifics tied to bloat dunk.  Uses a more
//      complicated two rig setup for the wheels, each manually rotated, with
//      independent attachment points on each.  Every x seconds, picks a random
//      light from each rig to enable to be shot.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2016 Tripwire Interactive LLC
// - Dan Weiss
//=============================================================================

class KFMG_BloatDunk extends KFMG_TargetGame;

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

/** Two rigs used for alternately rotating wheels */
var() KFMGA_TargetGame OuterWheel;
var() KFMGA_TargetGame InnerWheel;

/** Dunk tank rig for handling curtain/dunk board */
var() SkeletalMeshActor DunkTankRig;

/** Bloat being dunked */
var() SkeletalMeshActor BloatRig;

/** How often to activate a new set of targets */
var() float TargetActivationDelay;

/** How fast to begin rotation */
var() float StartingRotationRate;

/** How much to increase rotation per-target hit */
var() float PerHitRotationIncrease;

/** How many targets to activate per attempt */
var() int NumActiveTargets;

/** Current rotation rate */
var float CurrentRotationRate;

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

enum eBloatGameState
{
    BGS_Off,
    BGS_On,
    BGS_Victory,
    BGS_Defeat,
};

/** What state the game is currently in */
var repnotify eBloatGameState BloatGameState;

//-------------------
// Animation

/** Standard idle while game is off */
var() name IdleClosedAnim;

/** Standard idle while game is being played */
var() name IdleOpenAnim;

/** Animation played on game start */
var() name OpenAnim;

/** Played on victory to dunk the bloat */
var() name DunkVictoryAnim;

/** Victory close animation */
var() name VictoryCloseAnim;

/** Defeat close animation */
var() name DefeatCloseAnim;

replication
{
    if (bNetInitial)
        OuterWheel, InnerWheel, DunkTankRig, BloatRig;

    if (bNetDirty)
        CurrentRotationRate, BloatGameState;
}

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

    if (VarName == 'bGameRunning')
    {
        if (bGameRunning)
        {
            HandleDelayedStartup();
        }
    }

    if (VarName == 'bGameRunning' || VarName == 'BloatGameState')
    {
        if (!bGameRunning && BloatGameState > BGS_On)
        {
            FinalizeGame();
        }
    }
}

simulated event Tick(float DeltaTime)
{
    local Rotator NewRotation;
    super.Tick(DeltaTime);

    if (bGameRunning)
    {
        NewRotation = OuterWheel.Rotation;
        NewRotation.Pitch = (NewRotation.Pitch + (CurrentRotationRate * DeltaTime)) % 65536;
        OuterWheel.SetRotation(NewRotation);

        NewRotation = InnerWheel.Rotation;
        NewRotation.Pitch = (NewRotation.Pitch - (CurrentRotationRate * DeltaTime)) % 65536;
        InnerWheel.SetRotation(NewRotation);
    }    
}

/** 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;
    super.PostBeginPlay();

    if (InnerWheel != none && OuterWheel != none && TargetArchetype != none)
    {
        foreach InnerWheel.TargetBones(BoneName)
        {
            SpawnTarget(InnerWheel, BoneName);
        }

        foreach OuterWheel.TargetBones(BoneName)
        {
            SpawnTarget(OuterWheel, BoneName);
        }
    }

    Reset();
}

function SpawnTarget(KFMGA_TargetGame Wheel, name BoneName)
{
    local Actor NewTarget;
    local KFMGA_Target RiggedTarget;

    NewTarget = spawn(TargetArchetype.class, self, , , , TargetArchetype);
    if (NewTarget != none)
    {
        MinigameTargets.AddItem(NewTarget);
        NewTarget.SetBase(Wheel, , Wheel.SkeletalMeshComponent, BoneName);

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

//Activate target game.  All authority stuff should be done here.  Generic stuff should be done in StartupGame
function Activated(KFTrigger_MinigameButton ActivationSource)
{
    if (MinigameTargets.Length == (OuterWheel.TargetBones.Length + InnerWheel.TargetBones.Length))
    {
        super.Activated(ActivationSource);

        BloatGameState = BGS_On;
        TargetsRemaining = MinigameTargets.Length;
        HandleDelayedStartup();
    }
}

simulated function Reset()
{
    super.Reset();
    CurrentRotationRate = 0;
    BloatRig.SkeletalMeshComponent.StopAnim();
}

simulated function HandleDelayedStartup()
{
    local float AnimLength;
    local int i;
    local Vector NewLocation;
    local Rotator NewRotation;

    //Disable server optimization for the minigame rig
    InnerWheel.SetTickIsDisabled(false);

    //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 < InnerWheel.TargetBones.Length; ++i)
    {
        MinigameTargets[i].Reset();
        MinigameTargets[i].SetBase(InnerWheel, , InnerWheel.SkeletalMeshComponent, InnerWheel.TargetBones[i]);
    }

    //Disable server optimization for the minigame rig
    OuterWheel.SetTickIsDisabled(false);

    //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 < OuterWheel.TargetBones.Length; ++i)
    {
        MinigameTargets[InnerWheel.TargetBones.Length + i].Reset();
        MinigameTargets[InnerWheel.TargetBones.Length + i].SetBase(OuterWheel, , OuterWheel.SkeletalMeshComponent, OuterWheel.TargetBones[i]);
    }

    if (WorldInfo.NetMode != NM_DedicatedServer && BloatRig != none)
    {
        if (DunkTankRig.SkeletalMeshComponent.GetSocketWorldLocationAndRotation('BloatAttach', NewLocation, NewRotation))
        {
            BloatRig.SetLocation(NewLocation);
            BloatRig.SetRotation(NewRotation);
        }
        BloatRig.SetPhysics(PHYS_None);
        BloatRig.SetBase(DunkTankRig, , DunkTankRig.SkeletalMeshComponent, 'Root');
        BloatRig.SkeletalMeshComponent.PlayAnim('BloatDunk_Idle', , true);
    }    

    //If we have a rig and animation, delay activation of the game startup until after the open animation has been kicked
    //  Otherwise, simply skip to start
    if (DunkTankRig != none)
    {
        AnimLength = DunkTankRig.SkeletalMeshComponent.GetAnimLength(OpenAnim);
        if (AnimLength > 0.f)
        {
            SetTimer(AnimLength, false, 'StartupGame');
            if (WorldInfo.NetMode != NM_DedicatedServer)
            {
                DunkTankRig.SkeletalMeshComponent.PlayAnim(OpenAnim);
            }            
        }
        else
        {
            StartupGame();
        }
    }
    else
    {
        StartupGame();
    }
}

simulated function StartupGame()
{
    if (WorldInfo.NetMode != NM_DedicatedServer)
    {
        DunkTankRig.SkeletalMeshComponent.PlayAnim(IdleOpenAnim, , true);
    }    

    if (Role == ROLE_Authority)
    {
        CurrentRotationRate = StartingRotationRate;
        ActivateTargets();
        SetTimer(TargetActivationDelay, true, 'ActivateTargets');
    }
}

/** Find a random target per wheel and activate it.  Deactivate */
function ActivateTargets()
{
    local int i, Activated;
    local array<Actor> RandLookupList;

    //Deactivate old targets
    for (i = 0; i < MinigameTargets.Length; ++i)
    {
        if (KFMGA_Target(MinigameTargets[i]) != none)
        {
            KFMGA_Target(MinigameTargets[i]).SetInactive();
        }
    }

    //Get two random targets for now
    RandLookupList = MinigameTargets;
    while (RandLookupList.Length > 0 && Activated < NumActiveTargets)
    {
        i = Rand(RandLookupList.Length);
        if (KFMGA_Target(RandLookupList[i]).IsAlive())
        {
            Activated++;
            KFMGA_Target(RandLookupList[i]).SetActive();
        }
        RandLookupList.Remove(i, 1);
    }
}

/** 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--;
        CurrentRotationRate += PerHitRotationIncrease;

        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)
{
    if (Target.SpawnerOwnerIndex < InnerWheel.TargetBones.Length)
    {
        Target.SetBase(InnerWheel, , InnerWheel.SkeletalMeshComponent, InnerWheel.TargetBones[Target.SpawnerOwnerIndex]);
    }
    else
    {
        Target.SetBase(OuterWheel, , OuterWheel.SkeletalMeshComponent, OuterWheel.TargetBones[Target.SpawnerOwnerIndex - InnerWheel.TargetBones.Length]);
    }    
}

function MinigameComplete(bool bVictory)
{
    BloatGameState = bVictory ? BGS_Victory : BGS_Defeat;
    super.MinigameComplete(bVictory);
}

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

    super.FinalizeGame();

    ClearTimer('ActivateTargets');

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

    //Enable server optimization for the minigame rig while it's not running
    InnerWheel.SetTickIsDisabled(false);
    OuterWheel.SetTickIsDisabled(false);

    HandleDelayedShutdown();
}

/** Animation handling for delayed shutdown */
simulated function HandleDelayedShutdown()
{
    local float DelayTime;
    local name DelayedShutdownAnim;

    switch (BloatGameState)
    {
    case BGS_Victory:
        DelayTime = DunkTankRig.SkeletalMeshComponent.GetAnimLength(DunkVictoryAnim);
        DelayedShutdownAnim = DunkVictoryAnim;
        SetTimer(DelayTime * 2.f, false, 'FinalizeVictory');
        break;
    case BGS_Defeat:
        DelayTime = DunkTankRig.SkeletalMeshComponent.GetAnimLength(DefeatCloseAnim);
        DelayedShutdownAnim = DefeatCloseAnim;
        SetTimer(DelayTime, false, 'Reset');
        break;
    }

    if (WorldInfo.NetMode != NM_DedicatedServer)
    {
        DunkTankRig.SkeletalMeshComponent.PlayAnim(DelayedShutdownAnim);
        if (BloatGameState == BGS_Victory)
        {
            SetBloatRagdoll();
        }
    }    
}

simulated function SetBloatRagdoll()
{
    if (BloatRig == none)
    {
        return;
    }

    BloatRig.SkeletalMeshComponent.StopAnim();

    //PrepareRagdoll
    BloatRig.SkeletalMeshComponent.bUpdateSkelWhenNotRendered = true;
    if (BloatRig.SkeletalMeshComponent.bNotUpdatingKinematicDueToDistance)
    {
        BloatRig.SkeletalMeshComponent.ForceSkelUpdate();
        BloatRig.SkeletalMeshComponent.UpdateRBBonesFromSpaceBases(TRUE, TRUE);
        BloatRig.SkeletalMeshComponent.SetBlockRigidBody(TRUE);
    }

    //InitRagdoll
    BloatRig.SkeletalMeshComponent.PhysicsWeight = 1.0f;
    BloatRig.SkeletalMeshComponent.SetHasPhysicsAssetInstance(true);
    BloatRig.SetPhysics(PHYS_RigidBody);
    BloatRig.SkeletalMeshComponent.PhysicsAssetInstance.SetAllBodiesFixed(false);
    BloatRig.SkeletalMeshComponent.WakeRigidBody();

    //PlayRagdollDeath
    BloatRig.SkeletalMeshComponent.SetActorCollision(TRUE, FALSE);
    BloatRig.SkeletalMeshComponent.bUpdateJointsFromAnimation = FALSE;
    BloatRig.SkeletalMeshComponent.SetRBChannel(RBCC_KnockedDownPawn);
    BloatRig.SkeletalMeshComponent.SetRBCollidesWithChannel(RBCC_KnockedDownPawn, TRUE);
    BloatRig.SkeletalMeshComponent.SetRBCollidesWithChannel(RBCC_Pawn, FALSE);
    BloatRig.SkeletalMeshComponent.SetRBCollidesWithChannel(RBCC_DeadPawn, FALSE);
}

simulated function FinalizeVictory()
{
    local float ResetTime;
    
    ResetTime = DunkTankRig.SkeletalMeshComponent.GetAnimLength(VictoryCloseAnim);
    if (ResetTime > 0.f)
    {
        SetTimer(ResetTime, false, 'Reset');
    }
    else
    {
        Reset();
    }

    if (WorldInfo.NetMode != NM_DedicatedServer)
    {
        DunkTankRig.SkeletalMeshComponent.PlayAnim(VictoryCloseAnim);
    }
}

defaultproperties
{
    TargetActivationDelay = 2.f

    StartingRotationRate = 500
    PerHitRotationIncrease = 100
    NumActiveTargets = 2

    BloatGameState = BGS_Off

    //-------------------
    // Animation
    IdleClosedAnim = Idle_Close
    IdleOpenAnim = Idle_Open
    OpenAnim = Open
    DunkVictoryAnim = Dunk
    VictoryCloseAnim = Close_Win
    DefeatCloseAnim = Close_lost
}