/*
* 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;
}