520 lines
17 KiB
C++
520 lines
17 KiB
C++
//
|
|
// SuperTuxKart - a fun racing game with go-kart
|
|
// Copyright (C) 2012-2015 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 "karts/controller/ghost_controller.hpp"
|
|
#include "modes/world.hpp"
|
|
#include "race/race_manager.hpp"
|
|
#include "tracks/track.hpp"
|
|
#include "tracks/track_manager.hpp"
|
|
|
|
#include <irrlicht.h>
|
|
#include <stdio.h>
|
|
#include <string>
|
|
#include <cinttypes>
|
|
|
|
ReplayPlay::SortOrder ReplayPlay::m_sort_order = ReplayPlay::SO_DEFAULT;
|
|
ReplayPlay *ReplayPlay::m_replay_play = NULL;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Initialises the Replay engine
|
|
*/
|
|
ReplayPlay::ReplayPlay()
|
|
{
|
|
m_current_replay_file = 0;
|
|
m_second_replay_file = 0;
|
|
m_second_replay_enabled = false;
|
|
} // ReplayPlay
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Frees all stored data. */
|
|
ReplayPlay::~ReplayPlay()
|
|
{
|
|
} // ~Replay
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Resets all ghost karts back to start position.
|
|
*/
|
|
void ReplayPlay::reset()
|
|
{
|
|
for(unsigned int i=0; i<(unsigned int)m_ghost_karts.size(); i++)
|
|
{
|
|
m_ghost_karts[i]->reset();
|
|
}
|
|
} // reset
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ReplayPlay::loadAllReplayFile()
|
|
{
|
|
m_replay_file_list.clear();
|
|
|
|
// Load stock replay first
|
|
std::set<std::string> pre_record;
|
|
file_manager->listFiles(pre_record, file_manager
|
|
->getAssetDirectory(FileManager::REPLAY), /*is_full_path*/ true);
|
|
for (std::set<std::string>::iterator i = pre_record.begin();
|
|
i != pre_record.end(); ++i)
|
|
{
|
|
if (!addReplayFile(*i, /*custom_replay*/ true))
|
|
{
|
|
// Skip invalid replay file
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Now user recorded replay
|
|
std::set<std::string> files;
|
|
file_manager->listFiles(files, file_manager->getReplayDir(),
|
|
/*is_full_path*/ false);
|
|
|
|
int j=0;
|
|
|
|
for (std::set<std::string>::iterator i = files.begin();
|
|
i != files.end(); ++i)
|
|
{
|
|
if (!addReplayFile(*i, false, j))
|
|
{
|
|
// Skip invalid replay file
|
|
continue;
|
|
}
|
|
j++;
|
|
}
|
|
|
|
} // loadAllReplayFile
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool ReplayPlay::addReplayFile(const std::string& fn, bool custom_replay, int call_index)
|
|
{
|
|
|
|
char s[1024], s1[1024];
|
|
if (StringUtils::getExtension(fn) != "replay") return false;
|
|
FILE *fd = fopen(custom_replay ? fn.c_str() :
|
|
(file_manager->getReplayDir() + fn).c_str(), "r");
|
|
if (fd == NULL) return false;
|
|
ReplayData rd;
|
|
|
|
// custom_replay is true when full path of filename is given
|
|
rd.m_custom_replay_file = custom_replay;
|
|
rd.m_filename = fn;
|
|
|
|
fgets(s, 1023, fd);
|
|
unsigned int version;
|
|
if (sscanf(s,"version: %u", &version) != 1)
|
|
{
|
|
Log::warn("Replay", "No Version information "
|
|
"found in replay file (bogus replay file).");
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
if (version > getCurrentReplayVersion() ||
|
|
version < getMinSupportedReplayVersion() )
|
|
{
|
|
Log::warn("Replay", "Replay is version '%d'", version);
|
|
Log::warn("Replay", "STK replay version is '%d'", getCurrentReplayVersion());
|
|
Log::warn("Replay", "Minimum supported replay version is '%d'", getMinSupportedReplayVersion());
|
|
Log::warn("Replay", "Skipped '%s'", fn.c_str());
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
rd.m_replay_version = version;
|
|
|
|
if (version >= 4)
|
|
{
|
|
fgets(s, 1023, fd);
|
|
if(sscanf(s, "stk_version: %s", s1) != 1)
|
|
{
|
|
Log::warn("Replay", "No STK release version found in replay file.");
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
rd.m_stk_version = s1;
|
|
}
|
|
else
|
|
rd.m_stk_version = "";
|
|
|
|
while(true)
|
|
{
|
|
fgets(s, 1023, fd);
|
|
core::stringc is_end(s);
|
|
is_end.trim();
|
|
if (is_end == "kart_list_end") break;
|
|
char s1[1024];
|
|
char display_name_encoded[1024];
|
|
|
|
int scanned = sscanf(s,"kart: %s %[^\n]", s1, display_name_encoded);
|
|
if (scanned < 1)
|
|
{
|
|
Log::warn("Replay", "Could not read ghost karts info!");
|
|
break;
|
|
}
|
|
|
|
rd.m_kart_list.push_back(std::string(s1));
|
|
if (scanned == 2)
|
|
{
|
|
// If username of kart is present, use it
|
|
rd.m_name_list.push_back(StringUtils::xmlDecode(std::string(display_name_encoded)));
|
|
if (rd.m_name_list.size() == 1)
|
|
{
|
|
// First user is the game master and the "owner" of this replay file
|
|
rd.m_user_name = rd.m_name_list[0];
|
|
}
|
|
} else
|
|
{ // scanned == 1
|
|
// If username is not present, kart display name will default to kart name
|
|
// (see GhostController::getName)
|
|
rd.m_name_list.push_back("");
|
|
}
|
|
|
|
// Read kart color data
|
|
if (version >= 4)
|
|
{
|
|
float f = 0;
|
|
fgets(s, 1023, fd);
|
|
if(sscanf(s, "kart_color: %f", &f) != 1)
|
|
{
|
|
Log::warn("Replay", "Kart color missing in replay file.");
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
rd.m_kart_color.push_back(f);
|
|
}
|
|
else
|
|
rd.m_kart_color.push_back(0.0f); // Use default kart color
|
|
}
|
|
|
|
int reverse = 0;
|
|
fgets(s, 1023, fd);
|
|
if(sscanf(s, "reverse: %d", &reverse) != 1)
|
|
{
|
|
Log::warn("Replay", "No reverse info found in replay file.");
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
rd.m_reverse = reverse != 0;
|
|
|
|
fgets(s, 1023, fd);
|
|
if (sscanf(s, "difficulty: %u", &rd.m_difficulty) != 1)
|
|
{
|
|
Log::warn("Replay", " No difficulty found in replay file.");
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
|
|
if (version >= 4)
|
|
{
|
|
fgets(s, 1023, fd);
|
|
if (sscanf(s, "mode: %s", s1) != 1)
|
|
{
|
|
Log::warn("Replay", "Replay mode not found in replay file.");
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
rd.m_minor_mode = s1;
|
|
}
|
|
// Assume time-trial mode for old replays
|
|
else
|
|
rd.m_minor_mode = "time-trial";
|
|
|
|
|
|
fgets(s, 1023, fd);
|
|
if (sscanf(s, "track: %s", s1) != 1)
|
|
{
|
|
Log::warn("Replay", "Track info not found in replay file.");
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
rd.m_track_name = std::string(s1);
|
|
Track* t = track_manager->getTrack(rd.m_track_name);
|
|
if (t == NULL)
|
|
{
|
|
Log::warn("Replay", "Track '%s' used in replay not found in STK!",
|
|
rd.m_track_name.c_str());
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
|
|
rd.m_track = t;
|
|
|
|
fgets(s, 1023, fd);
|
|
if (sscanf(s, "laps: %u", &rd.m_laps) != 1)
|
|
{
|
|
Log::warn("Replay", "No number of laps found in replay file.");
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
|
|
fgets(s, 1023, fd);
|
|
if (sscanf(s, "min_time: %f", &rd.m_min_time) != 1)
|
|
{
|
|
Log::warn("Replay", "Finish time not found in replay file.");
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
|
|
if (version >= 4)
|
|
{
|
|
fgets(s, 1023, fd);
|
|
if (sscanf(s, "replay_uid: %" PRIu64, &rd.m_replay_uid) != 1)
|
|
{
|
|
Log::warn("Replay", "Replay UID not found in replay file.");
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
}
|
|
// No UID in old replay format
|
|
else
|
|
rd.m_replay_uid = call_index;
|
|
|
|
fclose(fd);
|
|
m_replay_file_list.push_back(rd);
|
|
|
|
assert(m_replay_file_list.size() > 0);
|
|
// Force to use custom replay file immediately
|
|
if (custom_replay)
|
|
m_current_replay_file = (unsigned int)m_replay_file_list.size() - 1;
|
|
|
|
return true;
|
|
|
|
} // addReplayFile
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ReplayPlay::load()
|
|
{
|
|
m_ghost_karts.clear();
|
|
|
|
if (m_second_replay_enabled)
|
|
loadFile(/* second replay */ true);
|
|
|
|
// Always load the first replay
|
|
loadFile(/* second replay */ false);
|
|
|
|
} // load
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ReplayPlay::loadFile(bool second_replay)
|
|
{
|
|
char s[1024];
|
|
|
|
int replay_index = second_replay ? m_second_replay_file : m_current_replay_file;
|
|
int replay_file_number = second_replay ? 2 : 1;
|
|
|
|
FILE *fd = openReplayFile(/*writeable*/false,
|
|
m_replay_file_list.at(replay_index).m_custom_replay_file, replay_file_number);
|
|
|
|
if(!fd)
|
|
{
|
|
Log::error("Replay", "Can't read '%s', ghost replay disabled.",
|
|
getReplayFilename(replay_file_number).c_str());
|
|
destroy();
|
|
return;
|
|
}
|
|
|
|
Log::info("Replay", "Reading replay file '%s'.", getReplayFilename(replay_file_number).c_str());
|
|
|
|
ReplayData &rd = m_replay_file_list[replay_index];
|
|
unsigned int num_kart = m_replay_file_list.at(replay_index).m_kart_list.size();
|
|
unsigned int lines_to_skip = (rd.m_replay_version == 3) ? 7 : 10;
|
|
lines_to_skip += (rd.m_replay_version == 3) ? num_kart : 2*num_kart;
|
|
|
|
for (unsigned int i = 0; i < lines_to_skip; i++)
|
|
fgets(s, 1023, fd);
|
|
|
|
// eof actually doesn't trigger here, since it requires first to try
|
|
// reading behind eof, but still it's clearer this way.
|
|
while(!feof(fd))
|
|
{
|
|
if(fgets(s, 1023, fd)==NULL) // eof reached
|
|
break;
|
|
readKartData(fd, s, second_replay);
|
|
}
|
|
|
|
fclose(fd);
|
|
} // loadFile
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Reads all data from a replay file for a specific kart.
|
|
* \param fd The file descriptor from which to read.
|
|
*/
|
|
void ReplayPlay::readKartData(FILE *fd, char *next_line, bool second_replay)
|
|
{
|
|
char s[1024];
|
|
|
|
int replay_index = second_replay ? m_second_replay_file : m_current_replay_file;
|
|
|
|
const unsigned int kart_num = m_ghost_karts.size();
|
|
unsigned int first_loaded_f_num = 0;
|
|
|
|
if (!second_replay && m_second_replay_enabled)
|
|
first_loaded_f_num = m_replay_file_list.at(m_second_replay_file).m_kart_list.size();
|
|
|
|
ReplayData &rd = m_replay_file_list[replay_index];
|
|
m_ghost_karts.push_back(std::make_shared<GhostKart>
|
|
(rd.m_kart_list.at(kart_num-first_loaded_f_num), kart_num, kart_num + 1,
|
|
rd.m_kart_color.at(kart_num-first_loaded_f_num)));
|
|
m_ghost_karts[kart_num]->init(RaceManager::KT_GHOST);
|
|
Controller* controller = new GhostController(getGhostKart(kart_num).get(),
|
|
rd.m_name_list[kart_num-first_loaded_f_num]);
|
|
getGhostKart(kart_num)->setController(controller);
|
|
|
|
unsigned int size;
|
|
if(sscanf(next_line,"size: %u",&size)!=1)
|
|
Log::fatal("Replay", "Number of records not found in replay file "
|
|
"for kart %d.", kart_num);
|
|
|
|
for(unsigned int i=0; i<size; i++)
|
|
{
|
|
fgets(s, 1023, fd);
|
|
float x, y, z, rx, ry, rz, rw, time, speed, steer, w1, w2, w3, w4, nitro_amount, distance;
|
|
int skidding_state, attachment, item_amount, item_type, special_value,
|
|
nitro, zipper, skidding, red_skidding, jumping;
|
|
|
|
// Check for EV_TRANSFORM event:
|
|
// -----------------------------
|
|
|
|
// Up to STK 0.9.3 replays
|
|
if (rd.m_replay_version == 3)
|
|
{
|
|
if(sscanf(s, "%f %f %f %f %f %f %f %f %f %f %f %f %f %f %d %d %d %d %d\n",
|
|
&time,
|
|
&x, &y, &z,
|
|
&rx, &ry, &rz, &rw,
|
|
&speed, &steer, &w1, &w2, &w3, &w4,
|
|
&nitro, &zipper, &skidding, &red_skidding, &jumping
|
|
)==19)
|
|
{
|
|
btQuaternion q(rx, ry, rz, rw);
|
|
btVector3 xyz(x, y, z);
|
|
PhysicInfo pi = {0};
|
|
BonusInfo bi = {0};
|
|
KartReplayEvent kre = {0};
|
|
|
|
pi.m_speed = speed;
|
|
pi.m_steer = steer;
|
|
pi.m_suspension_length[0] = w1;
|
|
pi.m_suspension_length[1] = w2;
|
|
pi.m_suspension_length[2] = w3;
|
|
pi.m_suspension_length[3] = w4;
|
|
pi.m_skidding_state = 0; //not saved in version 3 replays
|
|
bi.m_attachment = 0; //not saved in version 3 replays
|
|
bi.m_nitro_amount = 0; //not saved in version 3 replays
|
|
bi.m_item_amount = 0; //not saved in version 3 replays
|
|
bi.m_item_type = 0; //not saved in version 3 replays
|
|
bi.m_special_value = 0; //not saved in version 3 replays
|
|
kre.m_distance = 0.0f; //not saved in version 3 replays
|
|
kre.m_nitro_usage = nitro;
|
|
kre.m_zipper_usage = zipper!=0;
|
|
kre.m_skidding_effect = skidding;
|
|
kre.m_red_skidding = red_skidding!=0;
|
|
kre.m_jumping = jumping != 0;
|
|
m_ghost_karts[kart_num]->addReplayEvent(time,
|
|
btTransform(q, xyz), pi, bi, kre);
|
|
}
|
|
else
|
|
{
|
|
// Invalid record found
|
|
// ---------------------
|
|
Log::warn("Replay", "Can't read replay data line %d:", i);
|
|
Log::warn("Replay", "%s", s);
|
|
Log::warn("Replay", "Ignored.");
|
|
}
|
|
}
|
|
|
|
//version 4 replays (STK 0.9.4 and higher)
|
|
else
|
|
{
|
|
if(sscanf(s, "%f %f %f %f %f %f %f %f %f %f %f %f %f %f %d %d %f %d %d %d %f %d %d %d %d %d\n",
|
|
&time,
|
|
&x, &y, &z,
|
|
&rx, &ry, &rz, &rw,
|
|
&speed, &steer, &w1, &w2, &w3, &w4, &skidding_state,
|
|
&attachment, &nitro_amount, &item_amount, &item_type, &special_value,
|
|
&distance, &nitro, &zipper, &skidding, &red_skidding, &jumping
|
|
)==26)
|
|
{
|
|
btQuaternion q(rx, ry, rz, rw);
|
|
btVector3 xyz(x, y, z);
|
|
PhysicInfo pi = {0};
|
|
BonusInfo bi = {0};
|
|
KartReplayEvent kre = {0};
|
|
|
|
pi.m_speed = speed;
|
|
pi.m_steer = steer;
|
|
pi.m_suspension_length[0] = w1;
|
|
pi.m_suspension_length[1] = w2;
|
|
pi.m_suspension_length[2] = w3;
|
|
pi.m_suspension_length[3] = w4;
|
|
pi.m_skidding_state = skidding_state;
|
|
bi.m_attachment = attachment;
|
|
bi.m_nitro_amount = nitro_amount;
|
|
bi.m_item_amount = item_amount;
|
|
bi.m_item_type = item_type;
|
|
bi.m_special_value = special_value;
|
|
kre.m_distance = distance;
|
|
kre.m_nitro_usage = nitro;
|
|
kre.m_zipper_usage = zipper!=0;
|
|
kre.m_skidding_effect = skidding;
|
|
kre.m_red_skidding = red_skidding!=0;
|
|
kre.m_jumping = jumping != 0;
|
|
m_ghost_karts[kart_num]->addReplayEvent(time,
|
|
btTransform(q, xyz), pi, bi, kre);
|
|
}
|
|
else
|
|
{
|
|
// Invalid record found
|
|
// ---------------------
|
|
Log::warn("Replay", "Can't read replay data line %d:", i);
|
|
Log::warn("Replay", "%s", s);
|
|
Log::warn("Replay", "Ignored.");
|
|
}
|
|
}
|
|
} // for i
|
|
|
|
} // readKartData
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** call getReplayIdByUID and set the current replay file to the first one
|
|
* with a matching UID.
|
|
* \param uid The UID to match
|
|
*/
|
|
void ReplayPlay::setReplayFileByUID(uint64_t uid)
|
|
{
|
|
m_current_replay_file = getReplayIdByUID(uid);
|
|
} //setReplayFileByUID
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Search among replay file and return the first index with a matching UID.
|
|
* \param uid The UID to match
|
|
*/
|
|
unsigned int ReplayPlay::getReplayIdByUID(uint64_t uid)
|
|
{
|
|
for(unsigned int i = 0; i < m_replay_file_list.size(); i++)
|
|
{
|
|
if (m_replay_file_list[i].m_replay_uid == uid)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
Log::error("Replay", "Replay with UID of " PRIu64 " not found.", uid);
|
|
return 0;
|
|
} //setReplayFileByUID
|