//---------------------------------------------------------------------------- // File: mazeserver.cpp // // Desc: see main.cpp // // Copyright (c) 1999-2001 Microsoft Corp. All rights reserved. //----------------------------------------------------------------------------- #define STRICT #define D3D_OVERLOADS #include #include #include #include #include #include #include #include #include "DXUtil.h" #include "MazeServer.h" #include "Packets.h" #include "Maze.h" #include #include //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- HRESULT CMazeServer::Init( BOOL bLocalLoopback, const CMaze* pMaze ) { m_bLocalLoopback = bLocalLoopback; m_pMaze = pMaze; if( m_pMaze == NULL ) return DXTRACE_ERR( 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( 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( 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( 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: // Desc: //----------------------------------------------------------------------------- void CMazeServer::Shutdown() { } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- void CMazeServer::LockCell( DWORD x, DWORD y ) { if( x == 0xffff ) m_OffMapLock.Enter(); else m_LockGrid.LockCell(x>>m_dwMazeXShift,y>>m_dwMazeYShift); } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- void CMazeServer::UnlockCell( DWORD x, DWORD y ) { if( x == 0xffff ) m_OffMapLock.Leave(); else m_LockGrid.UnlockCell(x>>m_dwMazeXShift,y>>m_dwMazeYShift); } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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( 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- void CMazeServer::OnSessionLost( DWORD dwReason ) { ConsolePrintf( SLINE_LOG, TEXT("ERROR! Session was lost") ); } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- 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<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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- void CMazeServer::UnsafeAddPlayerDataToCell( PlayerData* pPlayerData ) { ServerCell* pCell = GetCell( pPlayerData ); pPlayerData->pNextInCell = pCell->pFirstPlayerData; pCell->pFirstPlayerData = pPlayerData; } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- 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"), pFromPlayer->NetID ); 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->aCameraYaw = pClientPosPack->aCameraYaw; // 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*) realloc( pSvrAckPack, 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->aCameraYaw = pCurPlayerData->aCameraYaw; 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_NOMSGBOX( 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- BOOL CMazeServer::IsClientVersionSupported( DWORD dwClientVersion ) { switch( dwClientVersion ) { case 107: // only v107 is supported return TRUE; default: return FALSE; } } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- DWORD CMazeServer::IDHash( DWORD id ) { DWORD hash = ((id) + (id>>8) + (id>>16) + (id>>24)) & (NUM_ID_HASH_BUCKETS-1); return hash; } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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 ); 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; while( TRUE ) { strEndOfLine = _tcschr( strStartOfLine, '\n' ); if( strEndOfLine == NULL ) break; *strEndOfLine = 0; ConsolePrintf( SLINE_LOG, strStartOfLine ); strStartOfLine = strEndOfLine + 1; } } //----------------------------------------------------------------------------- // Name: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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: // Desc: //----------------------------------------------------------------------------- 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 ); }