Use the network timer synchronizer to start game

This commit is contained in:
Benau
2018-08-27 09:16:35 +08:00
parent 817b576399
commit e5925a53b7
17 changed files with 165 additions and 208 deletions

View File

@@ -66,8 +66,6 @@ LRESULT CALLBACK separateProcessProc(_In_ HWND hwnd, _In_ UINT uMsg,
MainLoop::MainLoop(unsigned parent_pid)
: m_abort(false), m_ticks_adjustment(0), m_parent_pid(parent_pid)
{
m_network_timer.store(StkTime::getRealTimeMs());
m_start_game_ticks.store(0);
m_curr_time = 0;
m_prev_time = 0;
m_throttle_fps = true;
@@ -495,18 +493,4 @@ void MainLoop::abort()
m_abort = true;
} // abort
//-----------------------------------------------------------------------------
/** Set game start ticks told by server somewhere in the future.
*/
void MainLoop::setStartNetworkGameTimer(uint64_t ticks)
{
uint64_t ticks_now = getNetworkTimer();
if (ticks < ticks_now)
{
Log::warn("MainLoop", "Network timer is too slow to catch up");
ticks = ticks_now;
}
m_start_game_ticks.store(ticks);
} // setStartNetworkGameTimer
/* EOF */

View File

@@ -21,7 +21,6 @@
#define HEADER_MAIN_LOOP_HPP
#include "utils/synchronised.hpp"
#include "utils/time.hpp"
#include "utils/types.hpp"
#include <atomic>
@@ -40,8 +39,6 @@ private:
Synchronised<int> m_ticks_adjustment;
std::atomic<uint64_t> m_network_timer, m_start_game_ticks;
uint32_t m_curr_time;
uint32_t m_prev_time;
unsigned m_parent_pid;
@@ -65,17 +62,6 @@ public:
m_ticks_adjustment.getData() += ticks;
m_ticks_adjustment.unlock();
}
// ------------------------------------------------------------------------
uint64_t getNetworkTimer() const
{ return StkTime::getRealTimeMs() - m_network_timer.load(); }
// ------------------------------------------------------------------------
void setNetworkTimer(uint64_t ticks)
{ m_network_timer.store(StkTime::getRealTimeMs() - ticks); }
// ------------------------------------------------------------------------
void resetStartNetworkGameTimer() { m_start_game_ticks.store(0); }
// ------------------------------------------------------------------------
void setStartNetworkGameTimer(uint64_t ticks);
}; // MainLoop
extern MainLoop* main_loop;

View File

@@ -49,7 +49,6 @@ WorldStatus::WorldStatus()
m_play_track_intro_sound = UserConfigParams::m_music;
m_play_ready_set_go_sounds = true;
m_play_racestart_sounds = true;
m_server_is_ready.store(false);
IrrlichtDevice *device = irr_driver->getDevice();
@@ -96,10 +95,6 @@ void WorldStatus::reset()
// Set the right music
Track::getCurrentTrack()->startMusic();
// In case of a networked race the race can only start once
// all protocols are up. This flag is used to wait for
// a confirmation before starting the actual race.
m_server_is_ready.store(false);
} // reset
//-----------------------------------------------------------------------------
@@ -271,25 +266,13 @@ void WorldStatus::updateTime(int ticks)
return; // Don't increase time
case WAIT_FOR_SERVER_PHASE:
{
// Wait for all players to finish loading world
auto lobby = LobbyProtocol::get<LobbyProtocol>();
assert(lobby);
if (!lobby->allPlayersReady())
return;
// This stage is only reached in case of a networked game.
// The server waits for a confirmation from
// each client that they have started (to guarantee that the
// server is running with a local time behind all clients).
if (m_play_ready_set_go_sounds)
m_prestart_sound->play();
if (NetworkConfig::get()->isServer() &&
m_server_is_ready.load() == false) return;
m_phase = READY_PHASE;
auto cl = LobbyProtocol::get<ClientLobby>();
if (cl)
cl->startingRaceNow();
if (lobby && lobby->isRacing())
{
if (m_play_ready_set_go_sounds)
m_prestart_sound->play();
m_phase = READY_PHASE;
}
return; // Don't increase time
}
case READY_PHASE:
@@ -463,19 +446,6 @@ void WorldStatus::updateTime(int ticks)
} // switch m_phase
} // update
//-----------------------------------------------------------------------------
/** Called on the client when it receives a notification from the server that
* all clients (and server) are ready to start the race. The server will
* then additionally wait for all clients to report back that they are
* starting, which guarantees that the server is running far enough behind
* clients time that at server time T all events from the clients at time
* T have arrived, minimising rollback impact.
*/
void WorldStatus::startReadySetGo()
{
m_server_is_ready.store(true);
} // startReadySetGo
//-----------------------------------------------------------------------------
/** Sets the time for the clock.
* \param time New time to set.

View File

@@ -19,7 +19,6 @@
#define HEADER_WORLD_STATUS_HPP
#include "utils/cpp2011.hpp"
#include <atomic>
class SFXBase;
@@ -132,11 +131,6 @@ private:
int m_count_up_ticks;
bool m_engines_started;
/** In networked game a client must wait for the server to start 'ready
* set go' to make sure all client are actually ready to start the game.
* A server on the other hand will run behind all clients, so it will
* wait for all clients to indicate that they have started the race. */
std::atomic_bool m_server_is_ready;
void startEngines();
@@ -207,8 +201,6 @@ public:
// ------------------------------------------------------------------------
/** Get the ticks since start regardless of which way the clock counts */
int getTicksSinceStart() const { return m_count_up_ticks; }
// ------------------------------------------------------------------------
void setReadyToRace() { m_server_is_ready.store(true); }
}; // WorldStatus

View File

@@ -20,11 +20,12 @@
#define HEADER_NETWORK_TIMER_SYNCHRONIZER_HPP
#include "config/user_config.hpp"
#include "network/stk_host.hpp"
#include "utils/log.hpp"
#include "utils/time.hpp"
#include "utils/types.hpp"
#include "main_loop.hpp"
#include <atomic>
#include <cstdlib>
#include <deque>
#include <numeric>
@@ -35,16 +36,37 @@ class NetworkTimerSynchronizer
private:
std::deque<std::tuple<uint32_t, uint64_t, uint64_t> > m_times;
bool m_synchronised = false;
std::atomic_bool m_synchronised, m_force_set_timer;
public:
NetworkTimerSynchronizer()
{
m_synchronised.store(false);
m_force_set_timer.store(false);
}
// ------------------------------------------------------------------------
bool isSynchronised() const { return m_synchronised; }
bool isSynchronised() const { return m_synchronised.load(); }
// ------------------------------------------------------------------------
void enableForceSetTimer()
{
if (m_synchronised.load() == true)
return;
m_force_set_timer.store(true);
}
// ------------------------------------------------------------------------
void addAndSetTime(uint32_t ping, uint64_t server_time)
{
if (m_synchronised)
if (m_synchronised.load() == true)
return;
if (m_force_set_timer.load() == true)
{
m_force_set_timer.store(false);
m_synchronised.store(true);
STKHost::get()->setNetworkTimer(server_time + (uint64_t)(ping / 2));
return;
}
const uint64_t cur_time = StkTime::getRealTimeMs();
// Take max 20 averaged samples from m_times, the next addAndGetTime
// is used to determine that server_time if it's correct, if not
@@ -64,8 +86,9 @@ public:
if (std::abs(averaged_time - server_time_now) <
UserConfigParams::m_timer_sync_tolerance)
{
main_loop->setNetworkTimer(averaged_time);
m_synchronised = true;
STKHost::get()->setNetworkTimer(averaged_time);
m_force_set_timer.store(false);
m_synchronised.store(true);
Log::info("NetworkTimerSynchronizer", "Network "
"timer synchronized, difference: %dms", difference);
return;

View File

@@ -33,6 +33,7 @@
#include "network/game_setup.hpp"
#include "network/network_config.hpp"
#include "network/network_player_profile.hpp"
#include "network/network_timer_synchronizer.hpp"
#include "network/protocols/game_protocol.hpp"
#include "network/protocols/game_events_protocol.hpp"
#include "network/race_event_manager.hpp"
@@ -211,6 +212,18 @@ void ClientLobby::addAllPlayers(Event* event)
STKHost::get()->requestShutdown();
return;
}
// Timeout is too slow to synchronize, force it to stop and set current
// time
if (!STKHost::get()->getNetworkTimerSynchronizer()->isSynchronised())
{
core::stringw msg = _("Bad network connection is detected.");
MessageQueue::add(MessageQueue::MT_ERROR, msg);
Log::warn("ClientLobby", "Failed to synchronize timer before game "
"start, maybe you enter the game too quick? (at least 5 seconds "
"are required for synchronization.");
STKHost::get()->getNetworkTimerSynchronizer()->enableForceSetTimer();
}
NetworkString& data = event->data();
std::string track_name;
data.decodeString(&track_name);
@@ -737,37 +750,31 @@ void ClientLobby::connectionRefused(Event* event)
//-----------------------------------------------------------------------------
/*! \brief Called when the server broadcasts to start the race to all clients.
* \param event : Event providing the information (no additional informati
* in this case).
* \param event : Event providing the time the client should start game.
*/
void ClientLobby::startGame(Event* event)
{
m_state.store(RACING);
// Triggers the world finite state machine to go from WAIT_FOR_SERVER_PHASE
// to READY_PHASE.
World::getWorld()->setReadyToRace();
Log::info("ClientLobby", "Starting new game at %lf",
StkTime::getRealTime());
uint64_t start_time = event->data().getUInt64();
joinStartGameThread();
m_start_game_thread = std::thread([start_time, this]()
{
const uint64_t cur_time = STKHost::get()->getNetworkTimer();
if (!(start_time > cur_time))
{
Log::warn("ClientLobby", "Network timer is too slow to catch "
"up, you must have a poor network.");
STKHost::get()->setErrorMessage(
m_disconnected_msg.at(PDI_BAD_CONNECTION));
STKHost::get()->requestShutdown();
return;
}
int sleep_time = (int)(start_time - cur_time);
Log::info("ClientLobby", "Start game after %dms", sleep_time);
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
m_state.store(RACING);
});
} // startGame
//-----------------------------------------------------------------------------
/** Called from WorldStatus when reaching the READY phase, i.e. when the race
* was started. It is going to inform the server of the race start. This
* allows the server to wait for all clients to start, so the server will
* be running behind the client with the biggest latency, which should
* make it likely that at local time T on the server all messages from
* all clients at their local time T have arrived.
*/
void ClientLobby::startingRaceNow()
{
NetworkString* ns = getNetworkString(2);
ns->addUInt8(LE_STARTED_RACE);
sendToServer(ns, /*reliable*/true);
delete ns;
Log::verbose("ClientLobby", "StartingRaceNow at %lf",
StkTime::getRealTime());
} // startingRaceNow
//-----------------------------------------------------------------------------
/*! \brief Called when the kart selection starts.
* \param event : Event providing the information (no additional information

View File

@@ -45,6 +45,7 @@ LobbyProtocol::~LobbyProtocol()
if (RaceEventManager::getInstance())
RaceEventManager::getInstance()->stop();
delete m_game_setup;
joinStartGameThread();
} // ~LobbyProtocol
//-----------------------------------------------------------------------------

View File

@@ -26,6 +26,7 @@ class NetworkPlayerProfile;
#include <cassert>
#include <memory>
#include <thread>
#include <vector>
/*!
@@ -51,7 +52,6 @@ public:
LE_CLIENT_LOADED_WORLD, // Client finished loading world
LE_LOAD_WORLD, // Clients should load world
LE_START_RACE, // Server to client to start race
LE_STARTED_RACE, // Client to server that it has started race
LE_START_SELECTION, // inform client to start selection
LE_RACE_FINISHED, // race has finished, display result
LE_RACE_FINISHED_ACK, // client went back to lobby
@@ -76,14 +76,22 @@ public:
};
protected:
std::thread m_start_game_thread;
static std::weak_ptr<LobbyProtocol> m_lobby;
/** Stores data about the online game to play. */
GameSetup* m_game_setup;
// ------------------------------------------------------------------------
void configRemoteKart(
const std::vector<std::shared_ptr<NetworkPlayerProfile> >& players) const;
// ------------------------------------------------------------------------
void joinStartGameThread()
{
if (m_start_game_thread.joinable())
m_start_game_thread.join();
}
public:
/** Creates either a client or server lobby protocol as a singleton. */

View File

@@ -212,8 +212,6 @@ void ServerLobby::setup()
// the server are ready:
resetPeersReady();
m_peers_votes.clear();
m_server_delay = std::numeric_limits<double>::max();
m_server_max_ping = std::numeric_limits<double>::max();
m_timeout.store(std::numeric_limits<float>::max());
m_waiting_for_reset = false;
@@ -307,7 +305,6 @@ bool ServerLobby::notifyEventAsynchronous(Event* event)
case LE_CONNECTION_REQUESTED: connectionRequested(event); break;
case LE_KART_SELECTION: kartSelectionRequested(event); break;
case LE_CLIENT_LOADED_WORLD: finishedLoadingWorldClient(event); break;
case LE_STARTED_RACE: startedRaceOnClient(event); break;
case LE_VOTE: playerVote(event); break;
case LE_KICK_HOST: kickHost(event); break;
case LE_CHANGE_TEAM: changeTeam(event); break;
@@ -439,47 +436,11 @@ void ServerLobby::asynchronousUpdate()
return;
if (!checkPeersReady())
return;
m_state = WAIT_FOR_RACE_STARTED;
// Reset for next state usage
resetPeersReady();
signalRaceStartToClients();
m_server_max_ping = StkTime::getRealTime() +
((double)UserConfigParams::m_max_ping / 1000.0);
configPeersStartTime();
break;
}
case WAIT_FOR_RACE_STARTED:
{
const bool ping_timed_out =
m_server_max_ping < StkTime::getRealTime();
if (checkPeersReady() || ping_timed_out)
{
for (auto p : m_peers_ready)
{
auto cur_peer = p.first.lock();
if (!cur_peer)
continue;
if (ping_timed_out && p.second.second > m_server_max_ping)
sendBadConnectionMessageToPeer(cur_peer);
}
m_state = DELAY_SERVER;
const double jt =
(double)UserConfigParams::m_jitter_tolerance / 1000.0;
m_server_delay = StkTime::getRealTime() + jt;
Log::verbose("ServerLobby",
"Started delay at %lf to %lf with jitter tolerance %lf.",
StkTime::getRealTime(), m_server_delay, jt);
}
break;
}
case DELAY_SERVER:
if (m_server_delay < StkTime::getRealTime())
{
Log::verbose("ServerLobby", "End delay at %lf",
StkTime::getRealTime());
m_state = RACING;
World::getWorld()->setReadyToRace();
}
break;
case SELECTING:
{
auto result = handleVote();
@@ -634,7 +595,6 @@ void ServerLobby::update(int ticks)
case ACCEPTING_CLIENTS:
case WAIT_FOR_WORLD_LOADED:
case WAIT_FOR_RACE_STARTED:
case DELAY_SERVER:
{
// Waiting for asynchronousUpdate
break;
@@ -779,21 +739,6 @@ void ServerLobby::unregisterServer(bool now)
} // unregisterServer
//-----------------------------------------------------------------------------
/** This function is called when all clients have loaded the world and
* are therefore ready to start the race. It signals to all clients
* to start the race and then switches state to DELAY_SERVER.
*/
void ServerLobby::signalRaceStartToClients()
{
Log::verbose("Server", "Signaling race start to clients at %lf",
StkTime::getRealTime());
NetworkString *ns = getNetworkString(1);
ns->addUInt8(LE_START_RACE);
sendMessageToPeers(ns, /*reliable*/true);
delete ns;
} // startGame
//-----------------------------------------------------------------------------
/** Instructs all clients to start the kart selection. If event is NULL,
* the command comes from the owner less server.
@@ -1907,28 +1852,6 @@ void ServerLobby::finishedLoadingWorldClient(Event *event)
peer->getHostId(), StkTime::getRealTime());
} // finishedLoadingWorldClient
//-----------------------------------------------------------------------------
/** Called when a notification from a client is received that the client has
* started the race. Once all clients have informed the server that they
* have started the race, the server can start. This makes sure that the
* server's local time is behind all clients by at most max ping,
* which in turn means that when the server simulates local time T all
* messages from clients at their local time T should have arrived at
* the server, which creates smoother play experience.
*/
void ServerLobby::startedRaceOnClient(Event *event)
{
if (m_state.load() != WAIT_FOR_RACE_STARTED)
{
sendBadConnectionMessageToPeer(event->getPeerSP());
return;
}
std::shared_ptr<STKPeer> peer = event->getPeerSP();
m_peers_ready.at(peer) = std::make_pair(true, StkTime::getRealTime());
Log::info("ServerLobby", "Peer %s has started race at %lf",
peer->getAddress().toString().c_str(), StkTime::getRealTime());
} // startedRaceOnClient
//-----------------------------------------------------------------------------
/** Called when a client clicks on 'ok' on the race result screen.
* If all players have clicked on 'ok', go back to the lobby.
@@ -2114,3 +2037,52 @@ void ServerLobby::submitRankingsToAddons()
request->queue();
}
} // submitRankingsToAddons
//-----------------------------------------------------------------------------
/** This function is called when all clients have loaded the world and
* are therefore ready to start the race. It determine the start time in
* network timer for client and server based on pings and then switches state
* to WAIT_FOR_RACE_STARTED.
*/
void ServerLobby::configPeersStartTime()
{
uint32_t max_ping = 0;
const unsigned max_ping_from_peers = UserConfigParams::m_max_ping;
for (auto p : m_peers_ready)
{
auto peer = p.first.lock();
if (!peer)
continue;
if (peer->getAveragePing() > max_ping_from_peers)
{
sendBadConnectionMessageToPeer(peer);
continue;
}
max_ping = std::max(peer->getAveragePing(), max_ping);
}
// Start up time will be after 2000ms, so even if this packet is sent late
// (due to packet loss), the start time will still ahead of current time
uint64_t start_time = STKHost::get()->getNetworkTimer() + (uint64_t)2000;
NetworkString* ns = getNetworkString(10);
ns->addUInt8(LE_START_RACE).addUInt64(start_time);
sendMessageToPeers(ns, /*reliable*/true);
const unsigned jitter_tolerance = UserConfigParams::m_jitter_tolerance;
Log::info("ServerLobby", "Max ping from peers: %d, jitter tolerance: %d",
max_ping, jitter_tolerance);
// Delay server for max ping / 2 from peers and jitter tolerance.
start_time += (uint64_t)(max_ping / 2) + (uint64_t)jitter_tolerance;
delete ns;
m_state = WAIT_FOR_RACE_STARTED;
joinStartGameThread();
m_start_game_thread = std::thread([start_time, this]()
{
const uint64_t cur_time = STKHost::get()->getNetworkTimer();
assert(start_time > cur_time);
int sleep_time = (int)(start_time - cur_time);
Log::info("ServerLobby", "Start game after %dms", sleep_time);
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
m_state.store(RACING);
});
} // configPeersStartTime

View File

@@ -51,7 +51,6 @@ public:
LOAD_WORLD, // Server starts loading world
WAIT_FOR_WORLD_LOADED, // Wait for clients and server to load world
WAIT_FOR_RACE_STARTED, // Wait for all clients to have started the race
DELAY_SERVER, // Additional server delay
RACING, // racing
WAIT_FOR_RACE_STOPPED, // Wait server for stopping all race protocols
RESULT_DISPLAY, // Show result screen
@@ -90,12 +89,6 @@ private:
std::map<std::weak_ptr<STKPeer>, std::tuple<std::string, uint8_t, bool>,
std::owner_less<std::weak_ptr<STKPeer> > > m_peers_votes;
/** Keeps track of an artificial server delay, which is used to compensate
* for network jitter. */
double m_server_delay;
double m_server_max_ping;
bool m_has_created_server_id_file;
/** It indicates if this server is unregistered with the stk server. */
@@ -153,7 +146,6 @@ private:
void playerFinishedResult(Event *event);
bool registerServer();
void finishedLoadingWorldClient(Event *event);
void startedRaceOnClient(Event *event);
void kickHost(Event* event);
void changeTeam(Event* event);
void handleChat(Event* event);
@@ -220,6 +212,7 @@ private:
void checkRaceFinished();
void sendBadConnectionMessageToPeer(std::shared_ptr<STKPeer> p);
std::pair<int, float> getHitCaptureLimit(float num_karts);
void configPeersStartTime();
public:
ServerLobby();
virtual ~ServerLobby();
@@ -230,7 +223,6 @@ public:
virtual void update(int ticks) OVERRIDE;
virtual void asynchronousUpdate() OVERRIDE;
void signalRaceStartToClients();
void startSelection(const Event *event=NULL);
void checkIncomingConnectionRequests();
void finishedLoadingWorld() OVERRIDE;

View File

@@ -35,7 +35,6 @@
#include "utils/separate_process.hpp"
#include "utils/time.hpp"
#include "utils/vs.hpp"
#include "main_loop.hpp"
#include <string.h>
#if defined(WIN32)
@@ -304,12 +303,12 @@ STKHost::STKHost(bool server)
*/
void STKHost::init()
{
m_network_timer.store(StkTime::getRealTimeMs());
m_shutdown = false;
m_authorised = false;
m_network = NULL;
m_exit_timeout.store(std::numeric_limits<double>::max());
m_client_ping.store(0);
main_loop->resetStartNetworkGameTimer();
// Start with initialising ENet
// ============================
@@ -337,7 +336,6 @@ void STKHost::init()
*/
STKHost::~STKHost()
{
main_loop->resetStartNetworkGameTimer();
requestShutdown();
if (m_network_console.joinable())
m_network_console.join();
@@ -779,7 +777,7 @@ void STKHost::mainLoop()
if (need_ping)
{
BareNetworkString ping_packet;
uint64_t network_timer = main_loop->getNetworkTimer();
uint64_t network_timer = getNetworkTimer();
ping_packet.addUInt64(network_timer);
ping_packet.addUInt8((uint8_t)m_peer_pings.getData().size());
for (auto& p : m_peer_pings.getData())

View File

@@ -26,6 +26,7 @@
#include "network/network_string.hpp"
#include "network/transport_address.hpp"
#include "utils/synchronised.hpp"
#include "utils/time.hpp"
#include "irrString.h"
@@ -138,6 +139,8 @@ private:
std::atomic<uint32_t> m_client_ping;
std::atomic<uint64_t> m_network_timer;
std::unique_ptr<NetworkTimerSynchronizer> m_nts;
// ------------------------------------------------------------------------
@@ -318,7 +321,12 @@ public:
// ------------------------------------------------------------------------
NetworkTimerSynchronizer* getNetworkTimerSynchronizer() const
{ return m_nts.get(); }
// ------------------------------------------------------------------------
uint64_t getNetworkTimer() const
{ return StkTime::getRealTimeMs() - m_network_timer.load(); }
// ------------------------------------------------------------------------
void setNetworkTimer(uint64_t ticks)
{ m_network_timer.store(StkTime::getRealTimeMs() - ticks); }
}; // class STKHost
#endif // STK_HOST_HPP

View File

@@ -39,7 +39,7 @@ STKPeer::STKPeer(ENetPeer *enet_peer, STKHost* host, uint32_t host_id)
m_host_id = host_id;
m_connected_time = (float)StkTime::getRealTime();
m_validated.store(false);
m_average_ping = 0;
m_average_ping.store(0);
} // STKPeer
//-----------------------------------------------------------------------------
@@ -165,9 +165,9 @@ uint32_t STKPeer::getPing()
while (m_previous_pings.size() > ap)
{
m_previous_pings.pop_front();
m_average_ping =
m_average_ping.store(
(uint32_t)(std::accumulate(m_previous_pings.begin(),
m_previous_pings.end(), 0) / m_previous_pings.size());
m_previous_pings.end(), 0) / m_previous_pings.size()));
}
}
return m_enet_peer->roundTripTime;

View File

@@ -81,7 +81,7 @@ protected:
std::deque<uint32_t> m_previous_pings;
uint32_t m_average_ping;
std::atomic<uint32_t> m_average_ping;
public:
STKPeer(ENetPeer *enet_peer, STKHost* host, uint32_t host_id);
@@ -165,7 +165,7 @@ public:
// ------------------------------------------------------------------------
void setCrypto(std::unique_ptr<Crypto>&& c);
// ------------------------------------------------------------------------
uint32_t getAveragePing() const { return m_average_ping; }
uint32_t getAveragePing() const { return m_average_ping.load(); }
// ------------------------------------------------------------------------
ENetPeer* getENetPeer() const { return m_enet_peer; }
}; // STKPeer

View File

@@ -230,13 +230,13 @@ void RaceGUI::renderGlobal(float dt)
World *world = World::getWorld();
assert(world != NULL);
if(world->getPhase() >= WorldStatus::READY_PHASE &&
if(world->getPhase() >= WorldStatus::WAIT_FOR_SERVER_PHASE &&
world->getPhase() <= WorldStatus::GO_PHASE )
{
drawGlobalReadySetGo();
}
if(world->getPhase() == World::GOAL_PHASE)
drawGlobalGoal();
drawGlobalGoal();
// MiniMap is drawn when the players wait for the start countdown to end
drawGlobalMiniMap();

View File

@@ -70,6 +70,9 @@ RaceGUIBase::RaceGUIBase()
m_string_go = _("Go!");
//I18N: Shown when a goal is scored
m_string_goal = _("GOAL!");
// I18N: Shown waiting for other players in network to finish loading or
// waiting
m_string_waiting_for_others = _("Waiting for others");
m_music_icon = irr_driver->getTexture("notes.png");
if (!m_music_icon)
@@ -626,6 +629,18 @@ void RaceGUIBase::drawGlobalReadySetGo()
{
switch (World::getWorld()->getPhase())
{
case WorldStatus::WAIT_FOR_SERVER_PHASE:
{
static video::SColor color = video::SColor(255, 255, 255, 255);
core::rect<s32> pos(irr_driver->getActualScreenSize().Width>>1,
irr_driver->getActualScreenSize().Height>>1,
irr_driver->getActualScreenSize().Width>>1,
irr_driver->getActualScreenSize().Height>>1);
gui::IGUIFont* font = GUIEngine::getTitleFont();
font->draw(StringUtils::loadingDots(
m_string_waiting_for_others.c_str()), pos, color, true, true);
}
break;
case WorldStatus::READY_PHASE:
{
static video::SColor color = video::SColor(255, 255, 255, 255);

View File

@@ -133,7 +133,8 @@ private:
video::ITexture* m_plunger_face;
/** Translated strings 'ready', 'set', 'go'. */
core::stringw m_string_ready, m_string_set, m_string_go, m_string_goal;
core::stringw m_string_ready, m_string_set, m_string_go, m_string_goal,
m_string_waiting_for_others;
/** The position of the referee for all karts. */
std::vector<Vec3> m_referee_pos;