/* * Copyright 2014, 2016 Peter Olsson * * This file is part of Brum Brum Rally. * * Brum Brum Rally is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Brum Brum Rally is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "Network.h" #include "GameState.h" #include "Settings.h" #include "Track.h" #include "StateManager.h" #include "RaceState.h" #include "NetworkBlankState.h" #include "Track.h" #include "Driver.h" #include "ResultState.h" #include "AIDriver.h" #include "ErrorState.h" #include "NetworkMenu.h" #include "str.h" #include #include #include #include extern const int GAME_WIDTH; const Uint32 CONNECTION_TIMEOUT = 30000; const Uint32 GAMEINFO_SEND_INTERVAL = 1000; const Uint32 GAMEINFO_TIMEOUT = 3000; const Uint32 CHAT_SEND_INTERVAL = 250; const Uint32 PING_SEND_START = 20000; const Uint32 PING_SEND_INTERVAL = 3000; const Uint32 JOIN_TIMEOUT = 20000; const Uint32 GAME_STATE_SEND_INTERVAL = 100; const Uint32 GAME_TIMEOUT = 10000; const Uint32 GAME_NO_CHAT_INTERVAL = 2000; const Uint32 GAME_CHAT_INTERVAL = 100; const Uint32 CHAT_MESSAGE_TIMEOUT = 8000; const int NETWORK_STEP = 5; Network::Network() : socket(0), connectionsIt(connections.end()), connectionType(NET_LOBBY), gameState(), lastChatMessage(0), chatOn(true), game(), reservedCars(0), joinStatus(), newGameState(0), gameStateId(0), gameStateCount(0), lastGameTimestamp(0), lastGameSent(0), newTrackInfoReady(false), newTrackInfoCounter(0), newTrackStateTick(0), inputBuffer(), raceTick(0), lastSlowDown(0), track(), lastChatMessageDeliveredToGameOwner(0), lastChatMessageRecivedFromGameOwner(0) { if (SDLNet_Init() < 0) { error = SDL_GetError(); } } Network::~Network() { quitGame(); SDLNet_UDP_Close(socket); SDLNet_Quit(); } void Network::step() { while (receive()) { handleMessage(msg.read8(), msg.getPacket().address); } Uint32 now = SDL_GetTicks(); while (!chatBuffer.empty() && chatBuffer.back().timestamp + CHAT_MESSAGE_TIMEOUT <= now) { chatBuffer.pop_back(); } if (!connections.empty()) { if (connectionsIt == connections.end()) { connectionsIt = connections.begin(); } const IPaddress& address = connectionsIt->first; Connection& connection = connectionsIt->second; if (connection.lastRecive + CONNECTION_TIMEOUT <= now) { // Timeout. onConnectionTimeout(address); gameList.update(address, 0); delete connection.gameInfo; connections.erase(connectionsIt++); } else { if (connection.gameInfo && connection.lastRecive + GAMEINFO_TIMEOUT <= now) { gameList.update(address, 0); delete connection.gameInfo; connection.gameInfo = 0; } if (connectionType == NET_LOBBY) { if (connection.gameInfo == 0 && !chatBuffer.empty() && connection.lastChatMessageDelivered < lastChatMessage) { if (connection.lastSend + CHAT_SEND_INTERVAL <= now) { // Send unreceived message. sendLobbyMessage(connection, address); } } if (connection.lastSend + GAMEINFO_SEND_INTERVAL <= now) { // Request game info from game owners. // This also helps avoid timeout. // We send this message for all connections because // we don't know if they have become a game owner // without us notice. sendLobbyMessage(connection, address); } } else if (connectionType == NET_GAME_OWNER) { if (connection.lastRecive + PING_SEND_START <= now) { if (connection.lastSend + PING_SEND_INTERVAL <= now) { msg.reset(); msg.write8(MSG_PING); msg.write32(now); send(address); connection.lastSend = SDL_GetTicks(); } } } ++connectionsIt; } } chat.update(); if (connectionType == NET_GAME_OWNER) { // Timeouts std::map::iterator it; for (it = gameClients.begin(); it != gameClients.end();) { GamePlayer& gp = it->second; std::map::iterator tmp = it++; if (gp.lastRecive + GAME_TIMEOUT <= now) { erasePlayer(tmp, "timeout"); } } std::map::iterator jit; for (jit = joinInProgress.begin(); jit != joinInProgress.end();) { JoinInProgress& jip = jit->second; std::map::iterator tmp = jit++; if (jip.timeout <= now) { reservedCars &= ~jip.reservedCars; reservedPlayers -= jip.reservedPlayers; joinInProgress.erase(tmp); } } // Send new state info. if (lastGameSent + GAME_STATE_SEND_INTERVAL <= now) { std::map::iterator it; for (it = gameClients.begin(); it != gameClients.end(); ++it) { GamePlayer& gp = it->second; const IPaddress& address = it->first; if (gp.gameStateId != gameStateId) { msg.reset(); if (gameStateId == NETWORK_GAME_STATE_RACE) { msg.write8(MSG_NEW_STATE_RACE); msg.write32(now); msg.write32(gameStateCount); msg.write8(game.laps); msg.write8(game.collisions); map.write(msg); msg.write8(raceCars.size()); for (std::size_t i = 0; i < raceCars.size(); ++i) { NetworkRaceCar& nrc = *raceCars[i]; msg.write8(nrc.car); msg.write8(nrc.player == &gp ? nrc.driver : std::numeric_limits::max()); } } else if (gameStateId == NETWORK_GAME_STATE_TIMES || gameStateId == NETWORK_GAME_STATE_POINTS) { msg.write8(gameStateId == NETWORK_GAME_STATE_TIMES ? MSG_NEW_STATE_TIMES : MSG_NEW_STATE_POINTS); msg.write32(now); msg.write32(gameStateCount); msg.write8(game.tracks); msg.write8(game.track); msg.write8(raceCars.size()); std::vector::iterator it; for (it = raceCars.begin(); it != raceCars.end(); ++it) { NetworkRaceCar& car = **it; msg.write8(car.car); msg.writeString(car.name); msg.write32(gameStateId == NETWORK_GAME_STATE_TIMES ? car.time : car.points); } } else { // This should never happen! assert("invalid game state id"); continue; } send(address); } } lastGameSent = SDL_GetTicks(); } // Send race state. if (gameStateId == NETWORK_GAME_STATE_RACE && newTrackInfoReady) { std::map::iterator it; for (it = gameClients.begin(); it != gameClients.end(); ++it) { GamePlayer& gp = it->second; const IPaddress& address = it->first; if (gp.gameStateId == gameStateId) { msg.reset(); msg.write8(MSG_RACE_STATE); msg.write32(now); msg.write8(track->getCarCount()); msg.write8(removedCars.size()); for (std::size_t i = removedCars.size() - 1; i < removedCars.size(); --i) { msg.write8(removedCars[i]); } msg.write32(raceTick); Uint32 stateTick = std::max(gp.lastTick - gp.lastTick % NETWORK_STEP, newTrackStateTick - (TRACK_BUFFER_SIZE - 1) * NETWORK_STEP); msg.write32(stateTick); msg.write32(gp.ticksTooFast); msg.write32(gp.lastTimestamp); const TrackState& trackState = trackStateBuffer[(newTrackStateTick - stateTick) / NETWORK_STEP]; trackState.write(msg, raceCars.size()); for (Uint32 tick = stateTick; tick <= raceTick; ++tick) { msg.write32(inputBuffer[raceTick - tick]); } writeChatMessage(gp); send(address); } } newTrackInfoReady = false; newTrackInfoCounter = 0; } else if (gameStateId == NETWORK_GAME_STATE_TIMES || gameStateId == NETWORK_GAME_STATE_POINTS) { for (it = gameClients.begin(); it != gameClients.end(); ++it) { GamePlayer& gp = it->second; IPaddress address = it->first; bool haveMessagesToSend = !chatBuffer.empty() && gp.lastChatMessageDelivered < lastChatMessage; if (gp.lastSent + (haveMessagesToSend ? GAME_CHAT_INTERVAL : GAME_NO_CHAT_INTERVAL) <= now) { msg.reset(); msg.write8(MSG_GAME_CHAT_INFO); msg.write32(now); writeChatMessage(gp); send(address); gp.lastSent = SDL_GetTicks(); } } } } else if (connectionType == NET_GAME_PLAYER) { // Timeout if (lastGameRecive + GAME_TIMEOUT <= now) { setNewGameState(new ErrorState("End of game", "The network game has timed out", new NetworkMenu(this))); } else if (newTrackInfoReady) { msg.write8(MSG_RACE_INPUT); msg.write32(now); writeChatMessage(); msg.write32(raceTick); for (std::size_t i = 0; i < INPUT_BUFFER_SIZE; ++i) { msg.write32(inputBuffer[i]); } send(gameOwner); newTrackInfoReady = false; newTrackInfoCounter = 0; } else if (gameStateId == NETWORK_GAME_STATE_TIMES || gameStateId == NETWORK_GAME_STATE_POINTS) { bool haveMessagesToSend = !chatBuffer.empty() && lastChatMessageDeliveredToGameOwner < lastChatMessage; if (lastGameSent + (haveMessagesToSend ? GAME_CHAT_INTERVAL : GAME_NO_CHAT_INTERVAL) <= now) { msg.write8(MSG_GAME_CHAT_ADD); msg.write32(now); writeChatMessage(); send(gameOwner); lastGameSent = SDL_GetTicks(); } } } } void Network::updateTrack(bool noInput) { if (gameStateId != NETWORK_GAME_STATE_RACE) { return; } assert(track); Uint32& prevInput = inputBuffer[0]; Uint32& input = inputBuffer.next(); // Copy previous input. input = prevInput; // Collect local input. for (std::size_t i = 0; i < localCars.size(); ++i) { assert(i < localCars.size()); const NetworkRaceCar& car = localCars[i]; if (car.id >= 0 && car.human) { int carInput = noInput ? 0 : StateManager::getInstance().getDriver(car.driver)->drive(car.id, *track); input = (input & ~(0xF << (4 * car.id))) | (carInput << (4 * car.id)); } } track->update(TRACK_TIME_STEP * 0.001, &input); ++raceTick; if (raceTick % NETWORK_STEP == 0) { if (connectionType == NET_GAME_OWNER) { TrackState& trackState = trackStateBuffer.next(); track->save(trackState); newTrackStateTick = raceTick; } } ++newTrackInfoCounter; if (input != prevInput || newTrackInfoCounter >= NETWORK_STEP) { newTrackInfoReady = true; } } void Network::setStatus(const std::string& str) { Surface s(str, BIG_FONT, 123456, TEXT_BORDER_COLOR); statusRenderObject.setSurface(s); statusRenderObject.moveTo(GAME_WIDTH - s.width() - 2, 3); } const std::string& Network::getError() const { return error; } void Network::handleMessage(Uint8 msgType, IPaddress address) { Uint32 timestamp = msg.read32(); Uint32 now = SDL_GetTicks(); switch (msgType) { case MSG_PING: case MSG_PONG: case MSG_LOBBY: case MSG_GAME_INFO: { std::map::iterator it = connections.find(address); if (it == connections.end()) { return; } Connection& connection = it->second; if (timestamp <= connection.lastTimestamp) { return; } connection.lastTimestamp = timestamp; connection.lastRecive = now; switch (msgType) { case MSG_PING: { msg.reset(); msg.write8(MSG_PONG); msg.write32(now); send(address); connection.lastSend = SDL_GetTicks(); break; } case MSG_LOBBY: { if (connectionType == NET_LOBBY) { if (connection.gameInfo) { delete connection.gameInfo; connection.gameInfo = 0; gameList.update(address, 0); } Uint32 lastChatMessageDelivered = msg.read32(); if (lastChatMessageDelivered > connection.lastChatMessageDelivered) { connection.lastChatMessageDelivered = lastChatMessageDelivered; } Uint32 chatMessage = msg.read32(); if (chatMessage != 0 && chatMessage > connection.lastChatMessageRecived) { connection.lastChatMessageRecived = chatMessage; chat.addMessage(msg.readString(), CHAT_COLOR2); } if (chatMessage != 0) { // It's necessary to send this message here // to make sure the sender knows that we // have received the chat message. sendLobbyMessage(connection, address); } } else if (connectionType == NET_GAME_OWNER) { if (blacklist.count(address.host)) { return; } msg.reset(); msg.write8(MSG_GAME_INFO); msg.write32(SDL_GetTicks()); msg.write32(timestamp); msg.writeString(game.name); msg.write8(reservedPlayers); msg.write8(game.maxPlayers); msg.write8(game.tournament); msg.write8(game.collisions); send(address); connection.lastSend = SDL_GetTicks(); } break; } case MSG_GAME_INFO: { if (!connection.gameInfo) { connection.gameInfo = new NetworkGameInfo(); } connection.gameInfo->ping = now - msg.read32(); connection.gameInfo->name = msg.readString(); connection.gameInfo->players = msg.read8(); connection.gameInfo->maxPlayers = msg.read8(); connection.gameInfo->tournament = msg.read8(); connection.gameInfo->collisions = msg.read8(); gameList.update(address, connection.gameInfo); } } break; } case MSG_JOIN_START: { if (blacklist.count(address.host)) { return; } int maxPlayers = msg.read8(); if (maxPlayers < 1 || maxPlayers > 8) { return; } std::map::iterator it = joinInProgress.find(address); if (it != joinInProgress.end()) { // Throw away the old join progress. JoinInProgress& jip = it->second; if (timestamp <= jip.lastTimestamp) { return; } reservedCars &= ~jip.reservedCars; reservedPlayers -= jip.reservedPlayers; joinInProgress.erase(it); } if (reservedPlayers < game.maxPlayers) { JoinInProgress& jip = joinInProgress[address]; jip.timeout = now + JOIN_TIMEOUT; jip.reservedPlayers = 1; ++reservedPlayers; jip.reservedCars = 0; jip.lastTimestamp = timestamp; jip.maxPlayers = maxPlayers; jip.noMore = false; sendJoinStatus(address, JOIN_MORE_CARS, 0, reservedCars); } else { sendJoinStatus(address, JOIN_ERROR, 0, reservedCars); } break; } case MSG_JOIN_CAR: { std::map::iterator it = joinInProgress.find(address); if (it == joinInProgress.end()) { return; } JoinInProgress& jip = it->second; if (timestamp <= jip.lastTimestamp) { return; } jip.lastTimestamp = timestamp; Uint8 car = msg.read8(); if (car >= 8) { return; } jip.timeout = now + JOIN_TIMEOUT; Uint8 carBit = 1 << car; if (jip.noMore) { sendJoinStatus(address, JOIN_NO_MORE_CARS, jip.cars.size(), reservedCars); } else if ((reservedCars & carBit) != 0) { // this car has already been taken sendJoinStatus(address, JOIN_MORE_CARS, jip.cars.size(), reservedCars); } else { jip.cars.push_back(car); jip.reservedCars |= carBit; reservedCars |= carBit; if (jip.reservedPlayers < game.maxPlayersPerClient && reservedPlayers < game.maxPlayers && jip.reservedPlayers < jip.maxPlayers) { ++reservedPlayers; ++jip.reservedPlayers; sendJoinStatus(address, JOIN_MORE_CARS, jip.cars.size(), reservedCars); } else { jip.noMore = true; sendJoinStatus(address, JOIN_NO_MORE_CARS, jip.cars.size(), reservedCars); } } break; } case MSG_JOIN_END: { std::map::iterator it = joinInProgress.find(address); if (it == joinInProgress.end()) { // Check if player has already joined. std::map::iterator it = gameClients.find(address); if (it != gameClients.end()) { GamePlayer& gp = it->second; sendJoinStatus(address, JOIN_HAS_ENDED, gp.cars.size(), reservedCars); } return; } JoinInProgress& jip = it->second; if (timestamp <= jip.lastTimestamp) { return; } GamePlayer& p = gameClients[address]; for (std::size_t i = 0; i < jip.cars.size(); ++i) { NetworkRaceCar nrc; nrc.player = &p; nrc.car = jip.cars[i]; nrc.name = uniqueName(msg.readString()); nrc.id = -1; nrc.time = 0; nrc.points = 0; nrc.driver = i; nrc.human = true; p.cars.push_back(nrc); sendChatMessage(nrc.name + " has joined!", nrc.car); } // The client has joined successfully! sendJoinStatus(address, JOIN_HAS_ENDED, jip.cars.size(), reservedCars); p.gameStateId = NETWORK_GAME_STATE_BLANK; p.lastTick = 0; p.lastChatMessageDelivered = lastChatMessage; p.lastRecive = now; joinInProgress.erase(address); break; } case MSG_JOIN_QUIT: { std::map::iterator it = joinInProgress.find(address); if (it == joinInProgress.end()) { return; } JoinInProgress& jip = it->second; if (timestamp <= jip.lastTimestamp) { return; } reservedCars &= ~jip.reservedCars; reservedPlayers -= jip.reservedPlayers; joinInProgress.erase(it); break; } case MSG_JOIN_STATUS: { if (connectionType == NET_LOBBY) { if (joinStatus.address == address) { if (timestamp <= joinStatus.lastTimestamp) { return; } joinStatus.updated = true; joinStatus.lastTimestamp = timestamp; joinStatus.state = msg.read8(); joinStatus.joinedPlayers = msg.read8(); joinStatus.reservedCars = msg.read8(); if (joinStatus.state > JOIN_ERROR || joinStatus.joinedPlayers > 7) { joinStatus.state = JOIN_ERROR; } if (joinStatus.state == JOIN_HAS_ENDED) { int n = msg.read8(); chatInput.clear(); if (n > 8 || n < 0) { chatInput.addPlayer("error"); } else { for (int i = 0; i < n; ++i) { std::string name = msg.readString(); int car = msg.read8(); chatInput.addPlayer(name, car); } } } } } break; } case MSG_NEW_STATE_RACE: case MSG_NEW_STATE_TIMES: case MSG_NEW_STATE_POINTS: case MSG_RACE_STATE: case MSG_END_GAME: case MSG_GAME_CHAT_INFO: { if (connectionType != NET_GAME_PLAYER) { return; } if (address != gameOwner) { return; } if (timestamp <= lastGameTimestamp) { return; } lastGameTimestamp = timestamp; lastGameRecive = now; if (msgType == MSG_NEW_STATE_RACE || msgType == MSG_NEW_STATE_TIMES || msgType == MSG_NEW_STATE_POINTS) { Uint32 newGameStateCount = msg.read32(); if (newGameStateCount <= gameStateCount) { if (newGameStateCount == gameStateCount) { // Resend game state info in case the game // owner has not received that information. msg.reset(); msg.write8(MSG_STATE_STATUS); msg.write32(SDL_GetTicks()); msg.write8(gameStateId); msg.write32(gameStateCount); send(address); lastGameSent = SDL_GetTicks(); } return; } switch (msgType) { case MSG_NEW_STATE_RACE: { Uint8 laps = msg.read8(); if (laps > 9) { return; } bool collisions = msg.read8() != 0; map.read(msg); if (!map.isValid()) { return; } std::size_t carCount = msg.read8(); if (carCount < 1 || carCount > 8) { return; } std::vector cars(carCount); std::vector drivers(carCount); localCars.clear(); inputMask = 0; for (std::size_t i = 0; i < carCount; ++i) { Uint8 car = msg.read8(); if (car >= 8) { return; } cars[i] = car; Uint8 driver = msg.read8(); if (driver < 8) { drivers[i] = StateManager::getInstance().getDriver(driver); NetworkRaceCar nrc; nrc.player = 0; nrc.car = car; nrc.id = i; nrc.driver = driver; nrc.time = 0; nrc.points = 0; nrc.human = true; localCars.push_back(nrc); inputMask |= 0xF << (i * 4); } } // Start race! std::vector names(drivers.size()); RaceState* raceState = new RaceState(map, RaceSetup(drivers, cars, names, laps, 1, false, collisions), this); setNewGameState(raceState); track = raceState->getTrack(); for (std::size_t i = 0; i < TRACK_BUFFER_SIZE; ++i) { trackStateBuffer[i].setCars(carCount); track->save(trackStateBuffer[i]); } for (std::size_t i = 0; i < INPUT_BUFFER_SIZE; ++i) { inputBuffer[i] = 0; } gameStateId = NETWORK_GAME_STATE_RACE; raceTick = 0; newTrackInfoReady = false; newTrackInfoCounter = 0; newTrackStateTick = 0; break; } case MSG_NEW_STATE_TIMES: case MSG_NEW_STATE_POINTS: { Uint8 tracks = msg.read8(); Uint8 currentTrack = msg.read8(); Uint8 n = msg.read8(); if (n > 8) { return; } std::vector drivers(n); std::vector cars(n); std::vector names(n); std::vector values(n); for (int i = 0; i < n; ++i) { cars[i] = msg.read8() % 8; names[i] = msg.readString(); values[i] = msg.read32(); } RaceSetup raceSetup(drivers,cars ,names, 0, tracks, tracks > 1, 0); raceSetup.setTrack(currentTrack); void (RaceSetup::*setValue)(int, int); if (msgType == MSG_NEW_STATE_TIMES) { gameStateId = NETWORK_GAME_STATE_TIMES; setValue = &RaceSetup::setPlayerTime; } else { gameStateId = NETWORK_GAME_STATE_POINTS; setValue = &RaceSetup::setPlayerPoints; } for (int i = 0; i < n; ++i) { (raceSetup.*setValue)(i, values[i]); } setNewGameState(new ResultState(raceSetup, this, gameStateId == NETWORK_GAME_STATE_POINTS)); break; } } gameStateCount = newGameStateCount; msg.reset(); msg.write8(MSG_STATE_STATUS); msg.write32(SDL_GetTicks()); msg.write8(gameStateId); msg.write32(gameStateCount); send(address); lastGameSent = SDL_GetTicks(); } else if (msgType == MSG_RACE_STATE) { if (gameStateId != NETWORK_GAME_STATE_RACE) { return; } Uint8 carCount = msg.read8(); Uint8 removedCarCount = msg.read8(); Uint8 removedCar; for (int i = 0; i < removedCarCount; ++i) { removedCar = msg.read8(); if (carCount < track->getCarCount()) { eraseCar(removedCar); } } Uint32 tick = msg.read32(); Uint32 stateTick = msg.read32(); if (stateTick > tick) { return; } // Last message received by game owner. Uint32 ticksTooFast = msg.read32(); Uint32 myGameOwnerTimestamp = msg.read32(); if (ticksTooFast > 0 && myGameOwnerTimestamp > lastSlowDown) { // We are running too fast! raceTick -= ticksTooFast; lastSlowDown = now; } // It's important that we don't use else-if here because // the above if statement can make raceTick greater than // tick if ticksTooFast is faulty. It is also possible // that we are running too slow even though the game // owner noticed we ran too fast only recently. if (tick > raceTick) { // We are running too slow! raceTick = tick; } if (raceTick - stateTick > INPUT_BUFFER_SIZE) { // We can't handle this in a good way so it's better // to do nothing and hope the sync improves. return; } // We use one of the track states in the // buffer to avoid unnecessary reallocations. TrackState& trackState = trackStateBuffer[0]; trackState.read(msg, track->getCarCount()); track->restore(trackState); // This input might not look useful but it will be used in the // second loop below if stateTick == tick && tick < raceTick. Uint32 input = msg.read32(); for (Uint32 t = stateTick + 1; t <= tick; ++t) { Uint32 i = raceTick - t; input = msg.read32(); inputBuffer[i] = (inputBuffer[i] & inputMask) | (input & ~inputMask); } for (Uint32 t = tick + 1; t <= raceTick; ++t) { Uint32 i = raceTick - t; inputBuffer[i] = (inputBuffer[i] & inputMask) | (input & ~inputMask); } for (Uint32 t = stateTick + 1; t <= raceTick; ++t) { Uint32 i = raceTick - t; track->update(TRACK_TIME_STEP * 0.001, &inputBuffer[i]); } readChatMessage(); } else if (msgType == MSG_END_GAME) { setNewGameState(new ErrorState("End of game", "The network game has ended", new NetworkMenu(this))); } else if (msgType == MSG_GAME_CHAT_INFO) { if (readChatMessage()) { // Force message to be sent as soon as possible to // inform the game owner that we have received the // message. lastGameSent = 0; } } break; } case MSG_STATE_STATUS: case MSG_RACE_INPUT: case MSG_QUIT_GAME: case MSG_GAME_CHAT_ADD: { if (connectionType != NET_GAME_OWNER) { return; } std::map::iterator it = gameClients.find(address); if (it == gameClients.end()) { break; } GamePlayer& gp = it->second; if (timestamp <= gp.lastTimestamp) { return; } gp.lastTimestamp = timestamp; gp.lastRecive = now; if (msgType == MSG_STATE_STATUS) { int newPlayerGameStateId = msg.read8(); Uint32 playerStateCount = msg.read32(); if (gp.gameStateId != newPlayerGameStateId && playerStateCount == gameStateCount) { gp.gameStateId = newPlayerGameStateId; } } else if (msgType == MSG_RACE_INPUT) { if (gameStateId != NETWORK_GAME_STATE_RACE) { return; } readChatMessage(gp); Uint32 tick = msg.read32(); gp.ticksTooFast = tick > raceTick ? tick - raceTick : 0; if (gp.ticksTooFast > 0) { // No point handling out of sync input. // Better wait until we are in sync. return; } if (tick < gp.lastTick) { // This could happen when the player adjusts for out // of sync so we just modify the last tick value so // that it will work in the calculations that follow gp.lastTick = tick; } Uint32 stepBackNeeded = raceTick - gp.lastTick; Uint32 stepBackPossible = (TRACK_BUFFER_SIZE - 1) * NETWORK_STEP + raceTick % NETWORK_STEP; Uint32 stepBack = std::min(stepBackNeeded, stepBackPossible); Uint32 restoreTick = raceTick - stepBack; Uint32 restoreStateIndex = raceTick / NETWORK_STEP - restoreTick / NETWORK_STEP; track->restore(trackStateBuffer[restoreStateIndex]); // Read player input. for (Uint32 i = raceTick - tick; i < stepBack; ++i) { Uint32 preInputValue = inputBuffer[i]; inputBuffer[i] = (inputBuffer[i] & ~gp.inputMask) | (msg.read32() & gp.inputMask); if (inputBuffer[i] != preInputValue) { newTrackInfoReady = true; } } for (Uint32 i = 0; i < raceTick - tick; ++i) { inputBuffer[i] = (inputBuffer[i] & ~gp.inputMask) | (inputBuffer[raceTick - tick] & gp.inputMask); } stepBack += (restoreTick % NETWORK_STEP); raceTick -= stepBack; for (int i = stepBack - 1; i >= 0; --i) { track->update(TRACK_TIME_STEP * 0.001, &inputBuffer[i]); ++raceTick; if (raceTick % NETWORK_STEP == 0) { --restoreStateIndex; TrackState& trackState = trackStateBuffer[restoreStateIndex]; track->save(trackState); } } gp.lastTick = tick; } else if (msgType == MSG_QUIT_GAME) { erasePlayer(it); } else if (msgType == MSG_GAME_CHAT_ADD) { readChatMessage(gp); } break; } } } void Network::sendLobbyMessage(Connection& connection, const IPaddress& address) { msg.reset(); msg.write8(MSG_LOBBY); msg.write32(SDL_GetTicks()); msg.write32(connection.lastChatMessageRecived); if (!chatBuffer.empty() && connection.lastChatMessageDelivered < lastChatMessage) { Uint32 messageToSend = std::max(connection.lastChatMessageDelivered, static_cast(lastChatMessage - chatBuffer.size())) + 1; msg.write32(messageToSend); msg.writeString(chatBuffer[lastChatMessage - messageToSend].text); } else { msg.write32(0); } send(address); connection.lastSend = SDL_GetTicks(); } void Network::onConnectionTimeout(const IPaddress& addr) { } void Network::send(IPaddress address) { UDPpacket& packet = msg.getPacket(); packet.address = address; SDLNet_UDP_Send(socket, -1, &packet); } bool Network::receive() { msg.reset(); UDPpacket& packet = msg.getPacket(); return SDLNet_UDP_Recv(socket, &packet) == 1; } void Network::setGameState(GameState* newGameState, bool chatOn, bool gameListOn) { this->chatOn = chatOn; gameState = newGameState; if (gameState) { gameState->addRenderObject(statusRenderObject); } chat.setGameState(chatOn ? gameState : 0); chatInput.setGameState(chatOn && chat.isEnabled() ? gameState : 0); gameList.setGameState(gameListOn ? gameState : 0); } void Network::enableChatInput(bool chatInputOn) { chatInput.setGameState(chatInputOn && chat.isEnabled() ? gameState : 0); } bool Network::onEvent(const SDL_Event& event, bool isDriving) { if (event.type == SDL_KEYDOWN) { SDLKey key = event.key.keysym.sym; if (key == SDLK_F5 && connectionType == NET_LOBBY) { refreshAsNeeded(); } else if (!chatOn) { return false; } else if (key == SDLK_TAB) { chat.toggle(); chatInput.setGameState(chat.isEnabled() ? gameState : 0); return true; } else if (chat.isEnabled()) { // Don't start inputting a new chat message if the key is used for driving. if (isDriving && !chatInput.isVisible()) { assert(gameStateId == NETWORK_GAME_STATE_RACE); for (std::size_t i = 0; i < localCars.size(); ++i) { const NetworkRaceCar& car = localCars[i]; if (car.id >= 0 && car.human && StateManager::getInstance().getDriver(car.driver)->isUsingKey(key)) { // Wait a small time period after the car has finished to avoid accidental chat input. const double CHAT_KEY_DELAY = 1.0; // seconds if (!track->isCarFinished(car.id) || track->getCarFinishTime(car.id) + CHAT_KEY_DELAY > track->getRaceTime()) { // The key is used for driving! return false; } } } } if (chatInput.onEvent(event)) { if (chatInput.isReady()) { std::string text = chatInput.fetch(); std::istringstream iss(text); iss.ignore(1000, ':'); // ignore username iss.ignore(); // ignore space if (iss.get() == '/') { if (connectionType == NET_LOBBY) { chat.addMessage("No commands allowed here!"); } else if (connectionType == NET_GAME_PLAYER) { chat.addMessage("Commands can only be used by the \"game owner\"!"); } else { std::string command; if (iss >> command) { if (command == "help") { chat.addMessage("Commands: /ban /kick /kickall /mute /list /color"); } else if (command == "list") { std::vector names; std::vector::iterator itCar; for (itCar = localCars.begin(); itCar != localCars.end(); ++itCar) { names.push_back(itCar->name); } std::map::iterator itPlayer; for (itPlayer = gameClients.begin(); itPlayer != gameClients.end(); ++itPlayer) { GamePlayer& gp = itPlayer->second; for (itCar = gp.cars.begin(); itCar != gp.cars.end(); ++itCar) { names.push_back(itCar->name); } } std::sort(names.begin(), names.end()); std::string message; for (std::size_t i = 0; i < names.size(); ++i) { message += (message.empty() ? "" : ", ") + names[i]; } chat.addMessage(message); } else if (command == "color" || command == "colour" || command == "ban" || command == "kick" || command == "kickall" || command == "mute") { std::map::iterator itPlayer = gameClients.end(); std::vector::iterator itCar; bool carFound = false; std::string username; iss >> username; std::string token; while (iss >> token) { username += " " + token; } for (itCar = localCars.begin(); itCar != localCars.end(); ++itCar) { if (itCar->name == username) { carFound = true; break; } } if (!carFound) { for (itPlayer = gameClients.begin(); itPlayer != gameClients.end(); ++itPlayer) { GamePlayer& gp = itPlayer->second; for (itCar = gp.cars.begin(); itCar != gp.cars.end(); ++itCar) { if (itCar->name == username) { carFound = true; break; } } if (carFound) { break; } } } if (!carFound) { if (username.empty()) { chat.addMessage("Missing username!"); } else { chat.addMessage("No user named " + username +"!"); } } else if (command == "color" || command == "colour") { chat.addMessage(username + " drives a " + carColorName(itCar->car) + " car", carColor(itCar->car)); } else if (command == "ban") { if (itPlayer != gameClients.end()) { GamePlayer& gp = itPlayer->second; itCar = gp.cars.begin(); for (itCar = gp.cars.begin(); itCar != gp.cars.end();) { itCar = eraseCar(itCar, "banned"); } blacklist.insert(itPlayer->first.host); } else { chat.addMessage("You can't ban yourself!"); } } else if (command == "kick") { if (itPlayer != gameClients.end()) { eraseCar(itCar, "kicked"); } else { std::size_t localHumanPlayers = 0; for (std::size_t i = 0; i < localCars.size(); ++i) { if (localCars[i].human) { ++localHumanPlayers; } } if (!itCar->human || localHumanPlayers > 1) { eraseCar(itCar, "kicked"); } else { chat.addMessage("You need at least one player!"); } } } else if (command == "kickall") { if (itPlayer != gameClients.end()) { GamePlayer& gp = itPlayer->second; itCar = gp.cars.begin(); for (itCar = gp.cars.begin(); itCar != gp.cars.end();) { itCar = eraseCar(itCar, "kicked"); } } else { chat.addMessage("You need at least one player!"); } } else if (command == "mute") { if (itPlayer != gameClients.end()) { GamePlayer& gp = itPlayer->second; gp.mute = !gp.mute; sendChatMessage(username + " has been " + (gp.mute ? "" : "un") + "muted!", itCar->car); } else { chat.addMessage("You can't mute yourself!"); } } } else { chat.addMessage("Invalid command! Try /help"); } } } } else { sendChatMessage(text, chatInput.fetchCar()); } } return true; } } } return false; } void Network::setGame(const std::string& name, int maxPlayers, int maxPlayersPerClient, int laps, int tracks, int collisions) { game.name = name; game.maxPlayers = maxPlayers; game.maxPlayersPerClient = maxPlayersPerClient; game.laps = laps; game.tracks = tracks; game.tournament = tracks > 1; game.collisions = collisions; } void Network::createGame(Uint8 reservedCars, std::vector cars) { chat.clear(); chatInput.clear(); chatBuffer.clear(); removedCars.clear(); gameClients.clear(); joinInProgress.clear(); gameClients.clear(); localCars.clear(); connectionType = NET_GAME_OWNER; gameStateId = NETWORK_GAME_STATE_RACE; // Start race right away. std::vector freeCars(8); for (int i = 0; i < 8; ++i) { freeCars[i] = i; } for (std::size_t i = 0; i < cars.size(); ++i) { NetworkRaceCar nrc; nrc.player = 0; nrc.name = Settings::players.humanNames[i]; nrc.car = cars[i]; nrc.id = -1; nrc.driver = i; nrc.time = 0; nrc.points = 0; nrc.human = true; localCars.push_back(nrc); freeCars.erase(std::remove(freeCars.begin(), freeCars.end(), nrc.car), freeCars.end()); chatInput.addPlayer(nrc.name, nrc.car); } std::random_shuffle(freeCars.begin(), freeCars.end()); // Add computer players for (int i = 0; i < Settings::network.computerPlayers && i < int(freeCars.size()); ++i) { NetworkRaceCar nrc; nrc.player = 0; nrc.name = Settings::players.computerNames[i]; nrc.car = freeCars[i]; nrc.id = -1; nrc.driver = i; nrc.time = 0; nrc.points = 0; nrc.human = false; localCars.push_back(nrc); cars.push_back(nrc.car); reservedCars |= 1 << nrc.car; } reservedPlayers = cars.size(); this->reservedCars = reservedCars; gameStateCount = 0; startRace(); } GameList& Network::getGameList() { return gameList; } void Network::sendJoinStart() { msg.reset(); msg.write8(MSG_JOIN_START); msg.write32(SDL_GetTicks()); msg.write8(Settings::network.players); send(joinStatus.address); } void Network::sendJoinCar(int car) { msg.reset(); msg.write8(MSG_JOIN_CAR); msg.write32(SDL_GetTicks()); msg.write8(car); send(joinStatus.address); } void Network::sendJoinEnd() { msg.reset(); msg.write8(MSG_JOIN_END); msg.write32(SDL_GetTicks()); for (int i = 0; i < 8; ++i) { msg.writeString(Settings::players.humanNames[i]); } send(joinStatus.address); } void Network::join(const IPaddress& address) { joinStatus.updated = false; joinStatus.address = address; joinStatus.state = JOIN_NOT_STARTED; joinStatus.joinedPlayers = 0; joinStatus.reservedCars = 0; joinStatus.lastTimestamp = 0; } void Network::sendJoinStatus(const IPaddress& address, int state, Uint8 joinedPlayers, Uint8 reservedCars) { msg.reset(); msg.write8(MSG_JOIN_STATUS); msg.write32(SDL_GetTicks()); msg.write8(state); msg.write8(joinedPlayers); msg.write8(reservedCars); if (state == JOIN_HAS_ENDED) { GamePlayer& gp = gameClients[address]; msg.write8(gp.cars.size()); for (std::size_t i = 0; i < gp.cars.size(); ++i) { msg.writeString(gp.cars[i].name); msg.write8(gp.cars[i].car); } } send(address); } bool Network::getJoinStatus(JoinStatus& status) { status = joinStatus; joinStatus.updated = false; return status.updated; } GameState* Network::getNewGameState() { GameState* gs = newGameState; newGameState = 0; return gs; } void Network::setNewGameState(GameState* gs) { delete newGameState; newGameState = gs; } void Network::join() { connectionType = NET_GAME_PLAYER; gameOwner = joinStatus.address; gameStateId = NETWORK_GAME_STATE_BLANK; gameStateCount = 0; lastGameRecive = SDL_GetTicks(); // Instead calling connections.clear() here we simply let them // time out to make sure all the necessary cleanup is made. chat.clear(); // chatInput is cleared elsewhere. chatBuffer.clear(); setNewGameState(new NetworkBlankState(this)); } bool raceCarSorter(const NetworkRaceCar* c1, const NetworkRaceCar* c2) { int t1 = c1->time > 0 ? c1->time : std::numeric_limits::max(); int t2 = c2->time > 0 ? c2->time : std::numeric_limits::max(); return t1 > t2; } void Network::startRace() { // Add all cars. raceCars.clear(); for (std::size_t i = 0; i < localCars.size(); ++i) { raceCars.push_back(&localCars[i]); } std::map::iterator it; for (it = gameClients.begin(); it != gameClients.end(); ++it) { GamePlayer& gp = it->second; std::vector& nrc = gp.cars; for (std::size_t i = 0; i < nrc.size(); ++i) { raceCars.push_back(&nrc[i]); } gp.inputMask = 0; gp.lastTick = 0; } // Sort by time of previous race. std::sort(raceCars.begin(), raceCars.end(), raceCarSorter); // Update and collect information. std::vector drivers(raceCars.size()); std::vector cars(raceCars.size()); std::vector names(raceCars.size()); for (std::size_t i = 0; i < raceCars.size(); ++i) { NetworkRaceCar& car = *raceCars[i]; car.id = i; cars[i] = car.car; names[i] = car.name; if (car.player) { car.player->inputMask |= 0xF << (i * 4); } else { assert(car.driver < 8); if (car.human) { drivers[i] = StateManager::getInstance().getDriver(car.driver); } else { int difficulty = Settings::players.computerDifficulties[car.driver]; if (difficulty < 0) { difficulty = Settings::network.computerDifficulty; } drivers[i] = &AIDriver::getInstance(difficulty); } } } map = Map::generate(); // Start new race! ++gameStateCount; gameStateId = NETWORK_GAME_STATE_RACE; RaceSetup raceSetup(drivers, cars, names, Settings::network.laps, Settings::network.tracks, Settings::network.tournament, Settings::network.collisions); raceSetup.setTrack(game.track); for (std::size_t i = 0; i < raceCars.size(); ++i) { raceSetup.setPlayerPoints(raceCars[i]->id, raceCars[i]->points); } RaceState* raceState = new RaceState(map, raceSetup, this); setNewGameState(raceState); track = raceState->getTrack(); for (std::size_t i = 0; i < TRACK_BUFFER_SIZE; ++i) { trackStateBuffer[i].setCars(raceCars.size()); track->save(trackStateBuffer[i]); } for (std::size_t i = 0; i < INPUT_BUFFER_SIZE; ++i) { inputBuffer[i] = 0; } raceTick = 0; newTrackInfoReady = true; // Send state as soon as possible! newTrackInfoCounter = 0; newTrackStateTick = 0; } void Network::onRaceFinished(const RaceSetup& rs) { if (connectionType == NET_GAME_OWNER) { setNewGameState(new ResultState(rs, this)); ++gameStateCount; gameStateId = NETWORK_GAME_STATE_TIMES; std::vector::iterator it; for (it = raceCars.begin(); it != raceCars.end(); ++it) { NetworkRaceCar& car = **it; car.time = rs.getPlayerTime(car.id); car.points = rs.getPlayerPoints(car.id); } game.track = rs.getCurrentTrack(); // We call refresh here because finishing a race is not something that // can happen repeatedly without a user at the keyboard. Fetching the // list while not racing seems to be a good idea to minimize the cpu // usage while racing. refreshAsNeeded(); } } void Network::onShowPoints(const RaceSetup& rs) { if (connectionType == NET_GAME_OWNER) { setNewGameState(new ResultState(rs, this, true)); ++gameStateCount; gameStateId = NETWORK_GAME_STATE_POINTS; } } bool Network::isOwner() const { return connectionType == NET_GAME_OWNER; } void Network::sleep(Uint32 duration) { // The purpose of this "sleep" function is to handle messages more // rapidly in order to get more accurate ping value. if (connectionType != NET_GAME_PLAYER && !connections.empty()) { Uint32 stop = SDL_GetTicks() + duration; while (SDL_GetTicks() < stop) { if (receive()) { handleMessage(msg.read8(), msg.getPacket().address); } else { SDL_Delay(1); } } } else { SDL_Delay(duration); } } void Network::sendChatMessage(const std::string& text, int car) { SentChatMessage cm; cm.text = text; cm.timestamp = SDL_GetTicks(); cm.car = car; chatBuffer.push_front(cm); ++lastChatMessage; if (connectionType != NET_GAME_PLAYER) { chat.addMessage(text, carColor(car)); } } void Network::quitGame() { // For simplicity we just send the message repeatedly and hope the // other end receives it. I have no idea if routers might have some // kind of filter for identical messages that are sent rapidly so // for that reason we update the message each time so that at least // the time data is different. const int QUIT_SEND_COUNT = 8; const int QUIT_SEND_DELAY = 28; if (connectionType == NET_GAME_PLAYER) { for (int i = 0; i < QUIT_SEND_COUNT; ++i) { if (i > 0) { SDL_Delay(QUIT_SEND_DELAY); } msg.reset(); msg.write8(MSG_QUIT_GAME); msg.write32(SDL_GetTicks()); send(gameOwner); } } else if (connectionType == NET_GAME_OWNER) { for (int i = 0; i < QUIT_SEND_COUNT; ++i) { if (i > 0) { SDL_Delay(QUIT_SEND_DELAY); } msg.reset(); msg.write8(MSG_END_GAME); msg.write32(SDL_GetTicks()); std::map::iterator it; for (it = gameClients.begin(); it != gameClients.end(); ++it) { send(it->first); } } } else if (connectionType == NET_LOBBY && (joinStatus.state == JOIN_MORE_CARS || joinStatus.state == JOIN_NO_MORE_CARS)) { for (int i = 0; i < QUIT_SEND_COUNT; ++i) { if (i > 0) { SDL_Delay(QUIT_SEND_DELAY); } msg.reset(); msg.write8(MSG_JOIN_QUIT); msg.write32(SDL_GetTicks()); send(joinStatus.address); } } } void Network::erasePlayer(std::map::iterator it, const std::string& reason) { GamePlayer& gp = it->second; std::vector::iterator carIt; for (carIt = gp.cars.begin(); carIt != gp.cars.end();) { carIt = eraseCar(carIt, reason); } gameClients.erase(it); } std::vector::iterator Network::eraseCar(std::vector::iterator carIt, const std::string& reason) { assert(connectionType == NET_GAME_OWNER); if (carIt->id >= 0) { if (gameStateId == NETWORK_GAME_STATE_RACE) { for (std::size_t i = 0; i < TRACK_BUFFER_SIZE; ++i) { trackStateBuffer[i].removeCar(carIt->id, track->getCarCount()); } for (std::size_t i = 0; i < INPUT_BUFFER_SIZE; ++i) { Uint32 highMask = ~0U << ((carIt->id + 1) * 4); Uint32 lowMask = ~(~0U << (carIt->id * 4)); Uint32 highInput = (inputBuffer[i] & highMask) >> 4; Uint32 lowInput = inputBuffer[i] & lowMask; inputBuffer[i] = highInput | lowInput; } track->removeCar(carIt->id); removedCars.push_back(carIt->id); } raceCars.erase(raceCars.begin() + carIt->id); for (std::size_t i = carIt->id; i < raceCars.size(); ++i) { Uint32* mask; if (raceCars[i]->player) { mask = &raceCars[i]->player->inputMask; } else { mask = &inputMask; } *mask &= ~(0xF << (raceCars[i]->id * 4)); // remove old mask --raceCars[i]->id; *mask |= 0xF << (raceCars[i]->id * 4); // add new mask } } std::string quitMessage = carIt->name + " has quit!"; if (!reason.empty()) { quitMessage += " (" + reason + ")"; } sendChatMessage(quitMessage, carIt->car); reservedCars &= ~(1 << carIt->car); --reservedPlayers; std::vector::iterator nextIt; if (carIt->player) { carIt->player->removedCars.push_back(carIt->car); nextIt = carIt->player->cars.erase(carIt); } else { chatInput.removePlayer(carIt->car); nextIt = localCars.erase(carIt); } // The pointers need to be reinitialized because some // of the old pointers might not be valid any more. for (std::size_t i = 0; i < localCars.size(); ++i) { if (localCars[i].id >= 0) { raceCars[localCars[i].id] = &localCars[i]; } } std::map::iterator it; for (it = gameClients.begin(); it != gameClients.end(); ++it) { GamePlayer& gp = it->second; std::vector& nrc = gp.cars; for (std::size_t i = 0; i < nrc.size(); ++i) { if (nrc[i].id >= 0) { raceCars[nrc[i].id] = &nrc[i]; } } } return nextIt; } void Network::eraseCar(int carId) { assert(connectionType == NET_GAME_PLAYER); if (gameStateId == NETWORK_GAME_STATE_RACE && carId < track->getCarCount()) { track->removeCar(carId); for (std::size_t i = 0; i < INPUT_BUFFER_SIZE; ++i) { Uint32 highMask = ~0U << ((carId + 1) * 4); Uint32 lowMask = ~(~0U << (carId * 4)); Uint32 highInput = (inputBuffer[i] & highMask) >> 4; Uint32 lowInput = inputBuffer[i] & lowMask; inputBuffer[i] = highInput | lowInput; } inputMask = 0; std::vector::iterator carIt; for (carIt = localCars.begin(); carIt != localCars.end();) { if (carIt->id == carId) { carIt = localCars.erase(carIt); } else { if (carIt->id > carId) { --carIt->id; } inputMask |= 0xF << (carIt->id * 4); ++carIt; } } } } void Network::writeChatMessage() { msg.write32(lastChatMessageRecivedFromGameOwner); if (!chatBuffer.empty() && lastChatMessageDeliveredToGameOwner < lastChatMessage) { Uint32 messageToSend = std::max(lastChatMessageDeliveredToGameOwner, static_cast(lastChatMessage - chatBuffer.size())) + 1; msg.write32(messageToSend); SentChatMessage& message = chatBuffer[lastChatMessage - messageToSend]; msg.writeString(message.text); msg.write8(message.car); } else { msg.write32(0); } } void Network::writeChatMessage(const GamePlayer& gp) { msg.write32(gp.lastChatMessageRecived); if (!chatBuffer.empty() && gp.lastChatMessageDelivered < lastChatMessage) { Uint32 messageToSend = std::max(gp.lastChatMessageDelivered, static_cast(lastChatMessage - chatBuffer.size())) + 1; msg.write32(messageToSend); SentChatMessage& message = chatBuffer[lastChatMessage - messageToSend]; msg.writeString(message.text); msg.write8(message.car); } else { msg.write32(0); // send removed cars so that they can be removed from chat list. msg.write8(gp.removedCars.size()); for (std::size_t i = 0; i < gp.removedCars.size(); ++i) { msg.write8(gp.removedCars[i]); } } } void Network::readChatMessage(GamePlayer& gp) { Uint32 lastChatMessageDelivered = msg.read32(); if (lastChatMessageDelivered > gp.lastChatMessageDelivered) { gp.lastChatMessageDelivered = lastChatMessageDelivered; } Uint32 chatMessage = msg.read32(); if (chatMessage != 0 && chatMessage > gp.lastChatMessageRecived) { gp.lastChatMessageRecived = chatMessage; std::string message = msg.readString(); int car = msg.read8(); if (!gp.mute) { // Make sure the car is valid. for (std::size_t i = 0; i < gp.cars.size(); ++i) { if (gp.cars[i].car == car) { sendChatMessage(message, car); break; } } } } } bool Network::readChatMessage() { Uint32 lastChatMessageDelivered = msg.read32(); if (lastChatMessageDelivered > lastChatMessageDeliveredToGameOwner) { lastChatMessageDeliveredToGameOwner = lastChatMessageDelivered; } Uint32 chatMessage = msg.read32(); if (chatMessage != 0) { if (chatMessage > lastChatMessageRecivedFromGameOwner) { lastChatMessageRecivedFromGameOwner = chatMessage; std::string message = msg.readString(); int car = msg.read8(); chat.addMessage(message, carColor(car)); } // Return true even if the chat message has already been received // so that we can act properly in order to inform the game owner // that we have received this message. return true; } else { // Remove cars from char input list. int n = msg.read8(); for (int i = 0; i < n; ++i) { chatInput.removePlayer(msg.read8()); if (chatInput.empty()) { setNewGameState(new ErrorState("End of game", "You have been kicked out!", new NetworkMenu(this))); } } } return false; } std::string Network::uniqueName(const std::string& name) const { std::vector names; std::vector::const_iterator itCar; for (itCar = localCars.begin(); itCar != localCars.end(); ++itCar) { names.push_back(itCar->name); } std::map::const_iterator itPlayer; for (itPlayer = gameClients.begin(); itPlayer != gameClients.end(); ++itPlayer) { const GamePlayer& gp = itPlayer->second; for (itCar = gp.cars.begin(); itCar != gp.cars.end(); ++itCar) { names.push_back(itCar->name); } } std::string uName = name.empty() ? "noname" : name; int n = 1; while (std::find(names.begin(), names.end(), uName) != names.end()) { uName = name + "-" + int2string(++n); } return uName; } void Network::lobby() { if (connectionType != NET_LOBBY) { chat.clear(); chatInput.clear(); chatBuffer.clear(); chatInput.addDefaultPlayers(); } if (connectionType == NET_GAME_PLAYER) { // Let GAME_PLAYER refresh it's list if needed. refreshAsNeeded(); } connectionType = NET_LOBBY; reservedCars = 0; joinStatus = JoinStatus(); gameStateId = 0; gameStateCount = 0; lastGameTimestamp = 0; lastGameSent = 0; track = 0; lastChatMessageDeliveredToGameOwner = 0; lastChatMessageRecivedFromGameOwner = 0; } int Network::numberOfPlayersWaiting() const { int playerCount = 0; std::map::const_iterator it; for (it = connections.begin(); it != connections.end(); ++it) { const Connection& connection = it->second; if (!connection.isBot && connection.gameInfo == 0) { ++playerCount; } } return playerCount; }