Refactored replay by splitting it into one base class, and dedicated

classes for recording and for replaying. Added compression (by use of
interpolation) settings to stk_config - atm only using a certain
frequency (delta-t setting in stk_config) is used.


git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@10904 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
hikerstk 2012-02-28 22:33:49 +00:00
parent 4f1961044c
commit 3fc43d4d14
16 changed files with 719 additions and 474 deletions

View File

@ -52,6 +52,15 @@
<!-- Mostly for debugging: maximum number of history entries -->
<history max-frames="10000"/>
<!-- Replay related values, mostly concerned with saving less data
and using interpolation instead.
delta-t Minumum time between saving consecutive transform events.
delta-pos If the interpolated position is within this delta, a
transform event is not generated.
delta-angle If the interpolated angle is within this delta,
a transform event is not generated. -->
<replay delta-t="0.05" delta-pos="0.1" delta-angle="0.5" />
<!-- Skidmark data: maximum number of skid marks, and
time for skidmarks to fade out. -->
<skid-marks max-number="100" fadeout-time="60"/>

View File

@ -138,7 +138,12 @@ void STKConfig::load(const std::string &filename)
CHECK_NEG(m_leader_time_per_kart, "leader time-per-kart" );
CHECK_NEG(m_penalty_time, "penalty-time" );
CHECK_NEG(m_max_display_news, "max-display-news" );
CHECK_NEG(m_replay_delta_angle, "replay delta-angle" );
CHECK_NEG(m_replay_delta_pos2, "replay delta-position" );
CHECK_NEG(m_replay_dt, "replay delta-t" );
// Square distance to make distance checks cheaper (no sqrt)
m_replay_delta_pos2 *= m_replay_delta_pos2;
m_default_kart_properties->checkAllSet(filename);
} // load
@ -167,6 +172,9 @@ void STKConfig::init_defaults()
m_min_track_version = -100;
m_max_track_version = -100;
m_max_display_news = -100;
m_replay_delta_angle = -100;
m_replay_delta_pos2 = -100;
m_replay_dt = -100;
m_title_music = NULL;
m_enable_networking = true;
m_smooth_normals = false;
@ -345,6 +353,12 @@ void STKConfig::getAllData(const XMLNode * root)
if(const XMLNode *networking_node= root->getNode("networking"))
networking_node->get("enable", &m_enable_networking);
if(const XMLNode *replay_node = root->getNode("replay"))
{
replay_node->get("delta-angle", &m_replay_delta_angle);
replay_node->get("delta-pos", &m_replay_delta_pos2 );
replay_node->get("delta-t", &m_replay_dt );
}
// Get the default KartProperties
// ------------------------------
const XMLNode *node = root -> getNode("general-kart-defaults");

View File

@ -119,8 +119,21 @@ public:
* position is computed. */
std::vector<int> m_score_increase;
/** Filename of the title music to play.*/
MusicInformation
*m_title_music; /**<Filename of the title music to play.*/
*m_title_music;
/** Minimum time between consecutive saved tranform events. */
float m_replay_dt;
/** Maximum difference between interpolated and actual position. If the
* difference is larger than this, a new event is generated. */
float m_replay_delta_pos2;
/** A heading difference of more than that will trigger a new event to
* be generated. */
float m_replay_delta_angle;
private:
/** True if stk_config has been loaded. This is necessary if the
* --stk-config command line parameter has been specified to avoid

View File

@ -1109,7 +1109,15 @@
Name="replay"
>
<File
RelativePath="..\..\replay\replay.cpp"
RelativePath="..\..\replay\replay_base.cpp"
>
</File>
<File
RelativePath="..\..\replay\replay_play.cpp"
>
</File>
<File
RelativePath="..\..\replay\replay_recorder.cpp"
>
</File>
</Filter>
@ -2207,7 +2215,15 @@
Name="replay"
>
<File
RelativePath="..\..\replay\replay.hpp"
RelativePath="..\..\replay\replay_base.hpp"
>
</File>
<File
RelativePath="..\..\replay\replay_play.hpp"
>
</File>
<File
RelativePath="..\..\replay\replay_recorder.hpp"
>
</File>
</Filter>

View File

@ -35,7 +35,7 @@
#include "modes/profile_world.hpp"
#include "modes/world.hpp"
#include "race/history.hpp"
#include "replay/replay.hpp"
#include "replay/replay_recorder.hpp"
#include "states_screens/kart_selection.hpp"
#include "states_screens/options_screen_input2.hpp"
#include "states_screens/state_manager.hpp"
@ -237,8 +237,8 @@ void InputManager::handleStaticAction(int key, int value)
case KEY_F10:
if(world && value)
{
if(control_is_pressed)
Replay::get()->Save();
if(control_is_pressed && ReplayRecorder::get())
ReplayRecorder::get()->Save();
else
history->Save();
}

View File

@ -68,7 +68,7 @@ void GhostKart::update(float dt)
{
m_current ++;
}
if(m_current+1==m_all_times.size())
if(m_current+1>=m_all_times.size())
{
m_node->setVisible(false);
return;

View File

@ -172,7 +172,8 @@
#include "race/highscore_manager.hpp"
#include "race/history.hpp"
#include "race/race_manager.hpp"
#include "replay/replay.hpp"
#include "replay/replay_play.hpp"
#include "replay/replay_recorder.hpp"
#include "states_screens/story_mode_lobby.hpp"
#include "states_screens/state_manager.hpp"
#include "states_screens/dialogs/message_dialog.hpp"
@ -915,7 +916,7 @@ int handleCmdLine(int argc, char **argv)
}
else if( !strcmp(argv[i], "--ghost"))
{
Replay::get()->doReplay();
ReplayPlay::create();
// Force the no-start screen flag, since this initialises
// the player structures correctly.
UserConfigParams::m_no_start_screen = true;
@ -1058,7 +1059,7 @@ void initRest()
// The order here can be important, e.g. KartPropertiesManager needs
// defaultKartProperties, which are defined in stk_config.
history = new History ();
Replay::create();
ReplayRecorder::create();
material_manager = new MaterialManager ();
track_manager = new TrackManager ();
kart_properties_manager = new KartPropertiesManager();
@ -1122,7 +1123,7 @@ void cleanSuperTuxKart()
if(track_manager) delete track_manager;
if(material_manager) delete material_manager;
if(history) delete history;
Replay::destroy();
ReplayRecorder::destroy();
if(sfx_manager) delete sfx_manager;
if(music_manager) delete music_manager;
delete ParticleKindManager::get();

View File

@ -46,7 +46,8 @@
#include "race/highscore_manager.hpp"
#include "race/history.hpp"
#include "race/race_manager.hpp"
#include "replay/replay.hpp"
#include "replay/replay_play.hpp"
#include "replay/replay_recorder.hpp"
#include "states_screens/state_manager.hpp"
#include "states_screens/race_gui_base.hpp"
#include "states_screens/race_gui.hpp"
@ -150,8 +151,8 @@ void World::init()
} // for i
if(Replay::get()->isReplay())
Replay::get()->Load();
if(ReplayPlay::get())
ReplayPlay::get()->Load();
resetAllKarts();
// Note: track reset must be called after all karts exist, since check
@ -160,7 +161,7 @@ void World::init()
m_track->reset();
if(!history->replayHistory()) history->initRecording();
if(!Replay::get()->isReplay()) Replay::get()->initRecording();
if(ReplayRecorder::get()) ReplayRecorder::get()->init();
network_manager->worldLoaded();
powerup_manager->updateWeightsForRace(num_karts);
@ -635,7 +636,8 @@ void World::update(float dt)
#endif
history->update(dt);
Replay::get()->update(dt);
if(ReplayRecorder::get()) ReplayRecorder::get()->update(dt);
if(ReplayPlay::get()) ReplayPlay::get()->update(dt);
if(history->replayHistory()) dt=history->getNextDelta();
WorldStatus::update(dt);
// Clear race state so that new information can be stored
@ -912,8 +914,8 @@ void World::restartRace()
{
(*i)->reset();
}
if(Replay::get()->isReplay())
Replay::get()->reset();
if(ReplayPlay::get())
ReplayPlay::get()->reset();
resetAllKarts();
// Start music from beginning
@ -927,7 +929,7 @@ void World::restartRace()
race_manager->reset();
// Make sure to overwrite the data from the previous race.
if(!history->replayHistory()) history->initRecording();
if(!Replay::get()->isReplay()) Replay::get()->initRecording();
if(ReplayRecorder::get()) ReplayRecorder::get()->init();
} // restartRace

View File

@ -1,347 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2006 Joerg Henrichs
//
// This program 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.
//
// This program 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "replay/replay.hpp"
#include "io/file_manager.hpp"
#include "modes/world.hpp"
#include "karts/ghost_kart.hpp"
#include "karts/kart.hpp"
#include "physics/physics.hpp"
#include "race/race_manager.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
#include <stdio.h>
#include <string>
bool Replay::m_do_replay = false;
Replay *Replay::m_replay = NULL;
//-----------------------------------------------------------------------------
/** Initialises the Replay engine
*/
Replay::Replay()
{
m_next = 0;
m_num_ghost_karts = 0;
} // Replay
//-----------------------------------------------------------------------------
/** Frees all stored data. */
Replay::~Replay()
{
m_events.clear();
} // ~Replay
//-----------------------------------------------------------------------------
/** Starts replay from the replay file in the current directory.
*/
void Replay::initReplay()
{
m_next = 0;
Load();
} // initReplayd
//-----------------------------------------------------------------------------
/** Initialise the replay for a new recording. It especially allocates memory
* to store the replay data.
*/
void Replay::initRecording()
{
unsigned int size = stk_config->m_max_history
* race_manager->getNumberOfKarts();
m_events.resize(size);
m_next = 0;
m_ghost_karts.clear();
} // initRecording
//-----------------------------------------------------------------------------
/** Resets all ghost karts back to start position.
*/
void Replay::reset()
{
m_next = 0;
for(unsigned int i=0; i<m_ghost_karts.size(); i++)
{
m_ghost_karts[i]->reset();
}
} // reset
//-----------------------------------------------------------------------------
/** Depending on mode either saves the data for the current time step, or
* replays the data.
* /param dt Time step.
*/
void Replay::update(float dt)
{
if(m_do_replay)
updateReplay(dt);
else
updateRecording(dt);
} // update
//-----------------------------------------------------------------------------
/** Saves the current replay data.
* \param dt Time step size.
*/
void Replay::updateRecording(float dt)
{
World *world = World::getWorld();
unsigned int num_karts = world->getNumKarts();
if(m_next + num_karts>=m_events.size())
{
printf("Can't store more replay information.\n");
return;
}
// Once we use interpolate results, we don't have to increase
// m_next by num_karts, so count how often to increase
unsigned int count = 0;
for(unsigned int i=0; i<num_karts; i++)
{
ReplayEvent *p = &(m_events[m_next+i]);
const Kart *kart = world->getKart(i);
p->m_time = World::getWorld()->getTime();
p->m_type = ReplayEvent::EV_TRANSFORM;
p->m_kart_id = i;
p->m_event.m_t = kart->getTrans();
count ++;
} // for i
m_next += count;
} // updateRecording
//-----------------------------------------------------------------------------
/** Updates all ghost karts.
* \param dt Time step size.
*/
void Replay::updateReplay(float dt)
{
// First update all ghost karts
for(unsigned int i=0; i<m_ghost_karts.size(); i++)
m_ghost_karts[i]->update(dt);
} // updateReplay
//-----------------------------------------------------------------------------
/** Saves the replay data stored in the internal data structures.
*/
void Replay::Save()
{
std::string filename = file_manager->getConfigDir()+"/"
+ race_manager->getTrackName()+".replay";
FILE *fd = fopen(filename.c_str(),"w");
if(!fd)
{
filename = race_manager->getTrackName()+".replay";
fd = fopen(filename.c_str(), "w");
}
if(!fd)
{
printf("Can't open '%s' for writing - can't save replay data.\n",
filename.c_str());
return;
}
printf("Replay saved in '%s'.\n", filename.c_str());
World *world = World::getWorld();
int num_karts = world->getNumKarts();
fprintf(fd, "Version: %s\n", STK_VERSION);
fprintf(fd, "difficulty: %d\n", race_manager->getDifficulty());
fprintf(fd, "track: %s\n", world->getTrack()->getIdent().c_str());
fprintf(fd, "Laps: %d\n", race_manager->getNumLaps());
fprintf(fd, "numkarts: %d\n", num_karts);
int k;
for(k=0; k<num_karts; k++)
{
fprintf(fd, "model %d: %s\n",k, world->getKart(k)->getIdent().c_str());
}
fprintf(fd, "size: %d\n", m_next);
for(unsigned int i=0; i<m_events.size(); i++)
{
const ReplayEvent *p=&(m_events[i]);
if(p->m_type==ReplayEvent::EV_TRANSFORM)
fprintf(fd, "%d %f %d %f %f %f %f %f %f %f\n",
p->m_type, p->m_time, p->m_kart_id,
p->m_event.m_t.getOrigin().getX(),
p->m_event.m_t.getOrigin().getY(),
p->m_event.m_t.getOrigin().getZ(),
p->m_event.m_t.getRotation().getX(),
p->m_event.m_t.getRotation().getY(),
p->m_event.m_t.getRotation().getZ(),
p->m_event.m_t.getRotation().getW()
);
} // for k
fprintf(fd, "Replay file end.\n");
fclose(fd);
} // Save
//-----------------------------------------------------------------------------
/** Loads a replay data from file called 'trackname'.replay.
*/
void Replay::Load()
{
char s[1024], s1[1024];
int n;
std::string filename = file_manager->getConfigDir()+"/"
+ race_manager->getTrackName() + ".replay";
FILE *fd = fopen(filename.c_str(),"r");
if(!fd)
{
filename = race_manager->getTrackName()+".replay";
fd = fopen(filename.c_str(), "r");
if(!fd)
{
printf("Can't read '%s', ghost replay disabled.\n",
filename.c_str());
m_do_replay = false;
initRecording();
return;
}
}
printf("Reading replay file '%s'.\n", filename.c_str());
if (fgets(s, 1023, fd) == NULL)
{
fprintf(stderr, "ERROR: could not read '%s'.\n", filename.c_str());
exit(-2);
}
if (sscanf(s,"Version: %s",s1)!=1)
{
fprintf(stderr, "ERROR: no Version information found in replay file"
" (bogus replay file)\n");
exit(-2);
}
else
{
if (strcmp(s1,STK_VERSION))
{
fprintf(stderr, "WARNING: replay is version '%s'\n",s1);
fprintf(stderr, " STK version is '%s'\n",STK_VERSION);
}
}
if (fgets(s, 1023, fd) == NULL)
{
fprintf(stderr, "ERROR: could not read '%s'.\n", filename.c_str());
exit(-2);
}
if(sscanf(s, "difficulty: %d",&n)!=1)
{
fprintf(stderr,"WARNING: No difficulty found in replay file.\n");
exit(-2);
}
if(race_manager->getDifficulty()!=(RaceManager::Difficulty)n)
printf("Warning, difficulty of replay is '%d', "
"while '%d' is selected.\n",
race_manager->getDifficulty(), n);
fgets(s, 1023, fd);
if(sscanf(s, "track: %s",s1)!=1)
{
fprintf(stderr,"WARNING: Track not found in replay file.\n");
}
assert(std::string(s1)==race_manager->getTrackName());
race_manager->setTrack(s1);
unsigned int num_laps;
fgets(s, 1023, fd);
if(sscanf(s, "Laps: %d",&num_laps)!=1)
{
fprintf(stderr,"WARNING: No number of laps found in replay file.\n");
exit(-2);
}
race_manager->setNumLaps(num_laps);
fgets(s, 1023, fd);
if(sscanf(s, "numkarts: %d",&m_num_ghost_karts)!=1)
{
fprintf(stderr,"WARNING: No number of karts found in replay file.\n");
exit(-2);
}
for(unsigned int i=0; i<m_num_ghost_karts; i++)
{
fgets(s, 1023, fd);
if(sscanf(s, "model %d: %s",&n, s1)!=2)
{
fprintf(stderr,
"WARNING: No model information for kart %d found.\n",
i);
exit(-2);
}
m_ghost_karts.push_back(new GhostKart(std::string(s1)));
m_kart_ident.push_back(s1);
if(i<race_manager->getNumPlayers())
{
race_manager->setLocalKartInfo(i, s1);
}
} // for i<nKarts
// FIXME: The model information is currently ignored
fgets(s, 1023, fd);
unsigned int size;
if(sscanf(s,"size: %d",&size)!=1)
{
fprintf(stderr,
"WARNING: Number of records not found in replay file.\n");
exit(-2);
}
m_events.resize(size);
m_next = 0;
for(unsigned int i=0; i<size; i++)
{
fgets(s, 1023, fd);
unsigned int kart_id;
float x, y, z, rx, ry, rz, rw, time;
// Check for EV_TRANSFORM event:
// -----------------------------
if(sscanf(s, "0 %f %d %f %f %f %f %f %f %f\n",
&time, &kart_id,
&x, &y, &z,
&rx, &ry, &rz, &rw
)==9)
{
btQuaternion q(rx, ry, rz, rw);
btVector3 xyz(x, y, z);
m_ghost_karts[kart_id]->addTransform(time, btTransform(q, xyz));
}
else
{
// Invalid record found
// ---------------------
fprintf(stderr, "Can't read replay data line %d:\n", i);
fprintf(stderr, "%s", s);
fprintf(stderr, "Ignored.\n");
}
} // for k
fprintf(fd, "Replay file end.\n");
fclose(fd);
} // Load

View File

@ -1,108 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2012 Joerg Henrichs
//
// This program 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.
//
// This program 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_REPLAY_HPP
#define HEADER_REPLAY_HPP
#include "LinearMath/btTransform.h"
#include "utils/no_copy.hpp"
#include <string>
#include <vector>
class GhostKart;
/**
* \ingroup race
*/
class Replay : public NoCopy
{
private:
struct ReplayEvent
{
/** The id of the kart for which triggers this event. */
unsigned int m_kart_id;
/** Time at which this event happens. */
float m_time;
enum {EV_TRANSFORM, EV_NONE} m_type;
struct {
btTransform m_t;
} m_event; // union
}; // ReplayEvent
// ------------------------------------------------------------------------
/** The array storing all events. */
std::vector<ReplayEvent> m_events;
/** Static pointer to the one instance of the replay object. */
static Replay *m_replay;
/** True if a replay is done. */
static bool m_do_replay;
/** Points to the next free entry. */
unsigned int m_next;
/** Number of (ghost) karts contained in the replay file. */
unsigned int m_num_ghost_karts;
/** The identities of the karts to use. */
std::vector<std::string> m_kart_ident;
/** All ghost karts. */
std::vector<GhostKart*> m_ghost_karts;
void updateRecording (float dt);
void updateReplay(float dt);
Replay();
~Replay();
public:
void initReplay();
void initRecording();
void update(float dt);
void reset();
void Save();
void Load();
// ------------------------------------------------------------------------
/** Creates a new instance of the replay object. */
static void create() { m_replay = new Replay(); }
// ------------------------------------------------------------------------
/** Returns the instance of the replay object. */
static Replay *get() { assert(m_replay); return m_replay; }
// ------------------------------------------------------------------------
/** Delete the instance of the replay object. */
static void destroy() { delete m_replay; m_replay=NULL; }
// ------------------------------------------------------------------------
/** Sets that a replay is to be done. */
static void doReplay() { m_do_replay = true; }
// ------------------------------------------------------------------------
/** Returns if a replay is to be done. */
static bool isReplay() { return m_do_replay; }
// ------------------------------------------------------------------------
/** Returns the identifier of the n-th kart. */
const std::string& getKartIdent(unsigned int n) const
{
return m_kart_ident[n];
}
// ------------------------------------------------------------------------
/** Returns the number of karts contained in the replay file. */
unsigned int getNumberGhostKarts() const { return m_num_ghost_karts;}
}; // Replay
#endif

View File

@ -0,0 +1,46 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2012 Joerg Henrichs
//
// This program 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.
//
// This program 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "replay/replay_base.hpp"
#include "io/file_manager.hpp"
#include "race/race_manager.hpp"
// -----------------------------------------------------------------------------
ReplayBase::ReplayBase()
{
m_filename = "";
} // ReplayBaese
// -----------------------------------------------------------------------------
/** Opens a replay file (depending on the track name, which is taken from
* the race manager).
* \param writeable True if the file should be opened for writing.
* \return A FILE *, or NULL if the file could not be opened.
*/
FILE* ReplayBase::openReplayFile(bool writeable)
{
m_filename = file_manager->getConfigDir()+"/"
+ race_manager->getTrackName()+".replay";
FILE *fd = fopen(m_filename.c_str(), writeable ? "w" : "r");
if(!fd)
{
m_filename = race_manager->getTrackName()+".replay";
fd = fopen(m_filename.c_str(), writeable ? "w" : "r");
}
return fd;
} // openReplayFilen

View File

@ -0,0 +1,61 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2012 Joerg Henrichs
//
// This program 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.
//
// This program 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_REPLAY_BASE_HPP
#define HEADER_REPLAY_BASE_HPP
#include "LinearMath/btTransform.h"
#include "utils/no_copy.hpp"
#include <stdio.h>
#include <string>
/**
* \ingroup race
*/
class ReplayBase : public NoCopy
{
private:
/** The filename of the replay file. Only defined after calling
* openReplayFile. */
std::string m_filename;
protected:
/** Stores a transform event, i.e. a position and rotation of a kart
* at a certain time. */
struct TransformEvent
{
/** Time at which this event happens. */
float m_time;
/** The transform at a certain time. */
btTransform m_transform;
}; // TransformEvent
// ------------------------------------------------------------------------
ReplayBase();
// ------------------------------------------------------------------------
FILE *openReplayFile(bool writeable);
// ----------------------------------------------------------------------
/** Returns the filename that was opened. */
const std::string &getReplayFilename() const { return m_filename;}
// ----------------------------------------------------------------------
/** Returns the version number of the replay file. This is used to check
* that a loaded replay file can still be understood by this
* executable. */
unsigned int getReplayVersion() const { return 1; }
}; // ReplayBase
#endif

230
src/replay/replay_play.cpp Normal file
View File

@ -0,0 +1,230 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2012 Joerg Henrichs
//
// This program 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.
//
// This program 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "replay/replay_play.hpp"
#include "config/stk_config.hpp"
#include "io/file_manager.hpp"
#include "karts/ghost_kart.hpp"
#include "modes/world.hpp"
#include "race/race_manager.hpp"
#include "tracks/track.hpp"
#include <stdio.h>
#include <string>
ReplayPlay *ReplayPlay::m_replay_play = NULL;
//-----------------------------------------------------------------------------
/** Initialises the Replay engine
*/
ReplayPlay::ReplayPlay()
{
m_next = 0;
} // ReplayPlay
//-----------------------------------------------------------------------------
/** Frees all stored data. */
ReplayPlay::~ReplayPlay()
{
} // ~Replay
//-----------------------------------------------------------------------------
/** Starts replay from the replay file in the current directory.
*/
void ReplayPlay::init()
{
m_next = 0;
Load();
} // init
//-----------------------------------------------------------------------------
/** Resets all ghost karts back to start position.
*/
void ReplayPlay::reset()
{
m_next = 0;
for(unsigned int i=0; i<m_ghost_karts.size(); i++)
{
m_ghost_karts[i]->reset();
}
} // reset
//-----------------------------------------------------------------------------
/** Updates all ghost karts.
* \param dt Time step size.
*/
void ReplayPlay::update(float dt)
{
// First update all ghost karts
for(unsigned int i=0; i<m_ghost_karts.size(); i++)
m_ghost_karts[i]->update(dt);
} // update
//-----------------------------------------------------------------------------
/** Loads a replay data from file called 'trackname'.replay.
*/
void ReplayPlay::Load()
{
for(unsigned int i=0; i<m_ghost_karts.size(); i++)
{
delete m_ghost_karts[i];
}
m_ghost_karts.clear();
char s[1024], s1[1024];
int n;
FILE *fd = openReplayFile(/*writeable*/false);
if(!fd)
{
printf("Can't read '%s', ghost replay disabled.\n",
getReplayFilename().c_str());
destroy();
return;
}
printf("Reading replay file '%s'.\n", getReplayFilename().c_str());
if (fgets(s, 1023, fd) == NULL)
{
fprintf(stderr, "ERROR: could not read '%s'.\n",
getReplayFilename().c_str());
exit(-2);
}
int version;
if (sscanf(s,"Version: %d", &version)!=1)
{
fprintf(stderr, "ERROR: no Version information found in replay file"
" (bogus replay file)\n");
exit(-2);
}
if (version!=getReplayVersion())
{
fprintf(stderr, "WARNING: replay is version '%d'\n",version);
fprintf(stderr, " STK version is '%s'\n",getReplayVersion());
fprintf(stderr, " We try to proceed, but it may fail.\n");
}
if (fgets(s, 1023, fd) == NULL)
{
fprintf(stderr, "ERROR: could not read '%s'.\n",
getReplayFilename().c_str());
exit(-2);
}
if(sscanf(s, "difficulty: %d",&n)!=1)
{
fprintf(stderr,"WARNING: No difficulty found in replay file.\n");
exit(-2);
}
if(race_manager->getDifficulty()!=(RaceManager::Difficulty)n)
printf("Warning, difficulty of replay is '%d', "
"while '%d' is selected.\n",
race_manager->getDifficulty(), n);
fgets(s, 1023, fd);
if(sscanf(s, "track: %s",s1)!=1)
{
fprintf(stderr,"WARNING: Track not found in replay file.\n");
}
assert(std::string(s1)==race_manager->getTrackName());
race_manager->setTrack(s1);
unsigned int num_laps;
fgets(s, 1023, fd);
if(sscanf(s, "Laps: %d",&num_laps)!=1)
{
fprintf(stderr,"WARNING: No number of laps found in replay file.\n");
exit(-2);
}
race_manager->setNumLaps(num_laps);
fgets(s, 1023, fd);
unsigned int num_ghost_karts;
if(sscanf(s, "numkarts: %d",&num_ghost_karts)!=1)
{
fprintf(stderr,"WARNING: No number of karts found in replay file.\n");
exit(-2);
}
for(unsigned int k=0; k<num_ghost_karts; k++)
{
fgets(s, 1023, fd);
if(sscanf(s, "model %d: %s",&n, s1)!=2)
{
fprintf(stderr,
"WARNING: No model information for kart %d found.\n",
k);
exit(-2);
}
if(n != k)
{
fprintf(stderr,
"WARNING: Expected kart id %d, got %d - ignored.\n",
k, n);
}
m_ghost_karts.push_back(new GhostKart(std::string(s1)));
fgets(s, 1023, fd);
unsigned int size;
if(sscanf(s,"size: %d",&size)!=1)
{
fprintf(stderr,
"WARNING: Number of records not found in replay file "
"for kart %d.\n",
k);
exit(-2);
}
for(unsigned int i=0; i<size; i++)
{
fgets(s, 1023, fd);
float x, y, z, rx, ry, rz, rw, time;
// Check for EV_TRANSFORM event:
// -----------------------------
if(sscanf(s, "%f %f %f %f %f %f %f %f\n",
&time,
&x, &y, &z,
&rx, &ry, &rz, &rw
)==8)
{
btQuaternion q(rx, ry, rz, rw);
btVector3 xyz(x, y, z);
m_ghost_karts[k]->addTransform(time, btTransform(q, xyz));
}
else
{
// Invalid record found
// ---------------------
fprintf(stderr, "Can't read replay data line %d:\n", i);
fprintf(stderr, "%s", s);
fprintf(stderr, "Ignored.\n");
}
} // for k
} // for i<nKarts
fprintf(fd, "Replay file end.\n");
fclose(fd);
} // Load

View File

@ -0,0 +1,62 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2012 Joerg Henrichs
//
// This program 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.
//
// This program 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_REPLAY__PLAY_HPP
#define HEADER_REPLAY__PLAY_HPP
#include "replay/replay_base.hpp"
#include <string>
#include <vector>
class GhostKart;
/**
* \ingroup replay
*/
class ReplayPlay : public ReplayBase
{
private:
static ReplayPlay *m_replay_play;
/** Points to the next free entry. */
unsigned int m_next;
/** All ghost karts. */
std::vector<GhostKart*> m_ghost_karts;
ReplayPlay();
~ReplayPlay();
public:
void init();
void update(float dt);
void reset();
void Load();
// ------------------------------------------------------------------------
/** Creates a new instance of the replay object. */
static void create() { m_replay_play = new ReplayPlay(); }
// ------------------------------------------------------------------------
/** Returns the instance of the replay object. */
static ReplayPlay *get() { return m_replay_play; }
// ------------------------------------------------------------------------
/** Delete the instance of the replay object. */
static void destroy() { delete m_replay_play; m_replay_play=NULL; }
}; // Replay
#endif

View File

@ -0,0 +1,166 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2012 Joerg Henrichs
//
// This program 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.
//
// This program 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "replay/replay_recorder.hpp"
#include "config/stk_config.hpp"
#include "io/file_manager.hpp"
#include "karts/ghost_kart.hpp"
#include "modes/world.hpp"
#include "race/race_manager.hpp"
#include "tracks/track.hpp"
#include <stdio.h>
#include <string>
ReplayRecorder *ReplayRecorder::m_replay_recorder = NULL;
//-----------------------------------------------------------------------------
/** Initialises the Replay engine
*/
ReplayRecorder::ReplayRecorder()
{
} // ReplayRecorder
//-----------------------------------------------------------------------------
/** Frees all stored data. */
ReplayRecorder::~ReplayRecorder()
{
m_transform_events.clear();
} // ~Replay
//-----------------------------------------------------------------------------
/** Initialise the replay recorder. It especially allocates memory
* to store the replay data.
*/
void ReplayRecorder::init()
{
m_transform_events.clear();
m_transform_events.resize(race_manager->getNumberOfKarts());
for(unsigned int i=0; i<race_manager->getNumberOfKarts(); i++)
{
m_transform_events[i].resize(stk_config->m_max_history);
}
m_count_transforms.clear();
m_count_transforms.resize(race_manager->getNumberOfKarts(), 0);
m_last_saved_time.clear();
m_last_saved_time.resize(race_manager->getNumberOfKarts(), -1.0f);
#ifdef DEBUG
m_count = 0;
m_count_skipped_time = 0;
m_count_skipped_interpolation = 0;
#endif
} // init
//-----------------------------------------------------------------------------
/** Resets all ghost karts back to start position.
*/
void ReplayRecorder::reset()
{
} // reset
//-----------------------------------------------------------------------------
/** Saves the current replay data.
* \param dt Time step size.
*/
void ReplayRecorder::update(float dt)
{
World *world = World::getWorld();
unsigned int num_karts = world->getNumKarts();
float time = world->getTime();
// Once we use interpolate results, we don't have to increase
// m_next by num_karts, so count how often to increase
unsigned int count = 0;
for(unsigned int i=0; i<num_karts; i++)
{
#ifdef DEBUG
m_count ++;
#endif
if(time - m_last_saved_time[i]<stk_config->m_replay_dt)
{
#ifdef DEBUG
m_count_skipped_time ++;
#endif
continue;
}
m_count_transforms[i]++;
const Kart *kart = world->getKart(i);
if(m_count_transforms[i]>=m_transform_events[i].size())
{
// Only print this message once.
if(m_count_transforms[i]==m_transform_events[i].size())
printf("Can't store more events for kart %s.\n",
kart->getIdent().c_str());
continue;
}
TransformEvent *p = &(m_transform_events[i][m_count_transforms[i]-1]);
p->m_time = World::getWorld()->getTime();
p->m_transform = kart->getTrans();
} // for i
} // updateRecording
//-----------------------------------------------------------------------------
/** Saves the replay data stored in the internal data structures.
*/
void ReplayRecorder::Save()
{
FILE *fd = openReplayFile(/*writeable*/true);
if(!fd)
{
printf("Can't open '%s' for writing - can't save replay data.\n",
getReplayFilename().c_str());
return;
}
printf("Replay saved in '%s'.\n", getReplayFilename().c_str());
World *world = World::getWorld();
unsigned int num_karts = world->getNumKarts();
fprintf(fd, "Version: %d\n", getReplayVersion());
fprintf(fd, "difficulty: %d\n", race_manager->getDifficulty());
fprintf(fd, "track: %s\n", world->getTrack()->getIdent().c_str());
fprintf(fd, "Laps: %d\n", race_manager->getNumLaps());
fprintf(fd, "numkarts: %d\n", num_karts);
for(unsigned int k=0; k<num_karts; k++)
{
fprintf(fd, "model %d: %s\n",k, world->getKart(k)->getIdent().c_str());
fprintf(fd, "size: %d\n", m_count_transforms[k]);
for(unsigned int i=0; i<m_count_transforms[k]; i++)
{
const TransformEvent *p=&(m_transform_events[k][i]);
fprintf(fd, "%f %f %f %f %f %f %f %f\n",
p->m_time,
p->m_transform.getOrigin().getX(),
p->m_transform.getOrigin().getY(),
p->m_transform.getOrigin().getZ(),
p->m_transform.getRotation().getX(),
p->m_transform.getRotation().getY(),
p->m_transform.getRotation().getZ(),
p->m_transform.getRotation().getW()
);
} // for k
}
fprintf(fd, "Replay file end.\n");
fclose(fd);
} // Save

View File

@ -0,0 +1,80 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2012 Joerg Henrichs
//
// This program 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.
//
// This program 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_REPLAY_RECORDER_HPP
#define HEADER_REPLAY_RECORDER_HPP
#include "replay/replay_base.hpp"
#include <vector>
/**
* \ingroup replay
*/
class ReplayRecorder : public ReplayBase
{
private:
/** A separate vector of Replay Events for all transforms. */
std::vector< std::vector<TransformEvent> > m_transform_events;
/** Time at which a transform was saved for the last time. */
std::vector<float> m_last_saved_time;
/** Counts the number of transform events for each kart. */
std::vector<unsigned int> m_count_transforms;
/** Static pointer to the one instance of the replay object. */
static ReplayRecorder *m_replay_recorder;
#ifdef DEBUG
/** Counts overall number of events stored. */
unsigned int m_count;
/** Counts number of events skipped due to minimum time between events. */
unsigned int m_count_skipped_time;
/** Counts number of events skipped due to interpolation. */
unsigned int m_count_skipped_interpolation;
#endif
ReplayRecorder();
~ReplayRecorder();
public:
void init();
void update(float dt);
void reset();
void Save();
// ------------------------------------------------------------------------
/** Creates a new instance of the replay object. */
static void create() {
assert(!m_replay_recorder);
m_replay_recorder = new ReplayRecorder();
}
// ------------------------------------------------------------------------
/** Returns the instance of the replay object. Returns NULL if no
* recorder is available, i.e. recording can be disabled. */
static ReplayRecorder *get() { return m_replay_recorder; }
// ------------------------------------------------------------------------
/** Delete the instance of the replay object. */
static void destroy() { delete m_replay_recorder; m_replay_recorder=NULL; }
}; // ReplayRecorder
#endif