void CGame::ClientReadNet() { if (gu->gameTime - lastCpuUsageTime >= 1) { lastCpuUsageTime = gu->gameTime; if (playing) { net->Send(CBaseNetProtocol::Get().SendCPUUsage(profiler.GetPercent("CPU load"))); #if defined(USE_GML) && GML_ENABLE_SIM net->Send(CBaseNetProtocol::Get().SendLuaDrawTime(gu->myPlayerNum, luaDrawTime)); #endif } else { // the CPU-load percentage is undefined prior to SimFrame() net->Send(CBaseNetProtocol::Get().SendCPUUsage(0.0f)); } } boost::shared_ptr<const netcode::RawPacket> packet; // compute new timeLeft to "smooth" out SimFrame() calls if (!gameServer) { const unsigned int currentFrame = SDL_GetTicks(); if (timeLeft > 1.0f) timeLeft -= 1.0f; timeLeft += consumeSpeed * ((float)(currentFrame - lastframe) / 1000.f); if (skipping) timeLeft = 0.01f; lastframe = currentFrame; // read ahead to calculate the number of NETMSG_NEWFRAMES // we still have to process (in variable "que") int que = 0; // Number of NETMSG_NEWFRAMEs waiting to be processed. unsigned ahead = 0; while ((packet = net->Peek(ahead))) { if (packet->data[0] == NETMSG_NEWFRAME || packet->data[0] == NETMSG_KEYFRAME) ++que; ++ahead; } if(que < leastQue) leastQue = que; } else { // make sure ClientReadNet returns at least every 15 game frames // so CGame can process keyboard input, and render etc. timeLeft = (float)MAX_CONSECUTIVE_SIMFRAMES * gs->userSpeedFactor; } // always render at least 2FPS (will otherwise be highly unresponsive when catching up after a reconnection) unsigned procstarttime = SDL_GetTicks(); // really process the messages while (timeLeft > 0.0f && (SDL_GetTicks() - procstarttime) < 500 && (packet = net->GetData(gs->frameNum))) { const unsigned char* inbuf = packet->data; const unsigned dataLength = packet->length; const unsigned char packetCode = inbuf[0]; switch (packetCode) { case NETMSG_QUIT: { try { netcode::UnpackPacket pckt(packet, 3); std::string message; pckt >> message; logOutput.Print(message); if (!gameOver) { GameEnd(std::vector<unsigned char>()); } AddTraffic(-1, packetCode, dataLength); } catch (netcode::UnpackPacketException &e) { logOutput.Print("Got invalid QuitMessage: %s", e.err.c_str()); } break; } case NETMSG_PLAYERLEFT: { const unsigned char player = inbuf[1]; if (!playerHandler->IsValidPlayer(player)) { logOutput.Print("Got invalid player num (%i) in NETMSG_PLAYERLEFT", player); break; } playerHandler->PlayerLeft(player, inbuf[2]); eventHandler.PlayerRemoved(player, (int) inbuf[2]); AddTraffic(player, packetCode, dataLength); break; } case NETMSG_MEMDUMP: { MakeMemDump(); #ifdef TRACE_SYNC tracefile.Commit(); #endif AddTraffic(-1, packetCode, dataLength); break; } case NETMSG_STARTPLAYING: { unsigned timeToStart = *(unsigned*)(inbuf+1); if (timeToStart > 0) { GameSetupDrawer::StartCountdown(timeToStart); } else { StartPlaying(); } AddTraffic(-1, packetCode, dataLength); break; } case NETMSG_SENDPLAYERSTAT: { //logOutput.Print("Game over"); // Warning: using CPlayer::Statistics here may cause endianness problems // once net->SendData is endian aware! net->Send(CBaseNetProtocol::Get().SendPlayerStat(gu->myPlayerNum, playerHandler->Player(gu->myPlayerNum)->currentStats)); AddTraffic(-1, packetCode, dataLength); break; } case NETMSG_PLAYERSTAT: { const unsigned char player = inbuf[1]; if (!playerHandler->IsValidPlayer(player)) { logOutput.Print("Got invalid player num %i in playerstat msg",player); break; } playerHandler->Player(player)->currentStats = *(CPlayer::Statistics*)&inbuf[2]; if (gameOver) { CDemoRecorder* record = net->GetDemoRecorder(); if (record != NULL) { record->SetPlayerStats(player, playerHandler->Player(player)->currentStats); } } AddTraffic(player, packetCode, dataLength); break; } case NETMSG_PAUSE: { const unsigned char player = inbuf[1]; if (!playerHandler->IsValidPlayer(player)) { logOutput.Print("Got invalid player num %i in pause msg",player); break; } gs->paused=!!inbuf[2]; logOutput.Print(gs->paused ? "%s paused the game" : "%s unpaused the game" , playerHandler->Player(player)->name.c_str()); eventHandler.GamePaused(player, gs->paused); lastframe = SDL_GetTicks(); AddTraffic(player, packetCode, dataLength); break; } case NETMSG_INTERNAL_SPEED: { gs->speedFactor = *((float*) &inbuf[1]); sound->PitchAdjust(sqrt(gs->speedFactor)); // logOutput.Print("Internal speed set to %.2f",gs->speedFactor); AddTraffic(-1, packetCode, dataLength); break; } case NETMSG_USER_SPEED: { gs->userSpeedFactor = *((float*) &inbuf[2]); const unsigned char player = inbuf[1]; if (!playerHandler->IsValidPlayer(player) && player != SERVER_PLAYER) { logOutput.Print("Got invalid player num %i in user speed msg", player); break; } const char* pName = (player == SERVER_PLAYER)? "server": playerHandler->Player(player)->name.c_str(); logOutput.Print("Speed set to %.1f [%s]", gs->userSpeedFactor, pName); AddTraffic(player, packetCode, dataLength); break; } case NETMSG_CPU_USAGE: { logOutput.Print("Game clients shouldn't get cpu usage msgs?"); AddTraffic(-1, packetCode, dataLength); break; } case NETMSG_PLAYERINFO: { const unsigned char player = inbuf[1]; if (!playerHandler->IsValidPlayer(player)) { logOutput.Print("Got invalid player num %i in playerinfo msg", player); break; } playerHandler->Player(player)->cpuUsage = *(float*) &inbuf[2]; playerHandler->Player(player)->ping = *(boost::uint32_t*) &inbuf[6]; AddTraffic(player, packetCode, dataLength); break; } case NETMSG_PLAYERNAME: { try { netcode::UnpackPacket pckt(packet, 2); unsigned char player; pckt >> player; if (!playerHandler->IsValidPlayer(player)) throw netcode::UnpackPacketException("Invalid player number"); pckt >> playerHandler->Player(player)->name; playerHandler->Player(player)->readyToStart=(gameSetup->startPosType != CGameSetup::StartPos_ChooseInGame); playerHandler->Player(player)->active=true; wordCompletion->AddWord(playerHandler->Player(player)->name, false, false, false); // required? AddTraffic(player, packetCode, dataLength); } catch (netcode::UnpackPacketException &e) { logOutput.Print("Got invalid PlayerName: %s", e.err.c_str()); } break; } case NETMSG_CHAT: { try { ChatMessage msg(packet); HandleChatMsg(msg); AddTraffic(msg.fromPlayer, packetCode, dataLength); } catch (netcode::UnpackPacketException &e) { logOutput.Print("Got invalid ChatMessage: %s", e.err.c_str()); } break; } case NETMSG_SYSTEMMSG:{ try { netcode::UnpackPacket pckt(packet, 4); string s; pckt >> s; logOutput.Print(s); AddTraffic(-1, packetCode, dataLength); } catch (netcode::UnpackPacketException &e) { logOutput.Print("Got invalid SystemMessage: %s", e.err.c_str()); } break; } case NETMSG_STARTPOS:{ const unsigned char player = inbuf[1]; if (!playerHandler->IsValidPlayer(player) && player != SERVER_PLAYER) { logOutput.Print("Got invalid player num %i in start pos msg", player); break; } const int team = inbuf[2]; if (!teamHandler->IsValidTeam(team)) { logOutput.Print("Got invalid team num %i in startpos msg", team); } else { float3 pos(*(float*)&inbuf[4], *(float*)&inbuf[8], *(float*)&inbuf[12]); if (!luaRules || luaRules->AllowStartPosition(player, pos)) { teamHandler->Team(team)->StartposMessage(pos); if (inbuf[3] != 2 && player != SERVER_PLAYER) { playerHandler->Player(player)->readyToStart = !!inbuf[3]; } if (pos.y != -500) // no marker marker when no pos set yet { char label[128]; SNPRINTF(label, sizeof(label), "Start %i", team); inMapDrawer->LocalPoint(pos, label, player); // FIXME - erase old pos ? } } } AddTraffic(player, packetCode, dataLength); break; } case NETMSG_RANDSEED: { gs->SetRandSeed(*((unsigned int*)&inbuf[1]), true); AddTraffic(-1, packetCode, dataLength); break; } case NETMSG_GAMEID: { const unsigned char* p = &inbuf[1]; CDemoRecorder* record = net->GetDemoRecorder(); if (record != NULL) { record->SetGameID(p); } memcpy(gameID, p, sizeof(gameID)); logOutput.Print( "GameID: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", p[ 0], p[ 1], p[ 2], p[ 3], p[ 4], p[ 5], p[ 6], p[ 7], p[ 8], p[ 9], p[10], p[11], p[12], p[13], p[14], p[15]); AddTraffic(-1, packetCode, dataLength); break; } case NETMSG_PATH_CHECKSUM: { const unsigned char playerNum = inbuf[1]; if (!playerHandler->IsValidPlayer(playerNum)) { logOutput.Print("Got invalid player num %i in path checksum msg", playerNum); break; } const boost::uint32_t playerCheckSum = *(boost::uint32_t*) &inbuf[2]; const boost::uint32_t localCheckSum = pathManager->GetPathCheckSum(); const CPlayer* player = playerHandler->Player(playerNum); if (playerCheckSum == 0) { logOutput.Print( "[DESYNC WARNING] path-checksum for player %d (%s) is 0; non-writable cache?", playerNum, player->name.c_str() ); } else { if (playerCheckSum != localCheckSum) { logOutput.Print( "[DESYNC WARNING] path-checksum %08x for player %d (%s) does not match local checksum %08x", playerCheckSum, playerNum, player->name.c_str(), localCheckSum ); } } } break; case NETMSG_KEYFRAME: { int serverframenum = *(int*)(inbuf+1); net->Send(CBaseNetProtocol::Get().SendKeyFrame(serverframenum)); if (gs->frameNum == (serverframenum - 1)) { } else { // error LogObject() << "Error: Keyframe difference: " << gs->frameNum - (serverframenum - 1); } /* Fall through */ } case NETMSG_NEWFRAME: { timeLeft -= 1.0f; SimFrame(); // both NETMSG_SYNCRESPONSE and NETMSG_NEWFRAME are used for ping calculation by server #ifdef SYNCCHECK net->Send(CBaseNetProtocol::Get().SendSyncResponse(gs->frameNum, CSyncChecker::GetChecksum())); if ((gs->frameNum & 4095) == 0) {// reset checksum every ~2.5 minute gametime CSyncChecker::NewFrame(); } #endif AddTraffic(-1, packetCode, dataLength); if (videoCapturing->IsCapturing()) { return; } break; } case NETMSG_COMMAND: { try { netcode::UnpackPacket pckt(packet, 1); short int psize; pckt >> psize; unsigned char player; pckt >> player; if (!playerHandler->IsValidPlayer(player)) throw netcode::UnpackPacketException("Invalid player number"); Command c; pckt >> c.id; pckt >> c.options; for(int a = 0; a < ((psize-9)/4); ++a) { float param; pckt >> param; c.params.push_back(param); } selectedUnits.NetOrder(c,player); AddTraffic(player, packetCode, dataLength); } catch (netcode::UnpackPacketException &e) { logOutput.Print("Got invalid Command: %s", e.err.c_str()); } break; } case NETMSG_SELECT: { try { netcode::UnpackPacket pckt(packet, 1); short int psize; pckt >> psize; unsigned char player; pckt >> player; if (!playerHandler->IsValidPlayer(player)) throw netcode::UnpackPacketException("Invalid player number"); vector<int> selected; for (int a = 0; a < ((psize-4)/2); ++a) { short int unitid; pckt >> unitid; if (uh->GetUnit(unitid) == NULL) throw netcode::UnpackPacketException("Invalid unit ID"); if ((uh->GetUnit(unitid)->team == playerHandler->Player(player)->team) || gs->godMode) { selected.push_back(unitid); } } selectedUnits.NetSelect(selected, player); AddTraffic(player, packetCode, dataLength); } catch (netcode::UnpackPacketException &e) { logOutput.Print("Got invalid Select: %s", e.err.c_str()); } break; } case NETMSG_AICOMMAND: case NETMSG_AICOMMAND_TRACKED: { try { netcode::UnpackPacket pckt(packet, 1); short int psize; pckt >> psize; unsigned char player; pckt >> player; if (!playerHandler->IsValidPlayer(player)) throw netcode::UnpackPacketException("Invalid player number"); short int unitid; pckt >> unitid; if (unitid < 0 || static_cast<size_t>(unitid) >= uh->MaxUnits()) throw netcode::UnpackPacketException("Invalid unit ID"); Command c; pckt >> c.id; pckt >> c.options; if (packetCode == NETMSG_AICOMMAND_TRACKED) { pckt >> c.aiCommandId; } // insert the command parameters for (int a = 0; a < ((psize - 11) / 4); ++a) { float param; pckt >> param; c.params.push_back(param); } selectedUnits.AiOrder(unitid, c, player); AddTraffic(player, packetCode, dataLength); } catch (netcode::UnpackPacketException &e) { logOutput.Print("Got invalid AICommand: %s", e.err.c_str()); } break; }
void CPreGame::GameDataReceived(boost::shared_ptr<const netcode::RawPacket> packet) { ScopedOnceTimer startserver("PreGame::GameDataReceived"); try { gameData.reset(new GameData(packet)); } catch (const netcode::UnpackPacketException& ex) { throw content_error(std::string("Server sent us invalid GameData: ") + ex.what()); } // for demos, ReadDataFromDemo precedes UpdateClientNet -> GameDataReceived // this means gameSetup contains data from the original game but we need the // modified version (cf StartServerForDemo) which the server already has that // contains an extra player if (gameSetup != NULL) SafeDelete(gameSetup); if (CGameSetup::LoadReceivedScript(gameData->GetSetupText(), clientSetup->isHost)) { assert(gameSetup != NULL); gu->LoadFromSetup(gameSetup); gs->LoadFromSetup(gameSetup); // do we really need to do this so early? CPlayer::UpdateControlledTeams(); } else { throw content_error("Server sent us incorrect script"); } // some sanity checks for (int p = 0; p < playerHandler->ActivePlayers(); ++p) { const CPlayer* player = playerHandler->Player(p); if (!playerHandler->IsValidPlayer(player->playerNum)) { throw content_error("Invalid player in game data"); } if (!teamHandler->IsValidTeam(player->team)) { throw content_error("Invalid team in game data"); } if (!teamHandler->IsValidAllyTeam(teamHandler->AllyTeam(player->team))) { // TODO: seems not to make sense really throw content_error("Invalid ally team in game data"); } } // Load archives into VFS AddGameSetupArchivesToVFS(gameSetup, false); // Check checksums of map & game try { archiveScanner->CheckArchive(gameSetup->mapName, gameData->GetMapChecksum()); } catch (const content_error& ex) { LOG_L(L_WARNING, "Incompatible map-checksum: %s", ex.what()); } try { archiveScanner->CheckArchive(modArchive, gameData->GetModChecksum()); } catch (const content_error& ex) { LOG_L(L_WARNING, "Incompatible game-checksum: %s", ex.what()); } if (clientSetup->isHost && !gameSetup->recordDemo) { //script.txt allows to disable demo file recording (host only, used for menu) wantDemo = false; } if (clientNet != NULL && wantDemo) { assert(clientNet->GetDemoRecorder() == NULL); CDemoRecorder* recorder = new CDemoRecorder(gameSetup->mapName, gameSetup->modName, false); recorder->WriteSetupText(gameData->GetSetupText()); recorder->SaveToDemo(packet->data, packet->length, clientNet->GetPacketTime(gs->frameNum)); clientNet->SetDemoRecorder(recorder); LOG("Recording demo to: %s", (recorder->GetName()).c_str()); } }
void CPreGame::GameDataReceived(boost::shared_ptr<const netcode::RawPacket> packet) { ScopedOnceTimer startserver("PreGame::GameDataReceived"); try { gameData.reset(new GameData(packet)); } catch (const netcode::UnpackPacketException& ex) { throw content_error(std::string("Server sent us invalid GameData: ") + ex.what()); } CGameSetup* temp = new CGameSetup(); if (temp->Init(gameData->GetSetup())) { if (settings->isHost) { const std::string& setupTextStr = gameData->GetSetup(); std::fstream setupTextFile("_script.txt", std::ios::out); setupTextFile.write(setupTextStr.c_str(), setupTextStr.size()); setupTextFile.close(); } gameSetup = temp; gu->LoadFromSetup(gameSetup); gs->LoadFromSetup(gameSetup); CPlayer::UpdateControlledTeams(); } else { throw content_error("Server sent us incorrect script"); } // some sanity checks for (int p = 0; p < playerHandler->ActivePlayers(); ++p) { const CPlayer* player = playerHandler->Player(p); if (!playerHandler->IsValidPlayer(player->playerNum)) { throw content_error("Invalid player in game data"); } if (!teamHandler->IsValidTeam(player->team)) { throw content_error("Invalid team in game data"); } if (!teamHandler->IsValidAllyTeam(teamHandler->AllyTeam(player->team))) { // TODO: seems not to make sense really throw content_error("Invalid ally team in game data"); } } gs->SetRandSeed(gameData->GetRandomSeed(), true); LOG("Using map: %s", gameSetup->mapName.c_str()); vfsHandler->AddArchiveWithDeps(gameSetup->mapName, false); try { archiveScanner->CheckArchive(gameSetup->mapName, gameData->GetMapChecksum()); } catch (const content_error& ex) { LOG_L(L_WARNING, "Incompatible map-checksum: %s", ex.what()); } LOG("Using game: %s", gameSetup->modName.c_str()); vfsHandler->AddArchiveWithDeps(gameSetup->modName, false); modArchive = archiveScanner->ArchiveFromName(gameSetup->modName); LOG("Using game archive: %s", modArchive.c_str()); try { archiveScanner->CheckArchive(modArchive, gameData->GetModChecksum()); } catch (const content_error& ex) { LOG_L(L_WARNING, "Incompatible game-checksum: %s", ex.what()); } if (net != NULL && wantDemo) { assert(net->GetDemoRecorder() == NULL); CDemoRecorder* recorder = new CDemoRecorder(gameSetup->mapName, gameSetup->modName); recorder->WriteSetupText(gameData->GetSetup()); recorder->SaveToDemo(packet->data, packet->length, net->GetPacketTime(gs->frameNum)); net->SetDemoRecorder(recorder); LOG("recording demo: %s", (recorder->GetName()).c_str()); } }