Avoid extrapolation by making sure the client starts after receiving

a message from the server, and only updating the previous position
if the new previous position is indeed before the current client time.
Fixed conflicts, removed dumb-client related interpolation code, left
client starting in place.
This commit is contained in:
hiker
2016-04-01 08:51:39 +11:00
parent 8092cfc0a7
commit 09ce5515b0
7 changed files with 153 additions and 38 deletions

View File

@@ -26,6 +26,8 @@
#include "guiengine/modaldialog.hpp"
#include "karts/abstract_kart.hpp"
#include "modes/world.hpp"
#include "network/network_config.hpp"
#include "network/race_event_manager.hpp"
#include "tracks/track.hpp"
#include <irrlicht.h>
@@ -56,6 +58,8 @@ 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 = false;
IrrlichtDevice *device = irr_driver->getDevice();
@@ -190,7 +194,7 @@ void WorldStatus::updateTime(const float dt)
World::getWorld()->getWeather()->playSound();
}
return;
return; // Do not increase time
case TRACK_INTRO_PHASE:
m_auxiliary_timer += dt;
@@ -209,7 +213,7 @@ void WorldStatus::updateTime(const float dt)
// after 3.5 seconds.
if (m_track_intro_sound->getStatus() == SFXBase::SFX_PLAYING &&
m_auxiliary_timer < 3.5f)
return;
return; // Do not increase time
// Wait before ready phase if sounds are disabled
if (!UserConfigParams::m_sfx && m_auxiliary_timer < 3.0f)
@@ -219,7 +223,7 @@ void WorldStatus::updateTime(const float dt)
{
startEngines();
if (m_auxiliary_timer < 3.0f)
return;
return; // Do not increase time
}
m_auxiliary_timer = 0.0f;
@@ -227,11 +231,40 @@ void WorldStatus::updateTime(const float dt)
if (m_play_ready_set_go_sounds)
m_prestart_sound->play();
m_phase = READY_PHASE;
// In a networked game the client needs to wait for a notification
// from the server that ready-set-go can start now. So client will go
// to the 'wait_for_server_phase', from which they will progress once
// the notification is received. In all other cases (no networking or
// server), immediately go to race start
if(!NetworkConfig::get()->isNetworking() ||
NetworkConfig::get()->isServer() )
{
// Notify the clients that they can start ready-set-go
if(NetworkConfig::get()->isServer())
RaceEventManager::getInstance()->startReadySetGo();
m_server_is_ready = true;
m_phase = WAIT_FOR_SERVER_PHASE;
} // if not networked
else if(NetworkConfig::get()->isNetworking())
{
// must be client now
m_phase = WAIT_FOR_SERVER_PHASE;
}
return; // Don't increase time
case WAIT_FOR_SERVER_PHASE:
// On a client this phase waits for the server to be ready. On a
// server or in case of non-networked game this phase is reached
// with m_server_is_ready set to true, so it will immediately
// start the engines and then the race
if(!m_server_is_ready) return;
m_phase = READY_PHASE;
startEngines();
break;
// Receiving a 'startReadySetGo' message from the server triggers
// a call to startReadySetGo() here, which will change the phase
// (or state) of the finite state machine.
return; // Don't increase time
case READY_PHASE:
if (m_auxiliary_timer > 1.0)
{
@@ -245,7 +278,8 @@ void WorldStatus::updateTime(const float dt)
m_auxiliary_timer += dt;
// In artist debug mode, when without opponents, skip the ready/set/go counter faster
// In artist debug mode, when without opponents, skip the
// ready/set/go counter faster
if (UserConfigParams::m_artist_debug_mode &&
race_manager->getNumberOfKarts() == 1 &&
race_manager->getTrackName() != "tutorial")
@@ -253,7 +287,7 @@ void WorldStatus::updateTime(const float dt)
m_auxiliary_timer += dt*6;
}
return;
return; // Do not increase time
case SET_PHASE:
if (m_auxiliary_timer > 2.0)
{
@@ -270,7 +304,8 @@ void WorldStatus::updateTime(const float dt)
m_auxiliary_timer += dt;
// In artist debug mode, when without opponents, skip the ready/set/go counter faster
// In artist debug mode, when without opponents,
// skip the ready/set/go counter faster
if (UserConfigParams::m_artist_debug_mode &&
race_manager->getNumberOfKarts() == 1 &&
race_manager->getTrackName() != "tutorial")
@@ -278,7 +313,7 @@ void WorldStatus::updateTime(const float dt)
m_auxiliary_timer += dt*6;
}
return;
return; // Do not increase time
case GO_PHASE :
if (m_auxiliary_timer>2.5f && music_manager->getCurrentMusic() &&
@@ -294,7 +329,8 @@ void WorldStatus::updateTime(const float dt)
m_auxiliary_timer += dt;
// In artist debug mode, when without opponents, skip the ready/set/go counter faster
// In artist debug mode, when without opponents,
// skip the ready/set/go counter faster
if (UserConfigParams::m_artist_debug_mode &&
race_manager->getNumberOfKarts() == 1 &&
race_manager->getTrackName() != "tutorial")
@@ -302,7 +338,7 @@ void WorldStatus::updateTime(const float dt)
m_auxiliary_timer += dt*6;
}
break;
break; // Now the world time starts
case MUSIC_PHASE:
// Start the music here when starting fast
if (UserConfigParams::m_race_now)
@@ -347,7 +383,8 @@ void WorldStatus::updateTime(const float dt)
default: break;
}
Log::verbose("time", "%f %f %f phase %d",
m_time, dt, m_time+dt, m_phase);
switch (m_clock_mode)
{
case CLOCK_CHRONO:
@@ -374,9 +411,19 @@ void WorldStatus::updateTime(const float dt)
break;
default: break;
}
} // switch m_phase
} // update
//-----------------------------------------------------------------------------
/** Called on the client when it receives a notification from the server that
* the server is now starting its ready-set-go phase. This function changes
* the state of the finite state machine to be ready.
*/
void WorldStatus::startReadySetGo()
{
m_server_is_ready = true;
} // startReadySetGo
//-----------------------------------------------------------------------------
/** Sets the time for the clock.
* \param time New time to set.

View File

@@ -45,6 +45,10 @@ public:
// Game setup, e.g. track loading
SETUP_PHASE,
// Used in network games only: wait for the server to broadcast
// 'start'. This happens on a network client only
WAIT_FOR_SERVER_PHASE,
// 'Ready' is displayed
READY_PHASE,
@@ -82,7 +86,15 @@ public:
//Goal scored phase
GOAL_PHASE
};
protected:
/** Elasped/remaining time in seconds. */
double m_time;
/** If the start race should be played, disabled in cutscenes. */
bool m_play_racestart_sounds;
private:
/** Sound to play at the beginning of a race, during which a
* a camera intro of the track can be shown. */
SFXBase *m_track_intro_sound;
@@ -91,12 +103,9 @@ protected:
/** The third sound to be played in ready, set, go. */
SFXBase *m_start_sound;
/**
* Elasped/remaining time in seconds
*/
double m_time;
/** The clock mode: normal counting forwards, or countdown */
ClockType m_clock_mode;
protected:
bool m_play_track_intro_sound;
bool m_play_ready_set_go_sounds;
@@ -118,6 +127,12 @@ private:
bool m_engines_started;
void startEngines();
/** In networked game the client must wait for the server to start 'ready set go'
* (to guarantee that the client's time is not ahead of the server), This flag
* indicates that the notification from the server was received, and that the
* client can go to 'ready' phase. */
bool m_server_is_ready;
public:
WorldStatus();
virtual ~WorldStatus();
@@ -125,6 +140,7 @@ public:
virtual void reset();
virtual void updateTime(const float dt);
virtual void update(float dt);
void startReadySetGo();
virtual void pause(Phase phase);
virtual void unpause();
virtual void enterRaceOverState();

View File

@@ -39,7 +39,7 @@ bool GameEventsProtocol::notifyEvent(Event* event)
if (event->getType() != EVENT_TYPE_MESSAGE)
return true;
NetworkString &data = event->data();
if (data.size() < 5) // for token and type
if (data.size() < 1) // for token and type
{
Log::warn("GameEventsProtocol", "Too short message.");
return true;
@@ -56,6 +56,9 @@ bool GameEventsProtocol::notifyEvent(Event* event)
collectedItem(data); break;
case GE_KART_FINISHED_RACE:
kartFinishedRace(data); break;
case GE_START_READY_SET_GO:
receivedReadySetGo(); break;
default:
Log::warn("GameEventsProtocol", "Unkown message type.");
break;
@@ -101,18 +104,18 @@ void GameEventsProtocol::collectedItem(const NetworkString &data)
{
if (data.size() < 6)
{
Log::warn("GameEventsProtocol", "Too short message.");
}
uint32_t item_id = data.getUInt32();
uint8_t powerup_type = data.getUInt8();
uint8_t kart_id = data.getUInt8();
// now set the kart powerup
AbstractKart* kart = World::getWorld()->getKart(kart_id);
ItemManager::get()->collectedItem(ItemManager::get()->getItem(item_id),
kart, powerup_type);
Log::info("GameEventsProtocol", "Item %d picked by a player.",
powerup_type);
} // collectedItem
Log::warn("GameEventsProtocol", "collectedItem: Too short message.");
}
uint32_t item_id = data.getUInt32();
uint8_t powerup_type = data.getUInt8();
uint8_t kart_id = data.getUInt8();
// now set the kart powerup
AbstractKart* kart = World::getWorld()->getKart(kart_id);
ItemManager::get()->collectedItem(ItemManager::get()->getItem(item_id),
kart, powerup_type);
Log::info("GameEventsProtocol", "Item %d picked by a player.",
powerup_type);
} // collectedItem
// ----------------------------------------------------------------------------
/** This function is called from the server when a kart finishes a race. It
@@ -137,8 +140,39 @@ void GameEventsProtocol::kartFinishedRace(AbstractKart *kart, float time)
*/
void GameEventsProtocol::kartFinishedRace(const NetworkString &ns)
{
if (ns.size() < 5) // for token and type
{
Log::warn("GameEventsProtocol", "kartFinisheRace: Too short message.");
return;
}
uint8_t kart_id = ns.getUInt8();
float time = ns.getFloat();
World::getWorld()->getKart(kart_id)->finishedRace(time,
/*from_server*/true);
} // kartFinishedRace
// ----------------------------------------------------------------------------
/** This function is called on a server when the world starts the ready-set-go
* phase. It signals to all clients to do the same.
*/
void GameEventsProtocol::startReadySetGo()
{
assert(NetworkConfig::get()->isServer());
NetworkString *ns = getNetworkString(1);
ns->setSynchronous(true);
ns->addUInt8(GE_START_READY_SET_GO);
sendMessageToPeersChangingToken(ns, /*reliable*/true);
delete ns;
} // startReadySetGo
// ----------------------------------------------------------------------------
/** Called on the client when it receives the message that the server has
* started its ready-set-go. Signal to world that it can go to the next
* phase (ready phase) now.
*/
void GameEventsProtocol::receivedReadySetGo()
{
assert(NetworkConfig::get()->isClient());
World::getWorld()->startReadySetGo();
} // receivedReadySetGo

View File

@@ -11,8 +11,9 @@ class GameEventsProtocol : public Protocol
{
private:
enum GameEventType {
GE_ITEM_COLLECTED = 0x01,
GE_KART_FINISHED_RACE = 0x02
GE_START_READY_SET_GO = 0x01,
GE_ITEM_COLLECTED = 0x02,
GE_KART_FINISHED_RACE = 0x03
}; // GameEventType
public:
@@ -24,6 +25,8 @@ public:
void collectedItem(const NetworkString &ns);
void kartFinishedRace(AbstractKart *kart, float time);
void kartFinishedRace(const NetworkString &ns);
void startReadySetGo();
void receivedReadySetGo();
virtual void setup() OVERRIDE {};
virtual void update(float dt) OVERRIDE {};
virtual void asynchronousUpdate() OVERRIDE{}

View File

@@ -36,7 +36,9 @@ void KartUpdateProtocol::setup()
*/
bool KartUpdateProtocol::notifyEvent(Event* event)
{
if (event->getType() != EVENT_TYPE_MESSAGE)
// It might be possible that we still receive messages after
// the game was exited, so make sure we still have a world.
if (event->getType() != EVENT_TYPE_MESSAGE || !World::getWorld())
return true;
NetworkString &ns = event->data();
if (ns.size() < 33)

View File

@@ -100,6 +100,21 @@ void RaceEventManager::collectedItem(Item *item, AbstractKart *kart)
protocol->collectedItem(item,kart);
} // collectedItem
// ----------------------------------------------------------------------------
/** A message from the server to all clients that it is now starting the
* 'ready' phase. Clients will wait for this event before they display
* RSG. This will make sure that the server time is always ahead of
* the client time.
*/
void RaceEventManager::startReadySetGo()
{
// this is only called in the server
assert(NetworkConfig::get()->isServer());
GameEventsProtocol* protocol = static_cast<GameEventsProtocol*>(
ProtocolManager::getInstance()->getProtocol(PROTOCOL_GAME_EVENTS));
protocol->startReadySetGo();
} // startReadySetGo
// ----------------------------------------------------------------------------
void RaceEventManager::controllerAction(Controller* controller,
PlayerAction action, int value)

View File

@@ -16,9 +16,6 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
/*! \file network_world.hpp
*/
#ifndef NETWORK_WORLD_HPP
#define NETWORK_WORLD_HPP
@@ -59,6 +56,7 @@ public:
void controllerAction(Controller* controller, PlayerAction action,
int value);
void kartFinishedRace(AbstractKart *kart, float time);
void startReadySetGo();
// ------------------------------------------------------------------------
/** Returns if this instance is in running state or not. */
bool isRunning() { return m_running; }