//----------------------------------------------------------------------------
// File: mazeserver.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 <mmsystem.h>
#include <dplay8.h>
#include <dpaddr.h>
#include <dxerr9.h>
#include "DXUtil.h"
#include "MazeServer.h"
#include "Packets.h"
#include "Maze.h"
#include <malloc.h>
#include <tchar.h>



//-----------------------------------------------------------------------------
// Name: CMazeServer
// Desc: Constructor
//-----------------------------------------------------------------------------
CMazeServer::CMazeServer()
{
    m_dwPlayerCount         = 0;
    
    m_wActiveThreadCount   = 0;
    m_wMaxThreadCount      = 0;
    m_fAvgThreadCount      = 0;
    m_fAvgThreadTime       = 0;
    m_fMaxThreadTime       = 0;

    m_dwServerReliableRate  = 15;
    m_dwServerTimeout       = 150;
    m_dwLogLevel            = 2;
    m_pMaze                 = NULL;

    m_ClientNetConfig.ubReliableRate = 15;
    m_ClientNetConfig.wUpdateRate    = 150;
    m_ClientNetConfig.wTimeout       = 150;

    m_ClientNetConfig.dwThreadWait = 0;

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




//-----------------------------------------------------------------------------
// Name: Init
// Desc: Initialize the member variables
//-----------------------------------------------------------------------------
HRESULT CMazeServer::Init( BOOL bLocalLoopback, const CMaze* pMaze )
{
    m_bLocalLoopback = bLocalLoopback;
    m_pMaze = pMaze;
    if( m_pMaze == NULL )
        return DXTRACE_ERR_MSGBOX( TEXT("Param"), E_FAIL );

    // Grab height and width of maze
    m_dwWidth = m_pMaze->GetWidth();
    m_dwHeight = m_pMaze->GetHeight();

    m_ClientNetConfig.dwMazeWidth  = m_dwWidth;
    m_ClientNetConfig.dwMazeHeight = m_dwHeight;

    // Validate size. Must be a power-of-2 times LOCK_GRID_SIZE. Compute the shifts.
    if( m_dwWidth > SERVER_MAX_WIDTH || m_dwHeight > SERVER_MAX_HEIGHT )
        return DXTRACE_ERR_MSGBOX( TEXT("Maze height and width need to be less than 128"), E_INVALIDARG );
    if( (m_dwWidth % LOCK_GRID_SIZE) != 0 || (m_dwHeight % LOCK_GRID_SIZE) != 0 )
        return DXTRACE_ERR_MSGBOX( TEXT("Maze height and width need to be divisable by 16"), E_INVALIDARG );

    DWORD scale = m_dwWidth / LOCK_GRID_SIZE;
    m_dwMazeXShift = 0;
    while ( (scale >>= 1) )
        m_dwMazeXShift++;

    scale = m_dwHeight / LOCK_GRID_SIZE;
    m_dwMazeYShift = 0;
    while ( (scale >>= 1) )
        m_dwMazeYShift++;

    if( ((DWORD(LOCK_GRID_SIZE) << m_dwMazeXShift) != m_dwWidth) ||
        ((DWORD(LOCK_GRID_SIZE) << m_dwMazeYShift) != m_dwHeight) )
        return DXTRACE_ERR_MSGBOX( TEXT("Maze height and width need to be power of 2"), E_INVALIDARG );

    // Initialise the player list
    ZeroMemory( m_PlayerDatas, sizeof(m_PlayerDatas) );
    m_pFirstActivePlayerData = NULL;
    m_pFirstFreePlayerData = m_PlayerDatas;
    for( DWORD i = 1; i < MAX_PLAYER_OBJECTS-1; i++ )
    {
        m_PlayerDatas[i].pNext = &m_PlayerDatas[i+1];
        m_PlayerDatas[i].pPrevious = &m_PlayerDatas[i-1];
    }

    m_PlayerDatas[0].pNext = &m_PlayerDatas[1];
    m_PlayerDatas[MAX_PLAYER_OBJECTS-1].pPrevious = &m_PlayerDatas[MAX_PLAYER_OBJECTS-2];
    m_dwActivePlayerDataCount = 0;
    m_dwPlayerDataUniqueValue = 0;

    // Initialise the cells
    ZeroMemory( m_Cells, sizeof(m_Cells) );
    ZeroMemory( &m_OffMapCell, sizeof(m_OffMapCell) );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: Shutdown
// Desc: Handle the session shutdown
//-----------------------------------------------------------------------------
void CMazeServer::Shutdown()
{
}




//-----------------------------------------------------------------------------
// Name: LockRange
// Desc: Lock the given range of cells
//-----------------------------------------------------------------------------
void CMazeServer::LockRange( DWORD x1, DWORD y1, DWORD x2, DWORD y2 )
{
    m_LockGrid.LockRange( x1>>m_dwMazeXShift, y1>>m_dwMazeYShift ,
                          x2>>m_dwMazeXShift, y2>>m_dwMazeYShift );
}




//-----------------------------------------------------------------------------
// Name: UnlockRange
// Desc: Unlock the given range of cells
//-----------------------------------------------------------------------------
void CMazeServer::UnlockRange( DWORD x1, DWORD y1, DWORD x2, DWORD y2 )
{
    m_LockGrid.UnlockRange( x1>>m_dwMazeXShift, y1>>m_dwMazeYShift ,
                            x2>>m_dwMazeXShift, y2>>m_dwMazeYShift );
}




//-----------------------------------------------------------------------------
// Name: LockCell 
// Desc: Lock the cell at the given coordinates
//-----------------------------------------------------------------------------
void CMazeServer::LockCell( DWORD x, DWORD y )
{
    if( x == 0xffff )
        m_OffMapLock.Enter();
    else
        m_LockGrid.LockCell(x>>m_dwMazeXShift,y>>m_dwMazeYShift);
}




//-----------------------------------------------------------------------------
// Name: UnlockCell
// Desc: Unlock the cell at the given coordinates
//-----------------------------------------------------------------------------
void CMazeServer::UnlockCell( DWORD x, DWORD y )
{
    if( x == 0xffff )
        m_OffMapLock.Leave();
    else
        m_LockGrid.UnlockCell(x>>m_dwMazeXShift,y>>m_dwMazeYShift);
}




//-----------------------------------------------------------------------------
// Name: LockCellPair
// Desc: Lock the given pair of cells
//-----------------------------------------------------------------------------
void CMazeServer::LockCellPair( DWORD x1, DWORD y1, DWORD x2, DWORD y2 )
{
    if( x1 == x2 && y1 == y2 )
    {
        if( x1 != 0xffff && y2 != 0xffff )
            LockCell( x1, y1 );
        else
            m_OffMapLock.Enter();

        return;
    }

    DWORD x1shift = x1>>m_dwMazeXShift;
    DWORD x2shift = x2>>m_dwMazeXShift;
    DWORD y1shift = y1>>m_dwMazeYShift;
    DWORD y2shift = y2>>m_dwMazeYShift;

    if( x1 == 0xffff )
    {
        m_OffMapLock.Enter();
        m_LockGrid.LockCell(x2shift,y2shift);
    }
    else if( x2 == 0xffff )
    {
        m_OffMapLock.Enter();
        m_LockGrid.LockCell(x1shift,y1shift);
    }
    else 
    {
        m_LockGrid.LockCellPair(x1shift,y1shift,x2shift,y2shift);
    }
}




//-----------------------------------------------------------------------------
// Name: UnlockCellPair
// Desc: Unlock the given pair of cells
//-----------------------------------------------------------------------------
void CMazeServer::UnlockCellPair( DWORD x1, DWORD y1, DWORD x2, DWORD y2 )
{
    if( x1 == x2 && y1 == y2 )
    {
        if( x1 != 0xffff && y2 != 0xffff )
            UnlockCell( x1, y1 );
        else
            m_OffMapLock.Leave();

        return;
    }

    DWORD x1shift = x1>>m_dwMazeXShift;
    DWORD x2shift = x2>>m_dwMazeXShift;
    DWORD y1shift = y1>>m_dwMazeYShift;
    DWORD y2shift = y2>>m_dwMazeYShift;

    if( x1 == 0xffff )
    {
        m_LockGrid.UnlockCell(x2shift,y2shift);
        m_OffMapLock.Leave();
    }
    else if( x2 == 0xffff )
    {
        m_LockGrid.UnlockCell(x1shift,y1shift);
        m_OffMapLock.Leave();
    }
    else 
    {
        m_LockGrid.UnlockCellPair(x1shift,y1shift,x2shift,y2shift);
    }
}




//-----------------------------------------------------------------------------
// Name: OnAddConnection
// Desc: Handle a new client connection
//-----------------------------------------------------------------------------
void CMazeServer::OnAddConnection( DWORD id )
{
    m_AddRemoveLock.Enter();

    // Increment our count of players
    m_dwPlayerCount++;
    if( m_dwLogLevel > 0 )
    {
        ConsolePrintf( SLINE_LOG, TEXT("Adding player DPNID %0.8x"), id );
        ConsolePrintf( SLINE_LOG, TEXT("Players connected = %d"), m_dwPlayerCount );
    }

    if( m_dwPlayerCount > m_dwPeakPlayerCount )
        m_dwPeakPlayerCount = m_dwPlayerCount;

    // Create a player for this client
    PlayerData* pPlayerData = CreatePlayerData();
    if( pPlayerData == NULL )
    {
        ConsolePrintf( SLINE_LOG, TEXT("ERROR! Unable to create new PlayerData for client!") );
        DXTRACE_ERR_MSGBOX( TEXT("CreatePlayerData"), E_FAIL );
        m_AddRemoveLock.Leave();
        return;
    }

    // Store that pointer as local player data
    SetPlayerDataForID( id, pPlayerData );

    // Grab net config into to send to client
    m_ClientNetConfigLock.Enter();
    ServerConfigPacket packet( m_ClientNetConfig );
    m_ClientNetConfigLock.Leave();

    // Send it
    SendPacket( id, &packet, sizeof(packet), TRUE, 0 );

    m_AddRemoveLock.Leave();
}




//-----------------------------------------------------------------------------
// Name: OnRemoveConnection
// Desc: Handle a client disconnection
//-----------------------------------------------------------------------------
void CMazeServer::OnRemoveConnection( DWORD id )
{
    m_AddRemoveLock.Enter();

    // Decrement count of players
    m_dwPlayerCount--;

    if( m_dwLogLevel > 0 )
    {
        ConsolePrintf( SLINE_LOG, TEXT("Removing player DPNID %0.8x"), id );
        ConsolePrintf( SLINE_LOG, TEXT("Players connected = %d"), m_dwPlayerCount );
    }

    // Find playerdata for this client
    PlayerData* pPlayerData = GetPlayerDataForID( id );
    if( pPlayerData != NULL )
    {
        // Destroy it
        RemovePlayerDataID( pPlayerData );
        DestroyPlayerData( pPlayerData );
    }

    m_AddRemoveLock.Leave();
}




//-----------------------------------------------------------------------------
// Name: OnPacket
// Desc: Receive handler
//-----------------------------------------------------------------------------
HRESULT CMazeServer::OnPacket( DWORD dwFrom, void* pData, DWORD size )
{
    BOOL fFoundSize = FALSE;

    // Increment the number of thread we have in this process.
    m_csThreadCountLock.Enter();

    //Get the start time of when we entered the message handler.
    FLOAT   fStartTime = DXUtil_Timer( TIMER_GETAPPTIME );

    m_wActiveThreadCount++;
    if(m_wActiveThreadCount > m_wMaxThreadCount)
        m_wMaxThreadCount = m_wActiveThreadCount;
    
    // Calculate and average.
    FLOAT fdiff = m_wActiveThreadCount - m_fAvgThreadCount;
    m_fAvgThreadCount += fdiff/32;
    
    m_csThreadCountLock.Leave();


    ClientPacket* pClientPack = (ClientPacket*)pData;
    switch( pClientPack->wType )
    {
        case PACKETTYPE_CLIENT_POS:
            
            // Check to see if the packet has a valid size. Including 
            // the custom pack size.
            if( size < sizeof(ClientPosPacket))
                fFoundSize = FALSE;
            else if( ! IsValidPackSize(size - sizeof(ClientPosPacket)))
                fFoundSize = FALSE;
            else
                fFoundSize = TRUE;

            // If valid sized packet, handle the position.
            if(fFoundSize)
                HandleClientPosPacket( dwFrom, (ClientPosPacket*)pClientPack );
            else
                m_pNet->RejectClient( dwFrom, DISCONNNECT_REASON_CLIENT_OUT_OF_DATE );

            break;

        case PACKETTYPE_CLIENT_VERSION:
            if( size == sizeof(ClientVersionPacket) )
                HandleClientVersionPacket( dwFrom, (ClientVersionPacket*)pClientPack );
            else
                m_pNet->RejectClient( dwFrom, DISCONNNECT_REASON_CLIENT_OUT_OF_DATE );
            break;

        case PACKETTYPE_SERVER_CONFIG:

            // Server config packet sent to all users (including server). Just ignore and continue.

            break;
        default:
            HandleUnknownPacket( dwFrom, pClientPack, size );
            break;
    }

    //If the user wants to hold the thread, Sleep for given amount of time.
    if ( m_dwServerThreadWait > 0 )
    {
        Sleep( m_dwServerThreadWait );
    }
    
    // Retrieve thread data for this process.
    m_csThreadCountLock.Enter();

    m_wActiveThreadCount--;

    FLOAT fDiffTime = (DXUtil_Timer( TIMER_GETAPPTIME ) - fStartTime) - m_fAvgThreadTime;
    m_fAvgThreadTime += fDiffTime/32;

    //Get the Max time in the thread.
    if ( fDiffTime > m_fMaxThreadTime )
    {
        m_fMaxThreadTime = fDiffTime;
    }

    m_csThreadCountLock.Leave();

    return S_OK;
}



//-----------------------------------------------------------------------------
// Name: IsValidPackSize
// Desc: Validate the given packet size
//-----------------------------------------------------------------------------
BOOL CMazeServer::IsValidPackSize( DWORD dwSize )
{
    BOOL fFoundSize = FALSE;
    BYTE ubPackLocation = m_ClientNetConfig.ubClientPackIndex;
    
    // Check through the array of valid pack sizes.
    if(dwSize != m_ClientNetConfig.wClientPackSizeArray[ubPackLocation])
    {
        for( --ubPackLocation; ubPackLocation != m_ClientNetConfig.ubClientPackIndex; ubPackLocation--)
        {
            if(dwSize == m_ClientNetConfig.wClientPackSizeArray[ubPackLocation])
            {
                // Found valid size in the array.
                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 CMazeServer::OnSessionLost( DWORD dwReason )
{
    ConsolePrintf( SLINE_LOG, TEXT("ERROR! Session was lost") );
}




//-----------------------------------------------------------------------------
// Name: CreatePlayerData
// Desc: Activate the player at the top of the free player list
//-----------------------------------------------------------------------------
PlayerData* CMazeServer::CreatePlayerData()
{
    m_PlayerDataListLock.Enter();

    // Grab first free player in the list
    PlayerData* pPlayerData = m_pFirstFreePlayerData;

    if( pPlayerData )
    {
        LockPlayerData( pPlayerData );

        // Got one, so remove it from the free list
        if( pPlayerData->pPrevious )
            pPlayerData->pPrevious->pNext = pPlayerData->pNext;
        if( pPlayerData->pNext )
            pPlayerData->pNext->pPrevious = pPlayerData->pPrevious;
        m_pFirstFreePlayerData = pPlayerData->pNext;

        // Add it to the active list
        if( m_pFirstActivePlayerData )
            m_pFirstActivePlayerData->pPrevious = pPlayerData;
        pPlayerData->pNext = m_pFirstActivePlayerData;
        pPlayerData->pPrevious = NULL;
        m_pFirstActivePlayerData = pPlayerData;

        // Update count of players
        m_dwActivePlayerDataCount++;

        // Generate the ID for this player
        m_dwPlayerDataUniqueValue++;
        pPlayerData->dwID = (DWORD) ((pPlayerData-m_PlayerDatas)|(m_dwPlayerDataUniqueValue<<PLAYER_OBJECT_SLOT_BITS));

        pPlayerData->pNextInIDHashBucket = NULL;
        pPlayerData->NetID = 0;
        pPlayerData->dwNumNearbyPlayers = 0;

        // Insert into the "off-map" cell
        pPlayerData->fPosX = pPlayerData->fPosY = -1;
        pPlayerData->wCellX = pPlayerData->wCellY = 0xffff;
        m_OffMapLock.Enter();
        pPlayerData->pNextInCell = m_OffMapCell.pFirstPlayerData;
        m_OffMapCell.pFirstPlayerData = pPlayerData;
        m_OffMapLock.Leave();

        // Mark as active
        pPlayerData->bActive = TRUE;

        UnlockPlayerData( pPlayerData );
    }

    m_PlayerDataListLock.Leave();

    return pPlayerData;
}




//-----------------------------------------------------------------------------
// Name: DestroyPlayerData
// Desc: Deactivate the given player
//-----------------------------------------------------------------------------
void CMazeServer::DestroyPlayerData( PlayerData* pPlayerData )
{
    m_PlayerDataListLock.Enter();
    LockPlayerData( pPlayerData );

    // Remove the player from its cell
    RemovePlayerDataFromCell( pPlayerData );

    // Mark as inactive
    pPlayerData->bActive = FALSE;

    // Remove player from active list
    if( pPlayerData->pPrevious )
        pPlayerData->pPrevious->pNext = pPlayerData->pNext;
    if( pPlayerData->pNext )
        pPlayerData->pNext->pPrevious = pPlayerData->pPrevious;

    if( m_pFirstActivePlayerData == pPlayerData )
        m_pFirstActivePlayerData = pPlayerData->pNext;

    // Add it to the free list
    if( m_pFirstFreePlayerData )
        m_pFirstFreePlayerData->pPrevious = pPlayerData;
    pPlayerData->pNext = m_pFirstFreePlayerData;
    pPlayerData->pPrevious = NULL;
    m_pFirstFreePlayerData = pPlayerData;

    // Update count of players
    m_dwActivePlayerDataCount--;

    UnlockPlayerData( pPlayerData );
    m_PlayerDataListLock.Leave();
}




//-----------------------------------------------------------------------------
// Name: RemovePlayerDataFromCell
// Desc: Remove the given player from its current cell
//-----------------------------------------------------------------------------
void CMazeServer::RemovePlayerDataFromCell( PlayerData* pPlayerData )
{
    // Lock the player
    LockPlayerData( pPlayerData );

    // Lock the cell the player is in
    ServerCell* pCell;
    if( pPlayerData->wCellX == 0xffff )
    {
        m_OffMapLock.Enter();
        pCell = &m_OffMapCell;
    }
    else
    {
        LockCell( pPlayerData->wCellX, pPlayerData->wCellY );
        pCell = &m_Cells[pPlayerData->wCellY][pPlayerData->wCellX];
    }

    // Remove it from the cell
    PlayerData* pPt = pCell->pFirstPlayerData;
    PlayerData* pPrev = NULL;
    while ( pPt )
    {
        if( pPt == pPlayerData )
        {
            if( pPrev )
                pPrev->pNextInCell = pPlayerData->pNextInCell;
            else
                pCell->pFirstPlayerData = pPlayerData->pNextInCell;

            pPlayerData->pNextInCell = NULL;
            break;
        }
        pPrev = pPt;
        pPt = pPt->pNextInCell;
    }

    // Unlock the cell
    if( pPlayerData->wCellX == 0xffff )
        m_OffMapLock.Leave();
    else
        UnlockCell( pPlayerData->wCellX, pPlayerData->wCellY );

    // Unlock the player
    UnlockPlayerData( pPlayerData );
}




//-----------------------------------------------------------------------------
// Name: UnsafeRemovePlayerDataFromCell
// Desc: Remove the given player from its current cell without synchronizing
//       the data access
//-----------------------------------------------------------------------------
void CMazeServer::UnsafeRemovePlayerDataFromCell( PlayerData* pPlayerData )
{
    ServerCell* pCell = GetCell( pPlayerData );
    PlayerData* pPt  = pCell->pFirstPlayerData;
    PlayerData* pPrev = NULL;
    while ( pPt )
    {
        if( pPt == pPlayerData )
        {
            if( pPrev )
                pPrev->pNextInCell = pPlayerData->pNextInCell;
            else
                pCell->pFirstPlayerData = pPlayerData->pNextInCell;
            pPlayerData->pNextInCell = NULL;
            break;
        }
        pPrev = pPt;
        pPt = pPt->pNextInCell;
    }
}




//-----------------------------------------------------------------------------
// Name: UnsafeAddPlayerDataToCell
// Desc: Add the given player to its current cell without synchronizing the
//       data access 
//-----------------------------------------------------------------------------
void CMazeServer::UnsafeAddPlayerDataToCell( PlayerData* pPlayerData )
{
    ServerCell* pCell   = GetCell( pPlayerData );
    pPlayerData->pNextInCell = pCell->pFirstPlayerData;
    pCell->pFirstPlayerData = pPlayerData;
}




//-----------------------------------------------------------------------------
// Name: HandleClientPosPacket
// Desc: Handle position packet from player
//-----------------------------------------------------------------------------
void CMazeServer::HandleClientPosPacket( DWORD dwFrom, ClientPosPacket* pClientPosPack )
{
    // Grab player for this client and lock it
    PlayerData* pFromPlayer = GetPlayerDataForID( dwFrom );
    if( pFromPlayer == NULL )
    {
        if( m_dwLogLevel > 1 )
            ConsolePrintf( SLINE_LOG, TEXT("DPNID %0.8x: Could not find data structure for this player"), dwFrom );
        return;
    }

    LockPlayerData( pFromPlayer );

    if( FALSE == pFromPlayer->bAllow )
    {
        if( m_dwLogLevel > 0 )
            ConsolePrintf( SLINE_LOG, TEXT("DPNID %0.8x: Got position packet from bad client.  Rejecting client"), pFromPlayer->NetID );

        m_pNet->RejectClient( dwFrom, DISCONNNECT_REASON_CLIENT_OUT_OF_DATE );
        UnlockPlayerData( pFromPlayer );
        return;
    }

    // Compute the cell the player should be in now
    DWORD newcellx = int(pClientPosPack->fX);
    DWORD newcelly = int(pClientPosPack->fY);
    DWORD oldcellx = pFromPlayer->wCellX;
    DWORD oldcelly = pFromPlayer->wCellY;

    // Have we moved cell?
    if( newcellx != oldcellx || newcelly != oldcelly )
    {
        // Yes, so lock the pair of cells in question
        LockCellPair( oldcellx, oldcelly, newcellx, newcelly );

        // Remove from old cell and add to new cell
        UnsafeRemovePlayerDataFromCell( pFromPlayer );
        pFromPlayer->wCellX = WORD(newcellx); pFromPlayer->wCellY = WORD(newcelly);
        UnsafeAddPlayerDataToCell( pFromPlayer );

        // Unlock cells
        UnlockCellPair( oldcellx, oldcelly, newcellx, newcelly );
    }

    // Update player position
    pFromPlayer->fPosX      = pClientPosPack->fX;
    pFromPlayer->fPosY      = pClientPosPack->fY;
    pFromPlayer->fVelPos    = pClientPosPack->fVelPos;
    pFromPlayer->aYaw       = pClientPosPack->aYaw;
    pFromPlayer->nVelYaw    = pClientPosPack->nVelYaw;

    // Allocate space to build the reply packet, and fill in header 
    DWORD dwAllocSize;
    ServerAckPacket* pSvrAckPack = NULL;

    // Begin by allocating a buffer sized according to
    // the current number of nearby players + 4.  This will give 
    // a little room for more players to come 'near' without resize
    // the buffer.
    DWORD dwMaxPlayerStatePackets = pFromPlayer->dwNumNearbyPlayers + 4;

    dwAllocSize = sizeof(ServerAckPacket) + dwMaxPlayerStatePackets*sizeof(PlayerStatePacket);
    pSvrAckPack = (ServerAckPacket*) malloc( dwAllocSize );
    if( NULL == pSvrAckPack )
    {
        // Out of mem.  Cleanup and return
        UnlockPlayerData( pFromPlayer );
        return;       
    }
    ZeroMemory( pSvrAckPack, dwAllocSize );

    *pSvrAckPack = ServerAckPacket(m_dwPlayerCount);
    pSvrAckPack->wPlayerStatePacketCount = 0;
    PlayerStatePacket* pChunk = (PlayerStatePacket*)(pSvrAckPack+1);

    // Compute range of cells we're going to scan for players to send
    DWORD minx = (newcellx > 7) ? (newcellx - 7) : 0;
    DWORD miny = (newcelly > 7) ? (newcelly - 7) : 0;
    DWORD maxx = (newcellx+7 >= m_dwWidth) ? m_dwWidth-1 : newcellx+7;
    DWORD maxy = (newcelly+7 >= m_dwHeight) ? m_dwHeight-1 : newcelly+7;

    // Lock that range of cells
    LockRange( minx, miny, maxx, maxy );

    // Scan through the cells, tagging player data onto the end of
    // our pSvrAckPacket until we run out of room
    for( DWORD y = miny; y <= maxy; y++ )
    {
        for( DWORD x = minx; x <= maxx; x++ )
        {
            PlayerData* pCurPlayerData = m_Cells[y][x].pFirstPlayerData;
            while ( pCurPlayerData )
            {
                if( pCurPlayerData != pFromPlayer )
                {
                    if( pSvrAckPack->wPlayerStatePacketCount >= dwMaxPlayerStatePackets )
                    {
                        // Make sure pChunk is where we think it is
                        assert( (BYTE*) pChunk == (BYTE*) ((BYTE*)pSvrAckPack + sizeof(ServerAckPacket) + pSvrAckPack->wPlayerStatePacketCount*sizeof(PlayerStatePacket)) );

                        // There are more than just 4 new nearby players, so resize the 
                        // buffer pSvrAckPack to allow 16 more PlayerStatePacket's.
                        dwMaxPlayerStatePackets += 16;
                        dwAllocSize = sizeof(ServerAckPacket) + dwMaxPlayerStatePackets*sizeof(PlayerStatePacket);
                        ServerAckPacket* pNewSvrAckPack = NULL;
                        pNewSvrAckPack = (ServerAckPacket*) realloc( pSvrAckPack, dwAllocSize );
                        if( NULL == pNewSvrAckPack )
                        {
                            // Out of mem.  Cleanup and return
                            free( pSvrAckPack );
                            UnlockRange( minx, miny, maxx, maxy );
                            UnlockPlayerData( pFromPlayer );
                            return;       
                        }

                        pSvrAckPack = pNewSvrAckPack;
                        pChunk = (PlayerStatePacket*) ((BYTE*) ((BYTE*)pSvrAckPack + sizeof(ServerAckPacket) + pSvrAckPack->wPlayerStatePacketCount*sizeof(PlayerStatePacket) ) );

                        // Make sure pChunk is still where its supposed to be
                        assert( (BYTE*) pChunk == (BYTE*) ((BYTE*)pSvrAckPack + sizeof(ServerAckPacket) + pSvrAckPack->wPlayerStatePacketCount*sizeof(PlayerStatePacket)) );
                    }

                    pChunk->dwID       = pCurPlayerData->dwID;
                    pChunk->fX         = pCurPlayerData->fPosX;
                    pChunk->fY         = pCurPlayerData->fPosY;
                    pChunk->fVelPos    = pCurPlayerData->fVelPos;
                    pChunk->aYaw       = pCurPlayerData->aYaw;
                    pChunk->nVelYaw    = pCurPlayerData->nVelYaw;
                    pChunk++;
                    pSvrAckPack->wPlayerStatePacketCount++;
                }
                pCurPlayerData = pCurPlayerData->pNextInCell;
            }
        }
    }

    // Update the dwNumNearbyPlayers for this player
    pFromPlayer->dwNumNearbyPlayers = pSvrAckPack->wPlayerStatePacketCount;

    // Unlock range of cells
    UnlockRange( minx, miny, maxx, maxy );

    if( m_dwLogLevel > 2 )
    {
        ConsolePrintf( SLINE_LOG, TEXT("DPNID %0.8x: Position is (%0.2f,%0.2f)"), pFromPlayer->NetID, pFromPlayer->fPosX, pFromPlayer->fPosY );
    }
    else if( m_dwLogLevel == 2 )
    {
        FLOAT fTime = DXUtil_Timer( TIMER_GETAPPTIME );
        if( fTime - pFromPlayer->fLastDisplayTime > 60.0f )
        {
            ConsolePrintf( SLINE_LOG, TEXT("DPNID %0.8x: Position is (%0.2f,%0.2f)"), pFromPlayer->NetID, pFromPlayer->fPosX, pFromPlayer->fPosY );
            pFromPlayer->fLastDisplayTime = fTime;
        }
    }

    // Unlock the playerdata
    UnlockPlayerData( pFromPlayer );

    // Send acknowledgement back to client, including list of nearby players 
    DWORD acksize = sizeof(ServerAckPacket) + (pSvrAckPack->wPlayerStatePacketCount * sizeof(PlayerStatePacket));
 
    // Pack the buffer with dummy data.
    if(m_ClientNetConfig.wServerPackSizeArray[m_ClientNetConfig.ubServerPackIndex] > 0)
    {
        DWORD   dwBufferSize = acksize + m_ClientNetConfig.wServerPackSizeArray[m_ClientNetConfig.ubServerPackIndex];
        VOID*   pTempBuffer = 0;

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

        FillMemory(pTempBuffer, dwBufferSize, 'Z');
        memcpy(pTempBuffer, pSvrAckPack, acksize);

        SendPacket( dwFrom, pTempBuffer, dwBufferSize, FALSE, m_dwServerTimeout );
    
        free(pTempBuffer);
    }   
    else
    {
        SendPacket( dwFrom, pSvrAckPack, acksize, FALSE, m_dwServerTimeout );
    }

    free( pSvrAckPack );

}




//-----------------------------------------------------------------------------
// Name: HandleClientVersionPacket
// Desc: Handle version packet from player
//-----------------------------------------------------------------------------
void CMazeServer::HandleClientVersionPacket( DWORD dwFrom, ClientVersionPacket* pClientVersionPack )
{
    // Grab playerdata for this client and lock it
    PlayerData* pPlayerData = GetPlayerDataForID( dwFrom );
    if( pPlayerData == NULL )
        return;
    LockPlayerData( pPlayerData );

    // Record the version number 
    pPlayerData->dwVersion = pClientVersionPack->dwVersion;

    if( m_bLocalLoopback )
        pPlayerData->bAllow = TRUE;
    else
        pPlayerData->bAllow = IsClientVersionSupported( pClientVersionPack->dwVersion );

    if( m_dwLogLevel > 0 )
        ConsolePrintf( SLINE_LOG, TEXT("DPNID %0.8x: Client version=%d (%s)"), pPlayerData->NetID, pPlayerData->dwVersion, pPlayerData->bAllow ? TEXT("Accepted") : TEXT("Rejected") );

    if( FALSE == pPlayerData->bAllow )
    {
        if( m_dwLogLevel > 0 )
            ConsolePrintf( SLINE_LOG, TEXT("DPNID %0.8x: Rejecting client"), pPlayerData->NetID );

        m_pNet->RejectClient( dwFrom, DISCONNNECT_REASON_CLIENT_OUT_OF_DATE );
        UnlockPlayerData( pPlayerData );
        return;
    }

    // Unlock the playerdata
    UnlockPlayerData( pPlayerData );

    // Send acknowledgement to client that the client was either accepted or rejected
    ServerAckVersionPacket packet( pPlayerData->bAllow, dwFrom );
    SendPacket( dwFrom, &packet, sizeof(packet), TRUE, 0 );
}




//-----------------------------------------------------------------------------
// Name: IsClientVersionSupported
// Desc: Check the client version number
//-----------------------------------------------------------------------------
BOOL CMazeServer::IsClientVersionSupported( DWORD dwClientVersion )
{
    switch( dwClientVersion )
    {
        case 107: // only v107 is supported
            return TRUE;
        default:
            return FALSE;
    }
}




//-----------------------------------------------------------------------------
// Name: HandleUnknownPacket
// Desc: Handle unknown packet type from player
//-----------------------------------------------------------------------------
void CMazeServer::HandleUnknownPacket( DWORD dwFrom, ClientPacket* pClientPack, DWORD size )
{
    if( m_dwLogLevel > 1 )
        ConsolePrintf( SLINE_LOG, TEXT("ERROR! Unknown %d byte packet from player %0.8x"), size, dwFrom );

    m_pNet->RejectClient( dwFrom, DISCONNNECT_REASON_CLIENT_OUT_OF_DATE );
}




//-----------------------------------------------------------------------------
// Name: IDHash
// Desc: Hash the current id for hashtable storage
//-----------------------------------------------------------------------------
DWORD   CMazeServer::IDHash( DWORD id )
{
    DWORD   hash = ((id) + (id>>8) + (id>>16) + (id>>24)) & (NUM_ID_HASH_BUCKETS-1);
    return hash;
}




//-----------------------------------------------------------------------------
// Name: RemovePlayerDataID
// Desc: Remove the given player ID from the hashtable
//-----------------------------------------------------------------------------
void CMazeServer::RemovePlayerDataID( PlayerData* pPlayerData )
{
    // Hash the ID to a bucket number
    DWORD   bucket = IDHash( pPlayerData->NetID );

    // Lock that hash bucket
    const   DWORD   buckets_per_lock = NUM_ID_HASH_BUCKETS / NUM_ID_HASH_BUCKET_LOCKS;
    m_IDHashBucketLocks[bucket/buckets_per_lock].Enter();

    // Loop though players in bucket until we find the right one
    PlayerData* pPt = m_pstIDHashBucket[bucket];
    PlayerData* pPrev = NULL;
    while( pPt )
    {
        if( pPt == pPlayerData )
            break;
        pPrev = pPt;
        pPt = pPt->pNextInIDHashBucket;
    }

    if( pPt )
    {
        if( pPrev )
            pPrev->pNextInIDHashBucket = pPt->pNextInIDHashBucket;
        else
            m_pstIDHashBucket[bucket] = pPt->pNextInIDHashBucket;
        pPt->pNextInIDHashBucket = NULL;
    }

    // Unlock the hash bucket
    m_IDHashBucketLocks[bucket/buckets_per_lock].Leave();
}




//-----------------------------------------------------------------------------
// Name: SetPlayerDataForID
// Desc: Store the data for the given player id in the hashtable
//-----------------------------------------------------------------------------
void CMazeServer::SetPlayerDataForID( DWORD id, PlayerData* pPlayerData )
{
    // Make sure this player isn't added twice to the m_pstIDHashBucket[]
    // otherwise there will be a circular reference
    PlayerData* pSearch = GetPlayerDataForID( id );
    if( pSearch != NULL )
        return;

    // Hash the ID to a bucket number
    DWORD   bucket = IDHash( id );

    // Lock that hash bucket
    const   DWORD   buckets_per_lock = NUM_ID_HASH_BUCKETS / NUM_ID_HASH_BUCKET_LOCKS;
    m_IDHashBucketLocks[bucket/buckets_per_lock].Enter();

    // Add player onto hash bucket chain
    pPlayerData->pNextInIDHashBucket = m_pstIDHashBucket[bucket];
    m_pstIDHashBucket[bucket] = pPlayerData;

    // Store net id in player
    pPlayerData->NetID = id;

    // Unlock the hash bucket
    m_IDHashBucketLocks[bucket/buckets_per_lock].Leave();
}




//-----------------------------------------------------------------------------
// Name: GetPlayerDataForID
// Desc: Retrieve the assocated player data for the given ID
//-----------------------------------------------------------------------------
PlayerData* CMazeServer::GetPlayerDataForID( DWORD id )
{
    // Hash the ID to a bucket number
    DWORD   bucket = IDHash( id );

    // Lock that hash bucket
    const   DWORD   buckets_per_lock = NUM_ID_HASH_BUCKETS / NUM_ID_HASH_BUCKET_LOCKS;
    m_IDHashBucketLocks[bucket/buckets_per_lock].Enter();

    // Loop though players in bucket until we find the right one
    PlayerData* pPlayerData = m_pstIDHashBucket[bucket];
    while ( pPlayerData )
    {
        if( pPlayerData->NetID == id )
            break;
        pPlayerData = pPlayerData->pNextInIDHashBucket;
    }

    // Unlock the hash bucket
    m_IDHashBucketLocks[bucket/buckets_per_lock].Leave();

    // Return the player we found (will be NULL if we couldn't find it)
    return pPlayerData;
}




//-----------------------------------------------------------------------------
// Name: DisplayNextConnectionInfo
// Desc: calls DisplayConnectionInfo for each connection in a round-robin manner
//-----------------------------------------------------------------------------
void CMazeServer::DisplayNextConnectionInfo()
{
    if( m_pNet )
    {
        // Find the player that was displayed the longest time ago, and display it.
        FLOAT fCurTime = DXUtil_Timer( TIMER_GETAPPTIME );
        PlayerData* pOldestPlayerData = NULL;
        FLOAT fOldestTime = 0.0f;

        m_PlayerDataListLock.Enter();

        PlayerData* pPlayerData = m_pFirstActivePlayerData;
        while ( pPlayerData )
        {
            if( fCurTime - pPlayerData->fLastCITime > fOldestTime )
            {
                fOldestTime  = fCurTime - pPlayerData->fLastCITime;
                pOldestPlayerData = pPlayerData;
            }

            pPlayerData = pPlayerData->pNext;
        }

        // Display the player with the oldest CI field, and update its CI field.
        if( pOldestPlayerData )
        {
            ConsolePrintf( SLINE_LOG, TEXT("Displaying connection info for next player") );
            DisplayConnectionInfo( pOldestPlayerData->NetID );
            pOldestPlayerData->fLastCITime = fCurTime;
        }
        else
        {
            ConsolePrintf( SLINE_LOG, TEXT("No players found") );
        }

        m_PlayerDataListLock.Leave();
    }
}




//-----------------------------------------------------------------------------
// Name: PrintStats
// Desc: Print the current statistics to the console
//-----------------------------------------------------------------------------
void CMazeServer::PrintStats()
{
    ConsolePrintf( SLINE_LOG, TEXT("Thread count: Active=%d Avg=%.2f Max=%d"), 
                                    m_wActiveThreadCount, m_fAvgThreadCount, m_wMaxThreadCount );
    ConsolePrintf( SLINE_LOG, TEXT("Thread Time: Avg=%.4f Max=%.4f(s)"),
                                    m_fAvgThreadTime, m_fMaxThreadTime );
    ConsolePrintf( SLINE_LOG, TEXT("Players online (not including server player): %d"), m_dwPlayerCount );
    ConsolePrintf( SLINE_LOG, TEXT("Peak player count: %d"), m_dwPeakPlayerCount );
}




//-----------------------------------------------------------------------------
// Name: DisplayConnectionInfo
// Desc: Print the current connection info to the console
//-----------------------------------------------------------------------------
void CMazeServer::DisplayConnectionInfo( DWORD dwID )
{
    TCHAR strInfo[5000];
    TCHAR* strEndOfLine;
    TCHAR* strStartOfLine;

    // Query the IOutboudNet for info about the connection to this user
    m_pNet->GetConnectionInfo( dwID, strInfo, 5000 );

    ConsolePrintf( SLINE_LOG, TEXT("Displaying connection info for %0.8x"), dwID );
    ConsolePrintf( SLINE_LOG, TEXT("(Key: G=Guaranteed NG=Non-Guaranteed B=Bytes P=Packets)") );

    // Display each line seperately
    strStartOfLine = strInfo;
    for( ; ; )
    {
        strEndOfLine = _tcschr( strStartOfLine, '\n' );
        if( strEndOfLine == NULL )
            break;

        *strEndOfLine = 0;
        ConsolePrintf( SLINE_LOG, strStartOfLine );
        strStartOfLine = strEndOfLine + 1;
    }
}




//-----------------------------------------------------------------------------
// Name: SendPacket
// Desc: Send the given data to the given client player
//-----------------------------------------------------------------------------
HRESULT CMazeServer::SendPacket( DWORD to, void* pData, 
                                 DWORD size, BOOL reliable, DWORD dwTimeout )
{
    // Chance of forcing any packet to be delivered reliably
    if( m_Rand.Get( 100 ) < m_dwServerReliableRate )
        reliable = TRUE;

    return m_pNet->SendPacket( to, pData, size, reliable, dwTimeout );
}




//-----------------------------------------------------------------------------
// Name: SendConfigPacketToAll
// Desc: Send startup configuration data to all connected clients
//-----------------------------------------------------------------------------
void CMazeServer::SendConfigPacketToAll( ServerConfigPacket* pPacket )
{
    // If we're up and running, then send this new information to all clients
    if( m_pNet )
    {
        //Use the AllPlayers ID
        SendPacket( DPNID_ALL_PLAYERS_GROUP, pPacket, sizeof(ServerConfigPacket), TRUE, 0 );
    }
}




//-----------------------------------------------------------------------------
// Name: SetClientReliableRate
// Desc: Set the data reliability rate
//-----------------------------------------------------------------------------
void CMazeServer::SetClientReliableRate( DWORD percent )
{
    // Update client config, and build packet containing that data
    m_ClientNetConfigLock.Enter();
    m_ClientNetConfig.ubReliableRate = BYTE(percent);
    ServerConfigPacket packet( m_ClientNetConfig );
    m_ClientNetConfigLock.Leave();

    SendConfigPacketToAll( &packet );
}




//-----------------------------------------------------------------------------
// Name: SetClientUpdateRate
// Desc: Set the rate at which clients update position information
//-----------------------------------------------------------------------------
void CMazeServer::SetClientUpdateRate( DWORD rate )
{
    // Update client config, and build packet containing that data
    m_ClientNetConfigLock.Enter();
    m_ClientNetConfig.wUpdateRate = WORD(rate);
    ServerConfigPacket  packet( m_ClientNetConfig );
    m_ClientNetConfigLock.Leave();

    SendConfigPacketToAll( &packet );
}




//-----------------------------------------------------------------------------
// Name: SetClientTimeout
// Desc: Set the client data timeout period
//-----------------------------------------------------------------------------
void CMazeServer::SetClientTimeout( DWORD timeout )
{
    // Update client config, and build packet containing that data
    m_ClientNetConfigLock.Enter();
    m_ClientNetConfig.wTimeout = WORD(timeout);
    ServerConfigPacket  packet( m_ClientNetConfig );
    m_ClientNetConfigLock.Leave();

    SendConfigPacketToAll( &packet );
}




//-----------------------------------------------------------------------------
// Name: SetClientPackSize
// Desc: Set the packet size for client data
//-----------------------------------------------------------------------------
void CMazeServer::SetClientPackSize( DWORD size )
{
    // Update client config, and build packet containing that data
    m_ClientNetConfigLock.Enter();
    
    m_ClientNetConfig.ubClientPackIndex++; //Increase index and verify location in array.
    if(m_ClientNetConfig.ubClientPackIndex >= PACK_ARRAY_SIZE)   
        m_ClientNetConfig.ubClientPackIndex = 0;

    m_ClientNetConfig.wClientPackSizeArray[m_ClientNetConfig.ubClientPackIndex] = WORD(size);
    ServerConfigPacket packet( m_ClientNetConfig );
    m_ClientNetConfigLock.Leave();

    SendConfigPacketToAll( &packet );
}




//-----------------------------------------------------------------------------
// Name: SetServerPackSize
// Desc: Set the packet size for server data
//-----------------------------------------------------------------------------
void CMazeServer::SetServerPackSize( DWORD size )
{
    // Update client config, and build packet containing that data
    m_ClientNetConfigLock.Enter();
    
    m_ClientNetConfig.ubServerPackIndex++; //Increase index and verify location in array.
    if(m_ClientNetConfig.ubServerPackIndex >= PACK_ARRAY_SIZE)   
        m_ClientNetConfig.ubServerPackIndex = 0;

    m_ClientNetConfig.wServerPackSizeArray[m_ClientNetConfig.ubServerPackIndex] = WORD(size);
    ServerConfigPacket packet( m_ClientNetConfig );
    m_ClientNetConfigLock.Leave();

    SendConfigPacketToAll( &packet );
}


//-----------------------------------------------------------------------------
// Name: SetClientThreadWait
// Desc: Set the wait time for client threads
//-----------------------------------------------------------------------------
void CMazeServer::SetClientThreadWait( DWORD dwThreadWait )
{
    // Update client config, and build packet containing that data
    m_ClientNetConfigLock.Enter();
    
    m_ClientNetConfig.dwThreadWait = dwThreadWait;
    ServerConfigPacket packet( m_ClientNetConfig );
    m_ClientNetConfigLock.Leave();

    SendConfigPacketToAll( &packet );
}
