Save and use usernames in replay files (#2754) (#2893)

* 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

* Move Controller::getName definition to avoid unnecessary #include

* Backwards compatibility: use kart name if username is not in replay

* Fix code style issues
This commit is contained in:
Geoffrey Mon 2017-08-03 19:51:42 -04:00 committed by auriamg
parent 623bb460c6
commit a73af6eb0d
10 changed files with 75 additions and 51 deletions

View File

@ -34,3 +34,8 @@ Controller::Controller(AbstractKart *kart)
m_kart = kart;
setControllerName("Controller");
} // Controller
core::stringw Controller::getName() const
{
return translations->fribidize(m_kart->getName());
}

View File

@ -100,17 +100,16 @@ public:
/** Only local players can get achievements. */
virtual bool canGetAchievements () const { return false; }
// ------------------------------------------------------------------------
/** This should only be called for End- and LocalPlayer-Controller. */
virtual core::stringw getName() const
{
assert(false);
return core::stringw("");
} // getName
/** 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;
// ------------------------------------------------------------------------
/** 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,11 @@ public:
unsigned int getCurrentReplayIndex() const
{ return m_current_index; }
// ------------------------------------------------------------------------
/** Return the display name; if not set, use default display name (kart name) */
core::stringw getName() const OVERRIDE
{
return m_display_name.empty() ? Controller::getName() : m_display_name;
}
}; // GhostController
#endif

View File

@ -135,13 +135,31 @@ 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)
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("");
}
}
int reverse = 0;
@ -249,11 +267,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;