//----------------------------------------------------------------------------
// File: mazecient.cpp
//
// Desc: see main.cpp
//
// Copyright (c) 1999-2001 Microsoft Corp. All rights reserved.
//-----------------------------------------------------------------------------
#define STRICT
#define D3D_OVERLOADS
#include <windows.h>
#include <d3dx.h>
#include <stdio.h>
#include <math.h>
#include <dplay8.h>
#include <dpaddr.h>
#include <dxerr9.h>
#include "DXUtil.h"
#include "SyncObjects.h"
#include "IMazeGraphics.h"
#include "DummyConnector.h"
#include "MazeApp.h"
#include "MazeClient.h"
#include "Packets.h"
#include "DXUtil.h"




//-----------------------------------------------------------------------------
// Defines
//-----------------------------------------------------------------------------
#define NORTH_ANGLE 0x8000
#define EAST_ANGLE  0xc000
#define SOUTH_ANGLE 0x0000
#define WEST_ANGLE  0x4000




//-----------------------------------------------------------------------------
// Name: CMazeClient
// Desc: Constructor
//-----------------------------------------------------------------------------
CMazeClient::CMazeClient()
{
    ZeroMemory( m_pctCells, sizeof(m_pctCells) );
    m_hReady            = CreateEvent( NULL, TRUE, FALSE, NULL );
    m_hGotFirstConfig   = CreateEvent( NULL, TRUE, FALSE, NULL );
    m_pNet              = NULL;
    m_bAutopilot        = FALSE;
    m_bEngageAutopilot  = TRUE;
    m_dwNumNearbyPlayers = 0;

    m_NetConfig.ubReliableRate = 0;
    m_NetConfig.wUpdateRate    = 150;
    m_NetConfig.wTimeout       = 150;
    m_NetConfig.dwMazeWidth    = 0;
    m_NetConfig.dwMazeHeight   = 0;

    m_NetConfig.dwThreadWait = 0;

    m_NetConfig.ubClientPackIndex = 0;
    m_NetConfig.ubServerPackIndex = 0;
    for(WORD x = 0; x < PACK_ARRAY_SIZE; x++)
    {
        m_NetConfig.wClientPackSizeArray[x]  = 0;
        m_NetConfig.wServerPackSizeArray[x]  = 0;
    }

}




//-----------------------------------------------------------------------------
// Name: ~CMazeClient
// Desc: Destructor
//-----------------------------------------------------------------------------
CMazeClient::~CMazeClient()
{
    CloseHandle( m_hGotFirstConfig );
    CloseHandle( m_hReady );
}




//-----------------------------------------------------------------------------
// Name: Init
// Desc: Initialize member variables
//-----------------------------------------------------------------------------
HRESULT CMazeClient::Init( CMazeApp* pMazeApp, IMazeGraphics* pMazeGraphics )
{
    m_pMazeApp              = pMazeApp;
    m_pMazeGraphics         = pMazeGraphics;
    m_aCameraYaw                  = 0;
    m_fLastOutboundTime     = DXUtil_Timer( TIMER_GETAPPTIME );
    m_bHaveInputFocus       = TRUE;

    Reset();

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: Reset
// Desc: Reset the client state
//-----------------------------------------------------------------------------
HRESULT CMazeClient::Reset()
{
    m_dwNumPlayers = 0;
    m_bAutopilot   = FALSE;

    SetFirstConfig( FALSE );
    SetMazeReady( FALSE ); 

    ZeroMemory( m_pctCells, sizeof(m_pctCells) );

    PlayerObject* pPlayerObject = m_PlayerObjects;
    for( DWORD i = 0; i < MAX_PLAYER_OBJECTS; i++, pPlayerObject++ )
    {
        pPlayerObject->dwID = 0;
        pPlayerObject->wCellX = pPlayerObject->wCellY = 0xffff;
        pPlayerObject->pNext = NULL;
    }

    return S_OK;
}





//-----------------------------------------------------------------------------
// Name: Shutdown
// Desc: Reset client state and destroy the maze
//-----------------------------------------------------------------------------
void CMazeClient::Shutdown()
{
    // Destroy the maze

    Reset();
    m_Maze.Empty();
}




//-----------------------------------------------------------------------------
// Name: Update
// Desc: Update the client position based on elapsed time since last update
//-----------------------------------------------------------------------------
void CMazeClient::Update( FLOAT fElapsed )
{
    // Don't do anyPlayerObject until we get a server config packet
    if( !IsMazeReady() ) 
        return;

    if( m_bAutopilot )
        DoAutopilot( fElapsed );
    else
        DoManualPilot( fElapsed );

    // See if it's time to send a packet to the server with our updated coordinates
    FLOAT fCurTime = DXUtil_Timer( TIMER_GETAPPTIME );
    if( (fCurTime - m_fLastOutboundTime)*1000.0f > m_NetConfig.wUpdateRate )
    {

        ClientPosPacket packet( m_vCameraPos.x, m_vCameraPos.z, 
                                m_fCameraVelPos, m_aCameraYaw,   
                                m_nCameraVelYaw );

        // Pack the buffer with dummy data for testing.
        if(m_NetConfig.wClientPackSizeArray[m_NetConfig.ubClientPackIndex] > 0)
        {
            WORD    wBufferSize = sizeof(packet) + m_NetConfig.wClientPackSizeArray[m_NetConfig.ubClientPackIndex];
            VOID*   pTempBuffer = 0;

            pTempBuffer = malloc(wBufferSize);
            if( NULL == pTempBuffer )
            {
                //Out of memory, just bail
                DXTRACE_ERR( TEXT("System out of Memory!"), E_OUTOFMEMORY );
                return;
            }

            
            FillMemory(pTempBuffer, wBufferSize, 'Z');
            memcpy(pTempBuffer, &packet, sizeof(packet));

            SendPacket( (ClientPacket*)pTempBuffer, wBufferSize, FALSE, m_NetConfig.wTimeout );
        
            free(pTempBuffer);
        }   
        else
        {
            SendPacket( &packet, sizeof(packet), FALSE, m_NetConfig.wTimeout );
        }

        m_fLastOutboundTime = fCurTime;

    }
}




//-----------------------------------------------------------------------------
// Name: DoManualPilot
// Desc: Update position based on user input
//-----------------------------------------------------------------------------
void CMazeClient::DoManualPilot( FLOAT fElapsed )
{

    // Initialize velocities
    m_fCameraVelPos = 0.0f;
    m_nCameraVelYaw = 0;

    // Check if we have the input focus
    if( !m_bHaveInputFocus )
        return;

    float e = fElapsed*1000.0f;
    D3DXVECTOR3 pos = m_vCameraPos;

    // Do rotation and position calculations based on keyboard input
    // The if else structure guarantees that the tank won't try to 
    // turn both directions at once or turn while driving forward, both
    // of which are impossible in a tank
    if( GetAsyncKeyState( VK_LEFT ) & 0x8000 )
    {
        m_aCameraYaw += (DWORD) ((fElapsed*1000.0f) * 40.0f);
        m_nCameraVelYaw = 40000; // Velocity constant
    }
    else if( GetAsyncKeyState( VK_RIGHT ) & 0x8000 )
    {
        m_aCameraYaw -= (DWORD) ((fElapsed*1000.0f) * 40.0f);
        m_nCameraVelYaw = -40000; // Velocity constant
    }
    else if( GetAsyncKeyState( VK_UP ) & 0x8000 )
    {
        pos.x -= Sin(m_aCameraYaw) * 0.002f * e;
        pos.z += Cos(m_aCameraYaw) * 0.002f * e;
        m_fCameraVelPos = 2.0f; // Velocity constant
    }
    else if( GetAsyncKeyState( VK_DOWN ) & 0x8000 )
    {
        pos.x += Sin(m_aCameraYaw) * 0.002f * e;
        pos.z -= Cos(m_aCameraYaw) * 0.002f * e;
        m_fCameraVelPos = -2.0f; // Velocity constant
    }

    // Ensure that we have stayed within the maze boundaries
    if( pos.x < 0 ) pos.x = 0.1f;
    if( pos.x >= m_Maze.GetWidth() ) pos.x = m_Maze.GetWidth() - 0.1f;
    if( pos.z < 0 ) pos.z = 0.1f;
    if( pos.z >= m_Maze.GetHeight() ) pos.z = m_Maze.GetHeight() - 0.1f;
            
    m_vCameraPos = pos;    
}




//-----------------------------------------------------------------------------
// Name: DoAuotpilot
// Desc: Update position based on simple AI algorithm
//-----------------------------------------------------------------------------
void CMazeClient::DoAutopilot( FLOAT fElapsed )
{
    // While there is still time to use up...
    while( fElapsed )
    {
        // Initialize velocities
        m_fCameraVelPos = 0.0f;
        m_nCameraVelYaw = 0;

        // See if we need to turn
        if( m_aAutopilotTargetAngle != m_aCameraYaw )
        {
            SHORT diff = SHORT((m_aAutopilotTargetAngle - m_aCameraYaw)&TRIG_ANGLE_MASK);
            FLOAT fNeeded = abs(diff)/40.0f;
            if( fNeeded/1000.0f <= fElapsed )
            {
                m_aCameraYaw = m_aAutopilotTargetAngle;
                fElapsed -= fNeeded/1000.0f;
            }
            else
            {
                if( diff < 0 )
                {
                    m_aCameraYaw -= (DWORD) ((fElapsed*1000.0f) * 40.0f);
                    m_nCameraVelYaw = -40000; // Constant auto pilot rate
                }
                else
                {
                    m_aCameraYaw += (DWORD) ((fElapsed*1000.0f) * 40.0f);
                    m_nCameraVelYaw = 40000; // Constant auto pilot rate
                }

                fElapsed = 0;
            }
        }
        else
        {
            // Ensure vAutopilotTarget is inside the maze boundry
            if( m_vAutopilotTarget.x < 0                  || 
                m_vAutopilotTarget.x >= m_Maze.GetWidth() ||
                m_vAutopilotTarget.z < 0                  ||
                m_vAutopilotTarget.z >= m_Maze.GetHeight() )
            {
                ZeroMemory( m_AutopilotVisited, sizeof(m_AutopilotVisited) );
                m_AutopilotStack.Empty();
                PickAutopilotTarget();
                return;
            }
            
            // Facing right way, so now compute distance to target
            D3DXVECTOR3 diff = m_vAutopilotTarget - m_vCameraPos;

            float fRange = float(sqrt((diff.x*diff.x)+(diff.z*diff.z)));

            // Are we there yet?
            if( fRange > 0 )
            {
                // No, so compute how long we'd need
                FLOAT fNeeded = fRange / 0.002f;

                //Ensure we never leave the boundary of the Maze.
                D3DXVECTOR3 pos = m_vCameraPos;
                
                // Do we have enough time this frame?
                if( fNeeded/1000.0f <= fElapsed )
                {

                    // Yes, so just snap us there
                    pos.x = m_vAutopilotTarget.x;
                    pos.z = m_vAutopilotTarget.z;
                    
                    fElapsed -= fNeeded/1000.0f;
                }
                else
                {
                    // No, so move us as far as we can
                    pos.x -= Sin(m_aCameraYaw) * 0.002f * fElapsed*1000.0f;
                    pos.z += Cos(m_aCameraYaw) * 0.002f * fElapsed*1000.0f;
                    m_fCameraVelPos = 2.0f; // Velocity constant

                    fElapsed = 0;
                }

                // Ensure that we have stayed within the maze boundaries
                if( pos.x < 0 ) pos.x = 0.1f;
                if( pos.x >= m_Maze.GetWidth() ) pos.x = m_Maze.GetWidth() - 0.1f;
                if( pos.z < 0 ) pos.z = 0.1f;
                if( pos.z >= m_Maze.GetHeight() ) pos.z = m_Maze.GetHeight() - 0.1f;
            
                // Assign our new values back to our globals.
                m_vCameraPos = pos;

            }
            else
            {
                // Reached target, so pick another
                PickAutopilotTarget();
            }
        }
    }
}




//-----------------------------------------------------------------------------
// Name: EngageAutopilot
// Desc: Toggle autopilot control
//-----------------------------------------------------------------------------
void CMazeClient::EngageAutopilot( BOOL bEngage )
{
    m_bEngageAutopilot = bEngage;

    if( !IsMazeReady() ) 
        return;

    BOOL bPrevious = m_bAutopilot;
    m_bAutopilot = bEngage;

    // If we weren't on autopilot before and are are autopilot now then need to init autopilot
    if( m_bAutopilot && !bPrevious )
    {
        // First of all, snap us to the centre of the current cell
        int cellx = int(m_vCameraPos.x);
        int cellz = int(m_vCameraPos.z);
        m_vCameraPos.x = cellx + 0.5f;
        m_vCameraPos.z = cellz + 0.5f;

        // Ensure we're within the maze boundaries
        if( cellx < 0 ) m_vCameraPos.x = 0.5f;
        if( cellx >= int(m_Maze.GetWidth()) ) m_vCameraPos.x = m_Maze.GetWidth() - 0.5f;
        if( cellz < 0 ) m_vCameraPos.z = 0.5f;
        if( cellz >= int(m_Maze.GetHeight()) ) m_vCameraPos.z = m_Maze.GetHeight() - 0.5f;

        // Clear the visited array and stack
        ZeroMemory( m_AutopilotVisited, sizeof(m_AutopilotVisited) );
        m_AutopilotStack.Empty();

        // Pick the next target cell
        PickAutopilotTarget();
    }
}




//-----------------------------------------------------------------------------
// Name: PickAutopilotTarget
// Desc: Determine the next target point
//-----------------------------------------------------------------------------
void CMazeClient::PickAutopilotTarget()
{
    // Get current cell and mark as visited
    DWORD currentx = DWORD(m_vCameraPos.x);
    DWORD currentz = DWORD(m_vCameraPos.z);
    m_AutopilotVisited[currentz][currentx] = 1;

    // Figure out which directions are allowed. We're allowed to go in any direction
    // where there isn't a wall in the way and that takes us to a cell we've visited before.
    BYTE cell = m_Maze.GetCell(currentx,currentz);
    ANGLE alloweddirs[5];
    DWORD dwAllowed = 0;

    if( !(cell & MAZE_WALL_NORTH) && !m_AutopilotVisited[currentz-1][currentx] )
        alloweddirs[dwAllowed++] = NORTH_ANGLE;
    if( !(cell & MAZE_WALL_WEST) && !m_AutopilotVisited[currentz][currentx-1] )
        alloweddirs[dwAllowed++] = WEST_ANGLE;
    if( !(cell & MAZE_WALL_EAST) && !m_AutopilotVisited[currentz][currentx+1] )
        alloweddirs[dwAllowed++] = EAST_ANGLE;
    if( !(cell & MAZE_WALL_SOUTH) && !m_AutopilotVisited[currentz+1][currentx] )
        alloweddirs[dwAllowed++] = SOUTH_ANGLE;
/*
    printf( "Walls: ") );
    if( (cell & MAZE_WALL_NORTH) )
        printf( "N ") );
    if( (cell & MAZE_WALL_WEST) )
        printf( "W ") );
    if( (cell & MAZE_WALL_EAST) )
        printf( "E ") );
    if( (cell & MAZE_WALL_SOUTH) )
        printf( "S ") );
    printf( "\n") );
*/

    // Is there anywhere to go?
    if( dwAllowed == 0 )
    {
        // Nope. Can we backtrack?
        if( m_AutopilotStack.GetCount() > 0 )
        {
            // Yes, so pop cell off the stack
            AutopilotCell autoCell(m_AutopilotStack.Pop());
            m_vAutopilotTarget.x = float(autoCell.x) + 0.5f;
            m_vAutopilotTarget.z = float(autoCell.y) + 0.5f;

            if( autoCell.x < currentx )
                m_aAutopilotTargetAngle = WEST_ANGLE;
            else if( autoCell.x > currentx )
                m_aAutopilotTargetAngle = EAST_ANGLE;
            else if( autoCell.y > currentz )
                m_aAutopilotTargetAngle = SOUTH_ANGLE;
            else
                m_aAutopilotTargetAngle = NORTH_ANGLE;
        }
        else
        {
            // No, so we have explored entire maze and must start again
            ZeroMemory( m_AutopilotVisited, sizeof(m_AutopilotVisited) );
            m_AutopilotStack.Empty();
            PickAutopilotTarget();
        }
    }
    else
    {
        // See if we can continue in current direction
        BOOL bPossible = FALSE;
        for( DWORD i = 0; i < dwAllowed; i++ )
        {
            if( alloweddirs[i] == m_aCameraYaw )
            {
                bPossible = TRUE;
                break;
            }
        }

        // If it's allowed to go forward, then have 1 in 2 chance of doing that anyway, otherwise pick randomly from
        // available alternatives
        if( bPossible && (rand() & 0x1000) )
            m_aAutopilotTargetAngle = m_aCameraYaw;
        else
            m_aAutopilotTargetAngle = alloweddirs[ (rand() % (dwAllowed<<3) ) >>3 ];

        m_vAutopilotTarget.z = float(currentz) + 0.5f;
        m_vAutopilotTarget.x = float(currentx) + 0.5f;

        switch( m_aAutopilotTargetAngle )
        {
            case SOUTH_ANGLE:
                m_vAutopilotTarget.z += 1.0f;
                break;

            case WEST_ANGLE:
                m_vAutopilotTarget.x -= 1.0f;
                break;

            case EAST_ANGLE:
                m_vAutopilotTarget.x += 1.0f;
                break;

            case NORTH_ANGLE:
                m_vAutopilotTarget.z -= 1.0f;
                break;
        }

        // Push current cell onto stack
        m_AutopilotStack.Push( AutopilotCell(BYTE(currentx),BYTE(currentz)) );
    }
}




//-----------------------------------------------------------------------------
// Name: OnPacket
// Desc: Receive handler
//-----------------------------------------------------------------------------
HRESULT CMazeClient::OnPacket( DWORD dwFrom, void* dwData, DWORD dwSize )
{
    HRESULT hr  = DPN_OK;
    
    BOOL    fFoundSize = FALSE;
    DWORD   dwReqSize = 0;
    DWORD   dwSRand = 0;
    
    // Validate incoming data: A malicious user could modify or create an application
    // to send bogus information; to help guard against logical errors and denial 
    // of service attacks, the size of incoming data should be checked against what
    // is expected.
    if( dwSize < sizeof(ServerPacket) )
    {
        m_pMazeApp->SetDisconnectNow( TRUE );
        m_pMazeApp->SetOutOfDateClient( TRUE );
        m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Disconnected because MazeClient is out of date.") );
        m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Please get updated version") );
        m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("from http://msdn.microsoft.com/directx/") );
        return S_OK;
    }

    ServerPacket* pPacket = (ServerPacket*)dwData;
    switch( pPacket->wType )
    {
        case PACKETTYPE_SERVER_CONFIG:
        {
            if( dwSize != sizeof(ServerConfigPacket) )
            {
                m_pMazeApp->SetDisconnectNow( TRUE );
                m_pMazeApp->SetOutOfDateClient( TRUE );
                m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Disconnected because MazeClient is out of date.") );
                m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Please get updated version") );
                m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("from http://msdn.microsoft.com/directx/") );
                break;
            }

            m_NetConfig = ((ServerConfigPacket*)pPacket)->Config;

            m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Got MazeServer config settings") );
            m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Maze Size=(%d,%d) ReliableRate=%d%%"), 
                                       m_NetConfig.dwMazeWidth, m_NetConfig.dwMazeHeight, 
                                       DWORD(m_NetConfig.ubReliableRate) );
            m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("UpdateRate=%dms Timeout=%d"), 
                                       m_NetConfig.wUpdateRate, m_NetConfig.wTimeout );
            m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("ThreadWait=%dms"), 
                                       m_NetConfig.dwThreadWait );
            m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("ClientPackSize=%d ServerPackSize=%d (bytes)"), 
                                       m_NetConfig.wClientPackSizeArray[m_NetConfig.ubClientPackIndex],
                                       m_NetConfig.wServerPackSizeArray[m_NetConfig.ubServerPackIndex]);
            
            // See if we have gotten our fist config. If not, send version info.
            if(! GotFirstConfig()) //If first time in maze.
            {
                // The client expects the server to send a ServerConfigPacket packet first, 
                // then the client sends a ClientVersionPacket, and then the server sends a 
                // ServerAckVersionPacket packet and the game begins
                ClientVersionPacket packet( MAZE_CLIENT_VERSION );
                SendPacket( &packet, sizeof(packet), TRUE, 0 );

                SetFirstConfig( TRUE );
            }

            break;
        }

        case PACKETTYPE_SERVER_ACKVERSION:
        {
            if( dwSize != sizeof(ServerAckVersionPacket) )
            {
                m_pMazeApp->SetDisconnectNow( TRUE );
                m_pMazeApp->SetOutOfDateClient( TRUE );
                m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Disconnected because MazeClient is out of date.") );
                m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Please get updated version") );
                m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("from http://msdn.microsoft.com/directx/") );
                break;
            }

            ServerAckVersionPacket* pAckVersionPacket = (ServerAckVersionPacket*)pPacket;

            // Record the dpnid that the server uses for to talk to us. 
            // This is just done so that we can record this number in the 
            // logs to help match server side logs with client side logs.
            m_dwLocalClientID = pAckVersionPacket->dwClientID;

            m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Server assigned ID: 0x%0.8x"), m_dwLocalClientID );
            m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Server accepted client version") );


            hr = m_Maze.Init( m_NetConfig.dwMazeWidth, 
                              m_NetConfig.dwMazeHeight, DEFAULT_SEED );
            if( FAILED(hr) )
                DXTRACE_ERR_MSGBOX( TEXT("Init"), hr );

            //Seed the random number generator.
            dwSRand = (DWORD) (DXUtil_Timer( TIMER_GETABSOLUTETIME ) * (DWORD)GetCurrentThreadId() );
            srand( dwSRand );

            // Set random start location
            m_vCameraPos = D3DXVECTOR3( rand() % m_Maze.GetWidth() + 0.5f, 0.5, 
                                        rand() % m_Maze.GetHeight() + 0.5f );

            SetMazeReady( TRUE );
            EngageAutopilot( TRUE );
            break;
        }

        case PACKETTYPE_SERVER_ACKPOS:
            
            //Make sure we at least have a ServerAckPacket.
            if( dwSize < sizeof(ServerAckPacket) )
            {
                fFoundSize = FALSE;
            }
            else 
            {   
                //Size of our required packet. Does not include custom pack data.
                dwReqSize = (sizeof(ServerAckPacket) + 
                             (sizeof(PlayerStatePacket) *
                             ((ServerAckPacket*)pPacket)->wPlayerStatePacketCount));
                
                //Check to see if we have a valid packet size.
                if (dwSize < dwReqSize)
                    fFoundSize = FALSE;
                else if ( !IsValidPackSize( dwSize - dwReqSize ))
                    fFoundSize = FALSE;
                else
                    fFoundSize = TRUE;
            }
            
            //If we did not find a correct packet size. Exit.
            if( !fFoundSize )
            {
                m_pMazeApp->SetDisconnectNow( TRUE );
                m_pMazeApp->SetOutOfDateClient( TRUE );
                m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Disconnected because MazeClient is out of date.") );
                m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Please get updated version") );
                m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("from http://msdn.microsoft.com/directx/") );
                break;
            }

            //Found correct packet size. Update clients.
            SetPlayerStats( ((ServerAckPacket*)pPacket)->wPlayerCount,
                            ((ServerAckPacket*)pPacket)->wPlayerStatePacketCount );

            if( ((ServerAckPacket*)pPacket)->wPlayerStatePacketCount )
                HandlePlayerObjectsInAckPacket( (ServerAckPacket*)pPacket );
            break;

        default:
            m_pMazeApp->ConsolePrintf( LINE_LOG, TEXT("Received unknown %d byte packet from server"), dwSize );
            break;
    };

    
    //If the server has given us a custom wait time, Let's sleep for that amount of time.
    if( m_NetConfig.dwThreadWait > 0 )
    {
        Sleep( m_NetConfig.dwThreadWait );
    }
          
        
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: IsValidPackSize
// Desc: Validate the incoming packet size
//-----------------------------------------------------------------------------
BOOL CMazeClient::IsValidPackSize( DWORD dwSize )
{
    BOOL fFoundSize = FALSE;
    BYTE ubPackLocation = m_NetConfig.ubServerPackIndex;

    // Look for valid Client pack size in PackArray.
    // If found, return TRUE, otherwise FALSE.
    if(dwSize != m_NetConfig.wServerPackSizeArray[ubPackLocation])
    {
        for( --ubPackLocation; ubPackLocation != m_NetConfig.ubServerPackIndex; ubPackLocation--)
        {
            if(dwSize == m_NetConfig.wServerPackSizeArray[ubPackLocation])
            {
                fFoundSize = TRUE;
                break;
            }
            
            if(ubPackLocation >= PACK_ARRAY_SIZE)  ubPackLocation = PACK_ARRAY_SIZE;  //Wrap the array.
        }
    }
    else
    {
        fFoundSize = TRUE;
    }
    
    return fFoundSize;

}

//-----------------------------------------------------------------------------
// Name: OnSessionLost
// Desc: Disconnection handler
//-----------------------------------------------------------------------------
void CMazeClient::OnSessionLost( DWORD dwReason )
{
}



//-----------------------------------------------------------------------------
// Name: SendPacket
// Desc: Send the given data to the server
//-----------------------------------------------------------------------------
void CMazeClient::SendPacket( ClientPacket* pPacket, DWORD dwSize, 
                              BOOL bGuaranteed, DWORD dwTimeout )
{
    if( m_NetConfig.ubReliableRate > m_NetRandom.Get( 100 ) )
        bGuaranteed = TRUE;

    m_pNet->SendPacket( pPacket, dwSize, bGuaranteed, dwTimeout );
};




//-----------------------------------------------------------------------------
// Name: GetRoundTripLatencyMS
// Desc: Returns the current round trip latency in milliseconds
//-----------------------------------------------------------------------------
DWORD CMazeClient::GetRoundTripLatencyMS()
{
    return m_pNet->GetRoundTripLatencyMS();
}




//-----------------------------------------------------------------------------
// Name: GetThroughputBPS
// Desc: Returns the current throughput in bytes per second
//-----------------------------------------------------------------------------
DWORD CMazeClient::GetThroughputBPS()
{
    return m_pNet->GetThroughputBPS();
}



//-----------------------------------------------------------------------------
// Name: HandlePlayerObjectsInAckPacket
// Desc: Handles the player objects embedded in the acknowledgement packet
//-----------------------------------------------------------------------------
void CMazeClient::HandlePlayerObjectsInAckPacket( ServerAckPacket* pPacket )
{
    PlayerStatePacket* pClientInfo = (PlayerStatePacket*)(pPacket+1);

    // Lock the world database
    LockWorld();

    if( !IsMazeReady() )
    {
        UnlockWorld();
        return;
    }

    // Loop though the PlayerObject chunks
    for( DWORD count = pPacket->wPlayerStatePacketCount; count; count--, pClientInfo++ )
    {
        // Get the PlayerObject we think this is
        PlayerObject* pPlayerObject = &m_PlayerObjects[pClientInfo->dwID & PLAYER_OBJECT_SLOT_MASK];

        // Does the ID match the one we have?
        if( pPlayerObject->dwID != pClientInfo->dwID )
        {
            // No, so the PlayerObject we have needs to be deleted (server reused the same slot
            // number, so old PlayerObject must be toast)
            RemovePlayerObjectFromCells( pPlayerObject );

            // Set the ID to the new ID
            pPlayerObject->dwID = pClientInfo->dwID;
            pPlayerObject->wCellX = WORD(pClientInfo->fX);
            pPlayerObject->wCellY = WORD(pClientInfo->fY);

            // Insert into the appropriate cell list
            AddPlayerObjectToCells( pPlayerObject );
        }
        else
        {
            // Yes, compute the new cell coordinates
            DWORD newcellx = DWORD(pClientInfo->fX);
            DWORD newcelly = DWORD(pClientInfo->fY);

            // Are they the same as the ones we already have?
            if( newcellx != pPlayerObject->wCellX || newcelly != pPlayerObject->wCellY )
            {
                // No, so need to remove from old cell and add to new one
                RemovePlayerObjectFromCells( pPlayerObject );
                pPlayerObject->wCellX = WORD(newcellx);
                pPlayerObject->wCellY = WORD(newcelly);
                AddPlayerObjectToCells( pPlayerObject );
            }
        }

        // Update timestamp and position
        pPlayerObject->vPos.x     = pClientInfo->fX;    
        pPlayerObject->vVelPos.x  = pClientInfo->fVelPos * (FLOAT) -sin( AngleToFloat(pClientInfo->aYaw) );
        pPlayerObject->vPos.y     = 0.5f;               
        pPlayerObject->vVelPos.y  = 0.0f;
        pPlayerObject->vPos.z     = pClientInfo->fY;    
        pPlayerObject->vVelPos.z  = pClientInfo->fVelPos * (FLOAT)  cos( AngleToFloat(pClientInfo->aYaw) );
        
        pPlayerObject->aYaw       = pClientInfo->aYaw;
        pPlayerObject->nVelYaw    = pClientInfo->nVelYaw;
        pPlayerObject->fLastValidTime = DXUtil_Timer( TIMER_GETAPPTIME );
    }

    // Unlock world database
    UnlockWorld();
}




//-----------------------------------------------------------------------------
// Name: AddPlayerObjectToCells
// Desc: Add the given player object to the stored cell objects
//-----------------------------------------------------------------------------
void CMazeClient::AddPlayerObjectToCells( PlayerObject* pPlayerObject )
{
    if( pPlayerObject->wCellX == 0xffff )
        return;

    if( FALSE == IsPlayerObjectInCell( pPlayerObject->wCellX, pPlayerObject->wCellY, pPlayerObject ) )
    {
        PlayerObject** ppCell = &m_pctCells[pPlayerObject->wCellY][pPlayerObject->wCellX];
        pPlayerObject->pNext = *ppCell;
        *ppCell = pPlayerObject;
    }
}




//-----------------------------------------------------------------------------
// Name: RemovePlayerObjectFromCells
// Desc: Remove the given player from its presence in any of the stored cells
//-----------------------------------------------------------------------------
void CMazeClient::RemovePlayerObjectFromCells( PlayerObject* pPlayerObject )
{
    if( pPlayerObject->wCellX == 0xffff )
        return;

    PlayerObject** ppCell   = &m_pctCells[pPlayerObject->wCellY][pPlayerObject->wCellX];
    PlayerObject* pCur      = *ppCell;
    PlayerObject* pPrev     = NULL;
    while( pCur )
    {
        if( pCur == pPlayerObject )
        {
            pCur = pPlayerObject->pNext;

            // Found pPlayerObject, so remove pPlayerObject from the m_pctCells linked list
            if( pPrev )
                pPrev->pNext = pPlayerObject->pNext;
            else
                *ppCell = pPlayerObject->pNext;
            pPlayerObject->pNext = NULL;

            // Update pPlayerObject so that it is marked as removed
            pPlayerObject->wCellX = pPlayerObject->wCellY = 0xffff;

            // Continue searching, and remove any other instances of 
            // pPlayerObject from list (there shouldn't be, however)
        }
        else
        {
            pPrev = pCur;
            pCur  = pCur->pNext;
        }
    }
}




//-----------------------------------------------------------------------------
// Name: GetFirstPlayerObjectInCell
// Desc: Retrieve the first player object at the given cell location
//-----------------------------------------------------------------------------
PlayerObject* CMazeClient::GetFirstPlayerObjectInCell( DWORD x, DWORD z )
{
    if( !IsMazeReady() ) 
        return NULL;

    FLOAT fCurTime = DXUtil_Timer( TIMER_GETAPPTIME );

    // Remove any PlayerObjects which are out of date (since they're probably not really in this
    // cell any more, but the server has just stopped telling us about them)
    PlayerObject** ppCell           = &m_pctCells[z][x];
    PlayerObject* pCur              = m_pctCells[z][x];
    PlayerObject* pPrev             = NULL;
    PlayerObject* pPlayerObject     = NULL;
    while( pCur )
    {
        // Too old?
        if( (fCurTime - pCur->fLastValidTime) > 5.0f )
        {
            pPlayerObject = pCur;
            pCur = pCur->pNext;

            // pPlayerObject is too old, so remove pPlayerObject from the m_pctCells linked list
            if( pPrev )
                pPrev->pNext = pPlayerObject->pNext;
            else
                *ppCell = pPlayerObject->pNext;
            pPlayerObject->pNext = NULL;

            // Update pPlayerObject so that it is marked as removed
            pPlayerObject->wCellX = pPlayerObject->wCellY = 0xffff;

            // Continue searching, and remove any other old instances from list 
        }
        else
        {
            pPrev = pCur;
            pCur  = pCur->pNext;
        }
    }

    // Now return first remaining PlayerObject in the cell (if any)
    return m_pctCells[z][x];
}




//-----------------------------------------------------------------------------
// Name: IsPlayerObjectInCell
// Desc: Determines whether the given player is at the given cell location
//-----------------------------------------------------------------------------
BOOL CMazeClient::IsPlayerObjectInCell( DWORD wCellX, DWORD wCellY, PlayerObject* pPlayerObject )
{
    PlayerObject* pPlayerObjectTmp = m_pctCells[wCellY][wCellX];
    while( pPlayerObjectTmp )
    {
        if( pPlayerObjectTmp == pPlayerObject )
            return TRUE;

        pPlayerObjectTmp = pPlayerObjectTmp->pNext;
    }

    return FALSE;
}

