/*
	--------------------------------------------------------------
	KF_Roulette_Wheel
	--------------------------------------------------------------

	Interactive prop for the 2013 Summer Sideshow map.   Players
	place bets by throwing dosh onto the table.  The game begins
	when there are enough bets on the table.

	Author :  Alex Quick

	--------------------------------------------------------------
*/

class KF_Roulette_Wheel extends Actor
placeable
dependson(KF_Roulette_Bet_Zone);


const NUMPOCKETS = 37;
const POCKETSPACING = 1771.24;      // (65536 /  NumPockets)
const NUMBETSNDS = 3;

var     int                             FinalPocket;

/* The Tags of the Roulette zone volumes should all match this name */
var()   name                            TableName;


/* A struct representing a player who has bets on this table */
struct SPlayerTableInfo
{
	var	PlayerController               BettingPlayer;
	var	int                            InitialBetSum;      // amount this player had on the table as of the first bet.
	var int                            CurrentBetSum;      // amount this player has on the table right this minute.
};

/* Array of all players who have bets placed on this table */
var array<SPlayerTableInfo>             AllPlayers;


/* A struct representing a pocket on the roulette wheel. */
struct SPocketInfo
{
    var byte                            PocketClr;          // 0 = Green, 1=Black , 2 = Red
    var int                             PocketPosition;     // Rotation Roll value for this pocket.  (RUUs)
};

/* An array that stores all the colours associated with the "Pockets on the roulette wheel. */
var     SPocketInfo                     PocketInfo[NUMPOCKETS];

/* Minimum bet that must be on the table to play */
var()   int                             MinBet;

var()   KF_Roulette_Ball                Ball;

var     float                           MaxBallSpin,MaxWheelSpin;

var     float                           LastBallSpeedDecrement,BallSpeedDecrementInterval;

var     array<int>                      WinningNumbers;

/* Array of all the areas on this table that players can bet in */
var     array<KF_Roulette_Bet_Zone>     BetZones;

var     float                           SpinDuration;

/* Percent of SpinDuration that must pass before all betting is closed */
var     float                           BetsClosedTimePct;

var     float                           LastSpinTime;

/* The Roulette wheel is turning and the ball is in motion */
var     bool                            bSpinning;

/* Amount of time after a bet is placed before the wheel starts spinning */
var     float                           SpinCountDown;

var     float                           LastSpinCountDownTime;

/* There are sufficient bets on the table to spin, and the countdown to spin is in progress */
var     bool                            bCountingDownToSpin;

var     bool                            bNotifiedBettingClosed;

/* At least one player left his winnings from a previous spin on the table*/
var     bool                            bLettingItRide;

/*  Sounds ===============================================================*/


/* Sound the roulette wheel makes when its spinning around */
var Sound                               WheelSpinSnd;

var Sound                               PlaceBetSnds[NUMBETSNDS];

var string                              PlaceBetSndsRef[NUMBETSNDS];

var string                              WheelSpinSndRef;

/* This Table is active and open for business */
var  bool                               bActive;

var () const bool                       bStartActive;


replication
{
    reliable if(Role == Role_Authority)
        FinalPocket;
}

function PreBeginPlay()
{
    PreLoadSounds();
}

function PostBeginPlay()
{
    FindBetZones();
    SetActive(bStartActive);
}

function PreLoadSounds()
{
    local int i;

	if ( default.WheelSpinSndRef != "" )
	{
		WheelSpinSnd = sound(DynamicLoadObject(default.WheelSpinSndRef, class'Sound', true));
	}

    for(i = 0 ; i < NUMBETSNDS; i ++)
    {
        if ( default.PlaceBetSndsRef[i] != "" )
        {
            PlaceBetSnds[i] = sound(DynamicLoadObject(default.PlaceBetSndsRef[i], class'Sound', true));
        }
    }
}


/* Cache all the 'pieces' of this table */
function FindBetZones()
{
    local KF_Roulette_Bet_Zone Zone;

    foreach AllActors(class 'KF_Roulette_Bet_Zone', Zone, TableName)
    {
        BetZones[BetZones.length] = Zone;
        Zone.OwningTable = self;
        Zone.PayOutAmount = GetPayoutFor(Zone.ZoneType);
    }
}

/* Returns true if this table is open for betting */
function bool AcceptNewBets()
{
    return bActive && (!bSpinning || Level.TimeSeconds - LastSpinTime < (SpinDuration*BetsClosedTimePct)) ;
}

/* Player placed a bet on this table */
function OnAddBet()
{
    StartCountDown();
    PlaySound(PlaceBetSnds[Rand(NUMBETSNDS)]);
}

/* bet was rejected */
function OnBetRejected()
{
    NotifyBettingClosed();
}

/* Check if there are enough bets on the table to start the countdown to the spin */
function bool StartCountDown(optional bool SuppressNotifications)
{
    if(!bCountingDownToSpin && !bSpinning && CheckMinBet(SuppressNotifications))
    {
        bCountingDownToSpin = true;
        LastSpinCountDownTime = Level.TimeSeconds;
        SetTimer(SpinCountDown,false);

        return true;
    }

    return false;
}

function AbortCountDown()
{
    bLettingItRide = false;
    bCountingDownToSpin = false;
    SetTimer(0.f,false);
}

function Timer()
{
    bCountingDownToSpin = false;
    SpinWheel();
}

/* A player placed a bet somewhere on this table. Add him to the game */
function AddPlayer(PlayerController NewPlayer)
{
    if(!FindPlayer(NewPlayer))
    {
        AllPlayers.length = AllPlayers.length + 1;
        AllPlayers[AllPlayers.length - 1].BettingPlayer = NewPlayer;
    }

    StartCountDown();
}

/* A Player was removed from the game - (busted out or took his winnings off the table) */
function RemovePlayer(PlayerController PlayerToRemove)
{
    local int PlayerIdx;

    if(FindPlayer(PlayerToRemove,PlayerIdx))
    {
        AllPlayers.Remove(PlayerIdx,1);
    }
}

function  bool FindPlayer(PlayerController PlayerToFind, optional out int PlayerIdx)
{
    local int i;

    for(i = 0 ; i < AllPlayers.length ; i ++)
    {
        if(AllPlayers[i].BettingPlayer == PlayerToFind)
        {
            PlayerIdx = i;
            return true;
        }
    }

    return false;
}


/* Begins the wheel animation */
function SpinWheel()
{
    local int i;

    if(Level.TimeSeconds - LastSpinTime > SpinDuration)
    {
        if(WheelSpinSnd != none)
        {
            PlaySound(WheelSpinSnd);
        }

        LastSpinTime = Level.TimeSeconds;
        log("******* SPINNING THE WHEEL ********** ");
        FinalPocket = Rand(arrayCount(PocketInfo));
        log(" The ball landed on : "@FinalPocket@GetPocketClr(FinalPocket));

        bFixedRotationDir = true;
        bRotateToDesired = false;
        RotationRate.Yaw = -MaxWheelSpin ;

        Ball.StartRolling();

        bSpinning = true;

        for(i = 0 ; i < BetZones.length ; i ++)
        {
            BetZones[i].OnWheelSpin();
        }

        if(bLettingItRide)
        {
            NotifyLetItRide();
        }

        UpdatePlayerBetTotals();
    }
}

simulated function int GetCurrentPocket()
{
    return GetPocketAtPosition(RUUToPosition(Ball.Rotation.Yaw - Rotation.Yaw));
}

/* Toggle Table Active or not*/
function SetActive(bool On)
{
    if(Ball != none)
    {
        Ball.bHidden = !On;
    }

    bHidden = !On;
    bActive = On;

    if(!bActive)
    {
        if(bCountingDownToSpin)
        {
            AbortCountDown();
        }

        OnSpinComplete();
    }

}

function Tick(Float DeltaTime)
{
    local int i;

    /* Betting is closed */
    if(!AcceptNewBets() && !bNotifiedBettingClosed)
    {
        bNotifiedBettingClosed = true;
        NotifyBettingClosed();
    }


    if(bCountingDownToSpin)
    {
        /* Someone picked up their winnings */
        if(!CheckMinBet())
        {
            AbortCountDown();
        }

        for(i = 0 ; i < AllPlayers.Length ; i ++)
        {
            if(AllPlayers[i].BettingPlayer != none)
            {
                AllPlayers[i].BettingPlayer.ReceiveLocalizedMessage(class 'Msg_RouletteCountDown',int(SpinCountDown - (Level.TimeSeconds - LastSpinCountDownTime)));
            }
        }
    }

    if(bSpinning)
    {
/*      if(Level.TimeSeconds - LastBallSpeedDecrement > BallSpeedDecrementInterval)
        {
            LastBallSpeedDecrement = Level.TimeSeconds;
            Ball.RotationRate.Yaw = FMax(Ball.RotationRate.Yaw - ((MaxBallSpin * BallSpeedDecrementInterval)/SpinDuration),MaxBallSpin*0.01);
        }
*/


        if(Level.TimeSeconds - LastSpinTime >= SpinDuration )
        {
            OnSpinComplete();
        }
    }
}

function int GetPocketAtPosition(int InPos)
{
    local int i;

    for(i = 0 ; i < NUMPOCKETS ; i ++)
    {
        if(PocketInfo[i].PocketPosition == InPos)
        {
            return i ;
        }
    }

    return -1;
}

/* Converts the wheel position integer to a Unreal Unit Rotation value */

function float PositionToRUU(int Position)
{
    return (Position * PocketSpacing) & 65536;
}

/* Converts a Rotation value to a wheel position integer */
function int RUUToPosition(float RotVal)
{
    return FClamp(Round(RotVal / PocketSpacing),0,NumPockets-1) ;
}

/* Called when the ball has come to a rest on the desired number. */
function OnSpinComplete()
{
    RotationRate.Yaw = 0.f;

    Ball.DesiredRotation.Yaw = PositionToRUU(FinalPocket);
    Ball.StopRolling();

    bFixedRotationDir = false;
    bRotateToDesired = true;
    DesiredRotation.Yaw = 0.f;

    bSpinning = false;
    bNotifiedBettingClosed = false;

    WinningNumbers[WinningNumbers.length] = FinalPocket;
    ProcessBets();
}


static function string GetPocketClr(int Pocket)
{
    local int ClrIdx;
    local string ClrString;

    ClrIdx = default.PocketInfo[Pocket].PocketClr;
    switch(ClrIdx)
    {
        case 0 : ClrString = "Green" ; break;
        case 1 : ClrString = "Black" ; break;
        case 2 : ClrString = "Red" ; break;
    }

    return ClrString;
}

/* Returns true if there are enough bets on the table to spin the wheel */
function bool CheckMinBet(optional bool SuppressNotification)
{
    local int i;
    local int BetTotal;
    local bool EnoughCash;

    for(i = 0 ; i < BetZones.length ; i ++)
    {
        BetTotal += BetZones[i].GetBetTotal();
    }

    EnoughCash = BetTotal >= MinBet;

    if(!EnoughCash && !SuppressNotification)
    {
        NotifyNeedMinBet();
    }

    return EnoughCash;
}

/* Returns the payout ratio for a specific type of Bet */
function float GetPayoutFor(KF_Roulette_Bet_Zone.EBetType Bet)
{
    switch(Bet)
    {
        case BET_Straight :     return 35.f ;  break; // 35 to 1
        Default :               return 1.f;    break; // 1 to 1
    }
}

/* Calculates the winnings for each BetZone and clears bets on Zones which didn't hit */
function ProcessBets()
{
    local int i;

    if(BetZones.length == 0)
    {
        log("WARNING - No BetZones associated with this table. ");
    }

    for(i = 0 ; i < BetZones.length ; i ++)
    {
        if(BetZones[i].ZoneType == BET_Red && IsRed(FinalPocket))            //  Red bet
        {
            log("Red bet Wins on "$FinalPocket);
            BetZones[i].PayOut();
        }
        else if(BetZones[i].ZoneType == BET_Black && IsBlack(FinalPocket))   //  Black bet
        {
            log("Black bet Wins on "$FinalPocket);
            BetZones[i].PayOut();
        }
        else if (BetZones[i].ZoneType == BET_Even && IsEven(FinalPocket))
        {
            log("Even number bet Wins on "$FinalPocket);
            BetZones[i].PayOut();
        }
        else if (BetZones[i].ZoneType == BET_Odd && !IsEven(FinalPocket))
        {
            log("Odd number bet Wins on "$FinalPocket);
            BetZones[i].PayOut();
        }
        else if(BetZones[i].ZoneType == BET_1st && IsFirsts(FinalPocket)) // First 12
        {
            log("Firsts bet Wins on "$FinalPocket);
            BetZones[i].PayOut();
        }
        else if(BetZones[i].ZoneType == BET_2nd && IsSeconds(FinalPocket)) // Second 12
        {
            log("Seconds bet Wins on "$FinalPocket);
            BetZones[i].PayOut();
        }
        else if(BetZones[i].ZoneType == BET_3rd && IsThirds(FinalPocket)) // Third 12
        {
            log("Thirds bet Wins on "$FinalPocket);
            BetZones[i].PayOut();
        }
        else if(BetZones[i].ZoneType == BET_Low && IsLow(FinalPocket)) // Low Bet
        {
            log("Low bet Wins on "$FinalPocket);
            BetZones[i].PayOut();
        }
        else if(BetZones[i].ZoneType == BET_High && IsHigh(FinalPocket)) // High Bet
        {
            log("High bet Wins on "$FinalPocket);
            BetZones[i].PayOut();
        }
        else if(BetZones[i].ZoneType == BET_Straight &&
        FinalPocket == BetZones[i].ZoneNumber)                           // Straight number
        {
            log("Straight Bet Wins on "$FinalPocket);
            BetZones[i].PayOut();
        }
        else
        {
            BetZones[i].ClearOldBets();
        }
    }

    UpdatePlayerBetTotals();
    NotifyWinnings();
    RemoveBustedPlayers();

    /* Let's see if there's enough cash on the table to start another spin ... */
    if(StartCountDown(true))
    {
        bLettingItRide = true;
    }
}

/* Updates the current bet totals for all players on the table */
function UpdatePlayerBetTotals()
{
    local int i, NumPlayers;

    NumPlayers = AllPlayers.length;

    for(i = 0 ; i < NumPlayers ; i ++)
    {
        AllPlayers[i].CurrentbetSum = GetCurrentBetTotalFor(AllPlayers[i].BettingPlayer);
    }
}


/* Returns the total amount of dosh a player has bet on this table at the moment */
function int GetCurrentBetTotalFor( PlayerController Player)
{
    local int i;
    local int idx;
    local int TotalBet;

    for(i = 0; i < BetZones.Length ; i ++)
    {
        for(idx = 0 ; idx < BetZones[i].CurrentBets.length ;idx ++)
        {
            if(BetZones[i].CurrentBets[idx].BettingPlayer == Player)
            {
                TotalBet += BetZones[i].CurrentBets[idx].BetPickup.CashAmount ;
            }
        }
    }

    return TotalBet;
}

/* Remove any players who have no more bets on the table */
function RemoveBustedPlayers()
{
    local int i, NumPlayers;

    NumPlayers = AllPlayers.length;

    for(i = 0 ; i < NumPlayers ; i ++)
    {
        if(AllPlayers[i].CurrentBetSum <= 0)     // this guys went broke, he's not in the game anymore.
        {
            AllPlayers.Remove(i,1);
        }
    }
}


/* == Player Feedback & Localized Messaging ======================================================
==================================================================================================*/

/* Let players know how the last spin went.  Send them a local message with their net winnings */
function NotifyWinnings()
{
    local int i;
    local int NetWinnings;

    for(i = 0 ; i< AllPlayers.length ; i ++)
    {
        if(AllPlayers[i].BettingPlayer != none)
        {
            NetWinnings = AllPlayers[i].CurrentBetSum - AllPlayers[i].InitialBetSum ;

            /* let players know which number it landed on */
            AllPlayers[i].BettingPlayer.ReceiveLocalizedMessage(class 'Msg_RouletteSpin',FinalPocket);

            /* let players know how much they have won so far. */
            AllPlayers[i].BettingPlayer.ReceiveLocalizedMessage(class 'Msg_RouletteWinnings', NetWinnings);
        }
    }
}

/* Let players know that they are letting it ride. */
function NotifyLetitRide()
{
    local int i;

    for(i = 0 ; i< AllPlayers.length ; i ++)
    {
        if(AllPlayers[i].BettingPlayer != none)
        {
            AllPlayers[i].BettingPlayer.ReceiveLocalizedMessage(class 'Msg_RouletteGeneric', 1);
        }
    }
}

/* Let players know that they need to place more cash on the table to play*/
function NotifyNeedMinBet()
{
    local int i;

    for(i = 0 ; i< AllPlayers.length ; i ++)
    {
        if(AllPlayers[i].BettingPlayer != none)
        {
            AllPlayers[i].BettingPlayer.ReceiveLocalizedMessage(class 'Msg_RouletteGeneric', 3);
        }
    }
}

/* Let players know that they cannot place anymore bets right now */
function NotifyBettingClosed()
{
    local int i;

    for(i = 0 ; i< AllPlayers.length ; i ++)
    {
        if(AllPlayers[i].BettingPlayer != none)
        {
            AllPlayers[i].BettingPlayer.ReceiveLocalizedMessage(class 'Msg_RouletteGeneric', 2);
        }
    }
}

/*============================================================================================
=============================================================================================*/


/* Changes the UV2 Material on the Chips so they look glowy to the player who won them
and not glowy to everyone else */

function ClientSetChipMaterial(CashPickup Chips, bool Glow)
{
    local int i;
    local Material NewMat;

    for(i = 0 ; i < AllPlayers.length ; i ++)
    {
        NewMat = none ;

        if(KFPlayerController_Story(AllPlayers[i].BettingPlayer) != none )
        {
            if(Glow && AllPlayers[i].Bettingplayer == Chips.DroppedBy)
            {
                NewMat = Chips.default.UV2Texture ;
            }

            KFPlayerController_Story(AllPlayers[i].BettingPlayer).ClientSetUV2Tex(Chips,NewMat);
        }
    }
}

function bool IsBlack(int Num)
{
    return PocketInfo[Num].PocketClr == 1;
}

function bool IsRed(int Num)
{
    return PocketInfo[Num].PocketClr == 2;
}

function bool IsEven(int Num)
{
    return Num % 2 == 0;
}

function bool IsFirsts(int Num)
{
    return Num <= 12 && Num > 0;
}

function bool IsSeconds(int Num)
{
    return Num > 12 && Num <= 24 ;
}

function bool IsThirds( int Num)
{
    return Num > 24 && Num <= 36;
}

function bool IsLow (int Num)
{
    return Num > 0 && Num <= 18;
}

function bool IsHigh( int Num)
{
    return Num >= 19 && Num <= 36 ;
}


defaultproperties
{
    DrawType = DT_StaticMesh
    StaticMesh = StaticMesh'Pier_SM.Env_Pier_Roulette_Table_Wheel'
    Physics = PHYS_Rotating
	bFixedRotationDir= true
	bCollideActors = true
	RemoteRole = Role_SimulatedProxy

    MaxBallSpin = 190000
    MaxWheelSpin = 10000

    MinBet = 100
    BetsClosedTimePct = 0.5
    SpinDuration = 5
    SpinCountDown = 5
    BallSpeedDecrementInterval = 0.1

    WheelSpinSndRef ="Steamland_SND.Roulette_WheelSpin"
    PlaceBetSndsRef(0)="Steamland_SND.Roulette_StackOff_1"
    PlaceBetSndsRef(1)="Steamland_SND.Roulette_StackOff_2"
    PlaceBetSndsRef(2)="Steamland_SND.Roulette_StackOff_3"

    PocketInfo(0)=(PocketClr=0,PocketPosition=32)
    PocketInfo(1)=(PocketClr=2,PocketPosition=18)
    PocketInfo(2)=(PocketClr=1,Pocketposition=1)
    PocketInfo(3)=(PocketClr=2,Pocketposition=30)
    PocketInfo(4)=(PocketClr=2,Pocketposition=36)
    PocketInfo(5)=(PocketClr=2,PocketPosition=14)
    PocketInfo(6)=(PocketClr=1,PocketPosition=5)
    PocketInfo(7)=(PocketClr=2,PocketPosition=26)
    PocketInfo(8)=(PocketClr=1,PocketPosition=11)
    PocketInfo(9)=(PocketClr=2,PocketPosition=22)
    PocketInfo(10)=(PocketClr=1,PocketPosition=13)
    PocketInfo(11)=(PocketClr=1,PocketPosition=9)
    PocketInfo(12)=(PocketClr=2,PocketPosition=28)
    PocketInfo(13)=(PocketClr=1,PocketPosition=7)
    PocketInfo(14)=(PocketClr=2,PocketPosition=20)
    PocketInfo(15)=(PocketClr=1,Pocketposition=34)
    PocketInfo(16)=PocketClr=2,PocketPosition=16)
    PocketInfo(17)=(PocketClr=1,Pocketposition=3)
    PocketInfo(18)=(PocketClr=2,PocketPosition=24)
    PocketInfo(19)=(PocketClr=2,Pocketposition=35)
    PocketInfo(20)=(PocketClr=1,PocketPosition=19)
    PocketInfo(21)=(PocketClr=2,Pocketposition=0)
    PocketInfo(22)=(PocketClr=1,Pocketposition=23)
    PocketInfo(23)=(PocketClr=2,PocketPosition=12)
    PocketInfo(24)=(PocketClr=1,PocketPosition=15)
    PocketInfo(25)=(PocketClr=2,Pocketposition=2)
    PocketInfo(26)=(PocketClr=1,Pocketposition=31)
    PocketInfo(27)=(PocketClr=2,PocketPosition=6)
    PocketInfo(28)=(PocketClr=1,PocketPosition=27)
    PocketInfo(29)=(PocketClr=1,PocketPosition=25)
    PocketInfo(30)=(PocketClr=2,PocketPosition=10)
    PocketInfo(31)=(PocketClr=1,PocketPosition=21)
    PocketInfo(32)=(PocketClr=2,Pocketposition=33)
    PocketInfo(33)=(PocketClr=1,PocketPosition=17)
    PocketInfo(34)=(PocketClr=2,Pocketposition=4)
    PocketInfo(35)=(PocketClr=1,PocketPosition=29)
    PocketInfo(36)=(PocketClr=2,PocketPosition=8)


}

