Save and use usernames in replay files

Fixes #2754.

* Store usernames of each racer in recorded replay files
* Display those usernames in a column of the replay selection UI
  and in race result dialogs
* RaceResultGUI::getKartDisplayName functionality moved into
  Controller::getName
This commit is contained in:
Geoffrey Mon
2017-08-02 12:49:14 -04:00
parent a89b2f5df2
commit f3a962c391
10 changed files with 57 additions and 50 deletions

View File

@@ -28,9 +28,9 @@ using namespace irr;
*/
#include "input/input.hpp"
#include "karts/abstract_kart.hpp"
#include "states_screens/state_manager.hpp"
class AbstractKart;
class Item;
class KartControl;
class Material;
@@ -100,17 +100,19 @@ public:
/** Only local players can get achievements. */
virtual bool canGetAchievements () const { return false; }
// ------------------------------------------------------------------------
/** This should only be called for End- and LocalPlayer-Controller. */
/** Display name of the controller.
* Defaults to kart name; overriden by controller classes
* (such as player controllers) to display username. */
virtual core::stringw getName() const
{
assert(false);
return core::stringw("");
return translations->fribidize(m_kart->getName());
} // getName
// ------------------------------------------------------------------------
/** Returns the kart controlled by this controller. */
AbstractKart *getKart() const { return m_kart; }
}; // Controller
extern Translations* translations;
#endif
/* EOF */

View File

@@ -21,9 +21,10 @@
#include "karts/controller/kart_control.hpp"
#include "modes/world.hpp"
GhostController::GhostController(AbstractKart *kart)
GhostController::GhostController(AbstractKart *kart, core::stringw display_name)
: Controller(kart)
{
m_display_name = display_name;
} // GhostController
//-----------------------------------------------------------------------------

View File

@@ -37,11 +37,14 @@ private:
/** The current world time. */
float m_current_time;
/** Player name of the ghost kart. */
core::stringw m_display_name;
/** The list of the times at which the events of kart were reached. */
std::vector<float> m_all_times;
public:
GhostController(AbstractKart *kart);
GhostController(AbstractKart *kart, core::stringw display_name);
virtual ~GhostController() {};
virtual void reset() OVERRIDE;
virtual void update (float dt) OVERRIDE;
@@ -73,6 +76,7 @@ public:
unsigned int getCurrentReplayIndex() const
{ return m_current_index; }
// ------------------------------------------------------------------------
core::stringw getName() const OVERRIDE { return m_display_name; }
}; // GhostController
#endif

View File

@@ -81,7 +81,7 @@ protected:
/** 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 3; }
unsigned int getReplayVersion() const { return 4; }
public:
ReplayBase();

View File

@@ -135,13 +135,20 @@ bool ReplayPlay::addReplayFile(const std::string& fn, bool custom_replay)
is_end.trim();
if (is_end == "kart_list_end") break;
char s1[1024];
char display_name_encoded[1024];
if (sscanf(s,"kart: %s", s1) != 1)
if (sscanf(s,"kart: %s %[^\n]", s1, display_name_encoded) != 2)
{
Log::warn("Replay", "Could not read ghost karts info!");
break;
}
rd.m_kart_list.push_back(std::string(s1));
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];
}
}
int reverse = 0;
@@ -249,11 +256,12 @@ void ReplayPlay::readKartData(FILE *fd, char *next_line)
{
char s[1024];
const unsigned int kart_num = m_ghost_karts.size();
m_ghost_karts.push_back(new GhostKart(m_replay_file_list
[m_current_replay_file].m_kart_list.at(kart_num),
kart_num, kart_num + 1));
ReplayData &rd = m_replay_file_list[m_current_replay_file];
m_ghost_karts.push_back(new GhostKart(rd.m_kart_list.at(kart_num),
kart_num, kart_num + 1));
m_ghost_karts[kart_num].init(RaceManager::KT_GHOST);
Controller* controller = new GhostController(getGhostKart(kart_num));
Controller* controller = new GhostController(getGhostKart(kart_num),
rd.m_name_list[kart_num]);
getGhostKart(kart_num)->setController(controller);
unsigned int size;

View File

@@ -45,20 +45,23 @@ public:
SO_KART_NUM,
SO_DIFF,
SO_LAPS,
SO_TIME
SO_TIME,
SO_USER
};
class ReplayData
{
public:
std::string m_filename;
std::string m_track_name;
std::vector<std::string> m_kart_list;
bool m_reverse;
bool m_custom_replay_file;
unsigned int m_difficulty;
unsigned int m_laps;
float m_min_time;
std::string m_filename;
std::string m_track_name;
core::stringw m_user_name;
std::vector<std::string> m_kart_list;
std::vector<core::stringw> m_name_list;
bool m_reverse;
bool m_custom_replay_file;
unsigned int m_difficulty;
unsigned int m_laps;
float m_min_time;
bool operator < (const ReplayData& r) const
{
@@ -82,6 +85,9 @@ public:
case SO_TIME:
return m_min_time < r.m_min_time;
break;
case SO_USER:
return m_user_name < r.m_user_name;
break;
} // switch
return true;
} // operator <

View File

@@ -31,6 +31,7 @@
#include <algorithm>
#include <stdio.h>
#include <string>
#include <karts/controller/player_controller.hpp>
ReplayRecorder *ReplayRecorder::m_replay_recorder = NULL;
@@ -222,9 +223,12 @@ void ReplayRecorder::save()
fprintf(fd, "version: %d\n", getReplayVersion());
for (unsigned int real_karts = 0; real_karts < num_karts; real_karts++)
{
if (world->getKart(real_karts)->isGhostKart()) continue;
fprintf(fd, "kart: %s\n",
world->getKart(real_karts)->getIdent().c_str());
const AbstractKart *kart = world->getKart(real_karts);
if (kart->isGhostKart()) continue;
// XML encode the username to handle Unicode
fprintf(fd, "kart: %s %s\n", kart->getIdent().c_str(),
StringUtils::xmlEncode(kart->getController()->getName()).c_str());
}
fprintf(fd, "kart_list_end\n");

View File

@@ -80,6 +80,7 @@ void GhostReplaySelection::beforeAddingWidget()
m_replay_list_widget->addColumn( _("Difficulty"), 1);
m_replay_list_widget->addColumn( _("Laps"), 1);
m_replay_list_widget->addColumn( _("Finish Time"), 1);
m_replay_list_widget->addColumn( _("User"), 1);
} // beforeAddingWidget
// ----------------------------------------------------------------------------
@@ -122,6 +123,8 @@ void GhostReplaySelection::loadList()
(StringUtils::toWString(rd.m_laps), -1, 1, true));
row.push_back(GUIEngine::ListWidget::ListCell
(StringUtils::toWString(rd.m_min_time) + L"s", -1, 1, true));
row.push_back(GUIEngine::ListWidget::ListCell
(rd.m_user_name, -1, 1, true));
m_replay_list_widget->addItem(StringUtils::toString(i), row);
}
} // loadList
@@ -213,6 +216,9 @@ void GhostReplaySelection::onColumnClicked(int column_id)
case 5:
ReplayPlay::setSortOrder(ReplayPlay::SO_TIME);
break;
case 6:
ReplayPlay::setSortOrder(ReplayPlay::SO_USER);
break;
default:
assert(0);
break;

View File

@@ -486,7 +486,7 @@ void RaceResultGUI::backToLobby()
// Save a pointer to the current row_info entry
RowInfo *ri = &(m_all_row_infos[position - first_position]);
ri->m_is_player_kart = kart->getController()->isLocalPlayerController();
ri->m_kart_name = getKartDisplayName(kart);
ri->m_kart_name = kart->getController()->getName();
video::ITexture *icon =
kart->getKartProperties()->getIconMaterial()->getTexture();
@@ -855,7 +855,7 @@ void RaceResultGUI::backToLobby()
ri->m_kart_icon =
kart->getKartProperties()->getIconMaterial()->getTexture();
ri->m_is_player_kart = kart->getController()->isLocalPlayerController();
ri->m_kart_name = getKartDisplayName(kart);
ri->m_kart_name = kart->getController()->getName();
// In FTL karts do have a time, which is shown even when the kart
// is eliminated
@@ -907,29 +907,6 @@ void RaceResultGUI::backToLobby()
#endif
} // determineGPLayout
//-----------------------------------------------------------------------------
/** Returns a string to display next to a kart. For a player that's the name
* of the player, for an AI kart it's the name of the driver.
*/
core::stringw RaceResultGUI::getKartDisplayName(const AbstractKart *kart) const
{
const EndController *ec =
dynamic_cast<const EndController*>(kart->getController());
// If the race was given up, there is no end controller for the
// players, so this case needs to be handled separately
if(ec && ec->isLocalPlayerController())
return ec->getName();
else
{
// No end controller, check explicitely for a player controller
const PlayerController *pc =
dynamic_cast<const PlayerController*>(kart->getController());
// Check if the kart is a player controller to get the real name
if(pc) return pc->getName();
}
return translations->fribidize(kart->getName());
} // getKartDisplayName
//-----------------------------------------------------------------------------
/** Displays the race results for a single kart.
* \param n Index of the kart to be displayed.

View File

@@ -194,7 +194,6 @@ private:
void displayPostRaceInfo();
void displaySoccerResults();
void displayScreenShots();
irr::core::stringw getKartDisplayName(const AbstractKart *kart) const;
int getFontHeight () const;