Numerous improvements related to ghost replays (#3244)
* Update GUI files for replay improvements * Updated replay capabilities * Improve timer formatting possibilities Mainly, the ability to choose to display or not minutes, and to choose how many digits are shown after seconds (from 0 : second as smallest timestep - to 3 : ms as smallest timestep) Also displays "mm:ss.ms" rather than "mm:ss:ms". * Some new list widget possibilities Like the ability to update the header while the list is displayed (useful to add or remove columns) * Update ghost kart for the new replay data * Also update the ghost controller * Live differences with ghost replays in linear worlds * Replay-related UI changes Big changes to the replay selection screen, and small change to the race UI (add the live timer in ghost races) and the race result UI (add the option to directly race against a newly saved ghost). * Improves the replay action modal dialog * Fix time-to-ticks regression * Several requested improvements * Improved ghost icon Also updates the license * GUI changes and improvements to accomodate multi-mode support * Additional recorded data * More functions to get the current race state * Update replay variables to match what is used * Updated replay-related config values * Add ghost kart support to easter egg hunt mode * Transparent attachments for ghost karts * Use new stored data (color, item type) Also : - Interpolate speed for smoother display in watch-only mode - Coding style improvements * Fix coding style issues and add UI support for modes * Fix coding style issues & support for easter egg mode * Remove leftover prints * Use getDifficultyName to remove hardcoded values * Fix attach_ticks and coding style fixes * Make the position of the timers fully relative Otherwise, they would tend to touch each other in some resolutions Also add a comment about the 59.9995f * Make the list filling code clearer Also use getDifficultyName Most of the line changes shown by git correspond to moving around some bits or adjusting indentation. * Remove a TODO as requested * Fix dialog being dismissed too soon * Remove a fixme * Small clean up * Fix logging * Partial #3249 fix
This commit is contained in:
parent
0b79d9c1d1
commit
7f84dd39a6
@ -38,6 +38,8 @@ power.png by Auria, based on https://openclipart.org/detail/193925/check-engine
|
|||||||
|
|
||||||
crown.png by glitch, from https://openclipart.org/detail/210257/misc-game-crown, released under public domain
|
crown.png by glitch, from https://openclipart.org/detail/210257/misc-game-crown, released under public domain
|
||||||
|
|
||||||
|
ghost_plus.png by Alayan, based on https://openclipart.org/detail/17847/cartoon-ghost by lemmling, released under CC-O
|
||||||
|
|
||||||
====
|
====
|
||||||
|
|
||||||
Glass Skin by Auria, under CC-BY-SA 3+
|
Glass Skin by Auria, under CC-BY-SA 3+
|
||||||
|
BIN
data/gui/ghost_plus.png
Normal file
BIN
data/gui/ghost_plus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -1,15 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<stkgui>
|
<stkgui>
|
||||||
<div x="5%" y="5%" width="90%" height="90%" layout="vertical-row">
|
<div y="2%" width="100%" height="96%" layout="vertical-row">
|
||||||
<div x="5%" y="0%" width="90%" proportion="6" layout="horizontal-row">
|
<div width="100%" height="50%" proportion="6" layout="horizontal-row">
|
||||||
<div width="40%" height="100%" layout="vertical-row">
|
<div width="25%" height="100%" layout="vertical-row">
|
||||||
<icon id="icon" align="center" width="100%" icon="gui/loading.png" />
|
<icon-button proportion="1" width="100%" height="100%" id="track_screenshot" custom_ratio="1.33333"/>
|
||||||
</div>
|
</div>
|
||||||
<div width="60%" height="50%" layout="vertical-row">
|
<div width="75%" height="100%" layout="vertical-row">
|
||||||
<label id="name" width="100%" text_align="left"/>
|
<div width="100%" height="25%" layout="vertical-row" >
|
||||||
|
<label id="name" width="100%" text_align="center"/>
|
||||||
|
</div>
|
||||||
|
<!-- This is filled in programmatically -->
|
||||||
|
<box width="98%" height="75%" align="center" layout="vertical-row" padding="1">
|
||||||
|
<list id="current_replay_info" x="0" y="0" width="100%" height="100%"/>
|
||||||
|
</box>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div width="90%" align="center" layout="vertical-row" height="fit">
|
<div width="90%" align="center" layout="vertical-row" height="fit">
|
||||||
<div width="100%" height="fit" layout="horizontal-row" >
|
<div width="100%" height="fit" layout="horizontal-row" >
|
||||||
<checkbox width="fit" id="record-race" I18N="Ghost replay info action" text_align="left"/>
|
<checkbox width="fit" id="record-race" I18N="Ghost replay info action" text_align="left"/>
|
||||||
@ -21,13 +29,21 @@
|
|||||||
<spacer width="10"/>
|
<spacer width="10"/>
|
||||||
<label proportion="1" id="watch-only-text" height="100%" text_align="left" I18N="Ghost replay info action" text="Watch replay only"/>
|
<label proportion="1" id="watch-only-text" height="100%" text_align="left" I18N="Ghost replay info action" text="Watch replay only"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div width="100%" height="fit" layout="horizontal-row" >
|
||||||
|
<checkbox width="fit" id="compare-ghost" I18N="Ghost replay info action" text_align="left"/>
|
||||||
|
<spacer width="10"/>
|
||||||
|
<label proportion="1" id="compare-ghost-text" height="100%" text_align="left" I18N="Ghost replay info action" text="Compare to another ghost"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div width="80%" proportion="5" align="center">
|
<div width="90%" proportion="5" align="center">
|
||||||
<buttonbar id="actions" x="0" y="0" height="100%" width="100%" align="center">
|
<buttonbar id="actions" x="0" y="0" height="100%" width="100%" align="center">
|
||||||
<icon-button id="start" width="128" height="128"
|
<icon-button id="start" width="128" height="128"
|
||||||
icon="gui/green_check.png"
|
icon="gui/green_check.png"
|
||||||
I18N="Ghost replay info screen action" text="Start Race" />
|
I18N="Ghost replay info screen action" text="Start Race" />
|
||||||
|
<icon-button id="add-ghost-to-compare" width="128" height="128"
|
||||||
|
icon="gui/ghost_plus.png"
|
||||||
|
I18N="Ghost replay info screen action" text="Compare ghost" />
|
||||||
<icon-button id="remove" width="128" height="128"
|
<icon-button id="remove" width="128" height="128"
|
||||||
icon="gui/remove.png"
|
icon="gui/remove.png"
|
||||||
I18N="Ghost replay info action" text="Remove" />
|
I18N="Ghost replay info action" text="Remove" />
|
||||||
|
@ -8,19 +8,50 @@
|
|||||||
<icon-button id="reload" height="90%" icon="gui/restart.png"/>
|
<icon-button id="reload" height="90%" icon="gui/restart.png"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- This is filled in programmatically -->
|
||||||
<box proportion="1" width="98%" align="center" layout="vertical-row" padding="6">
|
<box proportion="1" width="98%" align="center" layout="vertical-row" padding="6">
|
||||||
<list id="replay_list" x="0" y="0" width="100%" height="100%"/>
|
<list id="replay_list" x="0" y="0" width="100%" height="100%"/>
|
||||||
</box>
|
</box>
|
||||||
|
|
||||||
<div width="99%" align="center" layout="vertical-row" height="fit">
|
<tabs id="race_mode" height="6%" max_height="110" x="2%" width="98%" align="center">
|
||||||
<div width="100%" height="fit" layout="horizontal-row" >
|
<icon-button id="tab_time_trial" width="128" height="128" icon="gui/mode_tt.png"
|
||||||
<checkbox width="fit" id="replay_difficulty_toggle" text_align="left"/>
|
I18N="In the ghost replay selection screen" text="Time trial"/>
|
||||||
<spacer width="10"/>
|
<icon-button id="tab_egg_hunt" width="128" height="128" icon="gui/mode_easter.png"
|
||||||
<label proportion="1" height="100%" text_align="left" I18N="In the ghost replay selection screen" text="Only show replays matching the current difficulty"/>
|
I18N="In the ghost replay selection screen" text="Egg hunt"/>
|
||||||
|
</tabs>
|
||||||
|
|
||||||
|
<spacer width="100%" height="1.5%" />
|
||||||
|
|
||||||
|
<div width="99%" align="center" layout="horizontal-row" height="fit">
|
||||||
|
<div proportion="1" height="fit" layout="horizontal-row" >
|
||||||
|
<checkbox width="fit" id="best_times_toggle" text_align="left"/>
|
||||||
|
<spacer width="2%" height="fit"/>
|
||||||
|
<label height="100%" text_align="left" I18N="In the ghost replay selection screen" text="Only show the best times"/>
|
||||||
|
</div>
|
||||||
|
<div proportion="1" height="fit" layout="horizontal-row" >
|
||||||
|
<checkbox width="fit" id="compare_toggle" text_align="left"/>
|
||||||
|
<spacer width="2%" height="fit"/>
|
||||||
|
<label height="100%" id="compare-toggle-text" text_align="left" I18N="In the ghost replay selection screen" text="Compare replay"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<spacer width="100%" height="1%" />
|
|
||||||
<button x="1%" id="record-ghost" I18N="In the ghost replay selection screen" text="Record ghost replay"/>
|
<div width="99%" align="center" layout="horizontal-row" height="fit">
|
||||||
|
<div proportion="2" height="fit" layout="horizontal-row" >
|
||||||
|
<checkbox width="fit" id="replay_difficulty_toggle" text_align="left"/>
|
||||||
|
<spacer width="1%" height="fit"/>
|
||||||
|
<label height="100%" text_align="left" I18N="In the ghost replay selection screen" text="Only show replays matching the current difficulty"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div width="99%" align="center" layout="horizontal-row" height="fit">
|
||||||
|
<div proportion="1" height="fit" layout="horizontal-row" >
|
||||||
|
<checkbox width="fit" id="replay_version_toggle" text_align="left"/>
|
||||||
|
<spacer width="1%" height="fit" />
|
||||||
|
<label height="100%" text_align="left" I18N="In the ghost replay selection screen" text="Only show replays matching the current version"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button x="1%" id="record-ghost" I18N="In the ghost replay selection screen" text="Record a ghost replay"/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</stkgui>
|
</stkgui>
|
||||||
|
@ -113,14 +113,18 @@
|
|||||||
|
|
||||||
<!-- Replay related values, mostly concerned with saving less data
|
<!-- Replay related values, mostly concerned with saving less data
|
||||||
and using interpolation instead.
|
and using interpolation instead.
|
||||||
max-time: Maximum race time that can be saved in a replay/history file.
|
max-frames: Maximum number of transform events that can be saved
|
||||||
delta-t Minumum time between saving consecutive transform events.
|
in a replay/history file. With normal play, 900 are
|
||||||
delta-pos If the interpolated position is within this delta, a
|
enough to store at least one minute, usually more.
|
||||||
transform event is not generated.
|
delta-t Maximum time between saving consecutive transform events.
|
||||||
delta-angle If the interpolated angle is within this delta,
|
The recording will do more transform events when some kart data
|
||||||
a transform event is not generated. -->
|
changes significantly.
|
||||||
<replay max-time="600" delta-t="0.05" delta-pos="0.1"
|
delta-speed If the speed difference exceeds this delta, a
|
||||||
delta-angle="0.5" />
|
new transform event is generated before maximum time.
|
||||||
|
delta-steering If the steering angle difference exceeds this delta,
|
||||||
|
new transform event is generated before maximum time. -->
|
||||||
|
<replay max-frames="12000" delta-t="0.200" delta-speed="0.6"
|
||||||
|
delta-steering="0.35" />
|
||||||
|
|
||||||
<!-- Determines the minimap related values.
|
<!-- Determines the minimap related values.
|
||||||
size: The size of the minimap (scaled afterwards) 480 = full screen height)
|
size: The size of the minimap (scaled afterwards) 480 = full screen height)
|
||||||
|
@ -150,9 +150,9 @@ void STKConfig::load(const std::string &filename)
|
|||||||
CHECK_NEG(m_leader_time_per_kart, "leader time-per-kart" );
|
CHECK_NEG(m_leader_time_per_kart, "leader time-per-kart" );
|
||||||
CHECK_NEG(m_penalty_ticks, "penalty-time" );
|
CHECK_NEG(m_penalty_ticks, "penalty-time" );
|
||||||
CHECK_NEG(m_max_display_news, "max-display-news" );
|
CHECK_NEG(m_max_display_news, "max-display-news" );
|
||||||
CHECK_NEG(m_replay_max_time, "replay max-time" );
|
CHECK_NEG(m_replay_max_frames, "replay max-frames" );
|
||||||
CHECK_NEG(m_replay_delta_angle, "replay delta-angle" );
|
CHECK_NEG(m_replay_delta_steering, "replay delta-steering" );
|
||||||
CHECK_NEG(m_replay_delta_pos2, "replay delta-position" );
|
CHECK_NEG(m_replay_delta_speed, "replay delta-speed " );
|
||||||
CHECK_NEG(m_replay_dt, "replay delta-t" );
|
CHECK_NEG(m_replay_dt, "replay delta-t" );
|
||||||
CHECK_NEG(m_minimap_size, "minimap size" );
|
CHECK_NEG(m_minimap_size, "minimap size" );
|
||||||
CHECK_NEG(m_minimap_ai_icon, "minimap ai_icon" );
|
CHECK_NEG(m_minimap_ai_icon, "minimap ai_icon" );
|
||||||
@ -164,7 +164,6 @@ void STKConfig::load(const std::string &filename)
|
|||||||
CHECK_NEG(m_default_moveable_friction, "physics default-moveable-friction");
|
CHECK_NEG(m_default_moveable_friction, "physics default-moveable-friction");
|
||||||
|
|
||||||
// Square distance to make distance checks cheaper (no sqrt)
|
// Square distance to make distance checks cheaper (no sqrt)
|
||||||
m_replay_delta_pos2 *= m_replay_delta_pos2;
|
|
||||||
m_default_kart_properties->checkAllSet(filename);
|
m_default_kart_properties->checkAllSet(filename);
|
||||||
} // load
|
} // load
|
||||||
|
|
||||||
@ -195,9 +194,9 @@ void STKConfig::init_defaults()
|
|||||||
m_min_server_version = -100;
|
m_min_server_version = -100;
|
||||||
m_max_server_version = -100;
|
m_max_server_version = -100;
|
||||||
m_max_display_news = -100;
|
m_max_display_news = -100;
|
||||||
m_replay_max_time = -100;
|
m_replay_max_frames = -100;
|
||||||
m_replay_delta_angle = -100;
|
m_replay_delta_steering = -100;
|
||||||
m_replay_delta_pos2 = -100;
|
m_replay_delta_speed = -100;
|
||||||
m_replay_dt = -100;
|
m_replay_dt = -100;
|
||||||
m_minimap_size = -100;
|
m_minimap_size = -100;
|
||||||
m_minimap_ai_icon = -100;
|
m_minimap_ai_icon = -100;
|
||||||
@ -405,10 +404,10 @@ void STKConfig::getAllData(const XMLNode * root)
|
|||||||
|
|
||||||
if(const XMLNode *replay_node = root->getNode("replay"))
|
if(const XMLNode *replay_node = root->getNode("replay"))
|
||||||
{
|
{
|
||||||
replay_node->get("delta-angle", &m_replay_delta_angle);
|
replay_node->get("delta-steering", &m_replay_delta_steering);
|
||||||
replay_node->get("delta-pos", &m_replay_delta_pos2 );
|
replay_node->get("delta-speed", &m_replay_delta_speed );
|
||||||
replay_node->get("delta-t", &m_replay_dt );
|
replay_node->get("delta-t", &m_replay_dt );
|
||||||
replay_node->get("max-time", &m_replay_max_time );
|
replay_node->get("max-frames", &m_replay_max_frames );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,19 +149,19 @@ public:
|
|||||||
/** Filename of the title music to play.*/
|
/** Filename of the title music to play.*/
|
||||||
MusicInformation *m_title_music;
|
MusicInformation *m_title_music;
|
||||||
|
|
||||||
/** Maximum time of a replay. */
|
/** Maximum number of transform events of a replay. */
|
||||||
int m_replay_max_time;
|
int m_replay_max_frames;
|
||||||
|
|
||||||
/** Minimum time between consecutive saved tranform events. */
|
/** Maximum time between consecutive saved tranform events. */
|
||||||
float m_replay_dt;
|
float m_replay_dt;
|
||||||
|
|
||||||
/** Maximum difference between interpolated and actual position. If the
|
/** If the speed difference with the last transform event
|
||||||
* difference is larger than this, a new event is generated. */
|
* is larger than this, a new event is generated. */
|
||||||
float m_replay_delta_pos2;
|
float m_replay_delta_speed;
|
||||||
|
|
||||||
/** A heading difference of more than that will trigger a new event to
|
/** A steering difference of more than that will trigger a new event to
|
||||||
* be generated. */
|
* be generated. */
|
||||||
float m_replay_delta_angle;
|
float m_replay_delta_steering;
|
||||||
|
|
||||||
/** The minimap size */
|
/** The minimap size */
|
||||||
float m_minimap_size;
|
float m_minimap_size;
|
||||||
|
@ -44,6 +44,7 @@ ListWidget::ListWidget() : Widget(WTYPE_LIST)
|
|||||||
m_sort_default = true;
|
m_sort_default = true;
|
||||||
m_sort_col = 0;
|
m_sort_col = 0;
|
||||||
m_sortable = true;
|
m_sortable = true;
|
||||||
|
m_header_created = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@ -121,6 +122,18 @@ void ListWidget::add()
|
|||||||
m_element = list_box;
|
m_element = list_box;
|
||||||
m_element->setTabOrder( list_box->getID() );
|
m_element->setTabOrder( list_box->getID() );
|
||||||
|
|
||||||
|
createHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void ListWidget::createHeader()
|
||||||
|
{
|
||||||
|
if (m_header_created)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int header_height = GUIEngine::getFontHeight() + 15;
|
||||||
|
|
||||||
if (m_header.size() > 0)
|
if (m_header.size() > 0)
|
||||||
{
|
{
|
||||||
//const int col_size = m_w / m_header.size();
|
//const int col_size = m_w / m_header.size();
|
||||||
@ -166,7 +179,8 @@ void ListWidget::add()
|
|||||||
|
|
||||||
m_check_inside_me = true;
|
m_check_inside_me = true;
|
||||||
}
|
}
|
||||||
}
|
m_header_created = true;
|
||||||
|
} // createHeader
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -179,7 +193,22 @@ void ListWidget::clear()
|
|||||||
assert(list != NULL);
|
assert(list != NULL);
|
||||||
|
|
||||||
list->clear();
|
list->clear();
|
||||||
}
|
} //clear
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void ListWidget::clearColumns()
|
||||||
|
{
|
||||||
|
m_header.clear();
|
||||||
|
for (unsigned int n=0; n<m_header_elements.size(); n++)
|
||||||
|
{
|
||||||
|
m_header_elements[n].elementRemoved();
|
||||||
|
m_children.remove( m_header_elements.get(n) );
|
||||||
|
}
|
||||||
|
|
||||||
|
m_header_elements.clearAndDeleteAll();
|
||||||
|
m_header_created = false;
|
||||||
|
} //clearColumns
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -89,6 +89,8 @@ namespace GUIEngine
|
|||||||
|
|
||||||
bool m_sortable;
|
bool m_sortable;
|
||||||
|
|
||||||
|
bool m_header_created;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
typedef irr::gui::CGUISTKListBox::ListItem ListItem;
|
typedef irr::gui::CGUISTKListBox::ListItem ListItem;
|
||||||
typedef ListItem::ListCell ListCell;
|
typedef ListItem::ListCell ListCell;
|
||||||
@ -135,12 +137,24 @@ namespace GUIEngine
|
|||||||
|
|
||||||
void addItem( const std::string& internal_name,
|
void addItem( const std::string& internal_name,
|
||||||
const std::vector<ListCell>& contents);
|
const std::vector<ListCell>& contents);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief create a header based on m_header
|
||||||
|
* \pre may only be called after the widget has been added to the screen with add()
|
||||||
|
*/
|
||||||
|
void createHeader();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief erases all items in the list
|
* \brief erases all items in the list, don't clear header
|
||||||
* \pre may only be called after the widget has been added to the screen with add()
|
* \pre may only be called after the widget has been added to the screen with add()
|
||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief clear the header
|
||||||
|
* \pre may only be called after the widget has been added to the screen with add()
|
||||||
|
*/
|
||||||
|
void clearColumns();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \return the number of items in the list
|
* \return the number of items in the list
|
||||||
@ -239,12 +253,10 @@ namespace GUIEngine
|
|||||||
m_listener = listener;
|
m_listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** To be called before Widget::add(); columns are persistent across multiple add/remove cycles
|
/** Columns are persistent across multiple "clear" add/remove cycles. clearColumns clear them immediately.
|
||||||
* \param proportion A column with proportion 2 will be twice as large as a column with proportion 1
|
* \param proportion A column with proportion 2 will be twice as large as a column with proportion 1
|
||||||
*/
|
*/
|
||||||
void addColumn(irr::core::stringw col, int proportion=1) { m_header.push_back( Column(col, proportion) ); }
|
void addColumn(irr::core::stringw col, int proportion=1) { m_header.push_back( Column(col, proportion) ); }
|
||||||
|
|
||||||
void clearColumns() { m_header.clear(); }
|
|
||||||
|
|
||||||
void setSortable(bool sortable) { m_sortable = sortable; }
|
void setSortable(bool sortable) { m_sortable = sortable; }
|
||||||
};
|
};
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include "config/user_config.hpp"
|
#include "config/user_config.hpp"
|
||||||
#include "graphics/explosion.hpp"
|
#include "graphics/explosion.hpp"
|
||||||
#include "graphics/irr_driver.hpp"
|
#include "graphics/irr_driver.hpp"
|
||||||
|
#include "graphics/render_info.hpp"
|
||||||
#include "items/attachment_manager.hpp"
|
#include "items/attachment_manager.hpp"
|
||||||
#include "items/item_manager.hpp"
|
#include "items/item_manager.hpp"
|
||||||
#include "items/projectile_manager.hpp"
|
#include "items/projectile_manager.hpp"
|
||||||
@ -60,8 +61,13 @@ Attachment::Attachment(AbstractKart* kart)
|
|||||||
|
|
||||||
// If we attach a NULL mesh, we get a NULL scene node back. So we
|
// If we attach a NULL mesh, we get a NULL scene node back. So we
|
||||||
// have to attach some kind of mesh, but make it invisible.
|
// have to attach some kind of mesh, but make it invisible.
|
||||||
m_node = irr_driver->addAnimatedMesh(
|
if (kart->isGhostKart())
|
||||||
attachment_manager->getMesh(Attachment::ATTACH_BOMB), "bomb");
|
m_node = irr_driver->addAnimatedMesh(
|
||||||
|
attachment_manager->getMesh(Attachment::ATTACH_BOMB), "bomb",
|
||||||
|
NULL, std::make_shared<RenderInfo>(0.0f, true));
|
||||||
|
else
|
||||||
|
m_node = irr_driver->addAnimatedMesh(
|
||||||
|
attachment_manager->getMesh(Attachment::ATTACH_BOMB), "bomb");
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
std::string debug_name = kart->getIdent()+" (attachment)";
|
std::string debug_name = kart->getIdent()+" (attachment)";
|
||||||
m_node->setName(debug_name.c_str());
|
m_node->setName(debug_name.c_str());
|
||||||
|
@ -152,3 +152,11 @@ void AbstractKart::kartIsInRestNow()
|
|||||||
// after all karts are reset
|
// after all karts are reset
|
||||||
setTrans(getBody()->getWorldTransform());
|
setTrans(getBody()->getWorldTransform());
|
||||||
} // kartIsInRest
|
} // kartIsInRest
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
/** Returns the time at which the kart was at a given distance.
|
||||||
|
* Returns -1.0f if none */
|
||||||
|
float AbstractKart::getTimeForDistance(float distance)
|
||||||
|
{
|
||||||
|
return -1.0f;
|
||||||
|
} // getTimeForDistance
|
||||||
|
@ -186,6 +186,11 @@ public:
|
|||||||
* overwriting this function is possible, but this implementation must
|
* overwriting this function is possible, but this implementation must
|
||||||
* be called. */
|
* be called. */
|
||||||
virtual void kartIsInRestNow();
|
virtual void kartIsInRestNow();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
/** Returns the time at which the kart was at a given distance.
|
||||||
|
* Returns -1.0f if none */
|
||||||
|
virtual float getTimeForDistance(float distance);
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Returns true if this kart has no wheels. */
|
/** Returns true if this kart has no wheels. */
|
||||||
bool isWheeless() const;
|
bool isWheeless() const;
|
||||||
@ -323,8 +328,8 @@ public:
|
|||||||
* \param fade_out_time How long the maximum speed will fade out linearly.
|
* \param fade_out_time How long the maximum speed will fade out linearly.
|
||||||
*/
|
*/
|
||||||
virtual void instantSpeedIncrease(unsigned int category, float add_max_speed,
|
virtual void instantSpeedIncrease(unsigned int category, float add_max_speed,
|
||||||
float speed_boost, float engine_force,
|
float speed_boost, float engine_force,
|
||||||
int duration, int fade_out_time) = 0;
|
int duration, int fade_out_time) = 0;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Defines a slowdown, which is in fraction of top speed.
|
/** Defines a slowdown, which is in fraction of top speed.
|
||||||
@ -411,6 +416,9 @@ public:
|
|||||||
/** Returns the last used powerup type. */
|
/** Returns the last used powerup type. */
|
||||||
virtual PowerupManager::PowerupType getLastUsedPowerup() = 0;
|
virtual PowerupManager::PowerupType getLastUsedPowerup() = 0;
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
/** Returns the number of powerups. */
|
||||||
|
virtual int getNumPowerup() const = 0;
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
/** Returns a points to this kart's graphical effects. */
|
/** Returns a points to this kart's graphical effects. */
|
||||||
virtual KartGFX* getKartGFX() = 0;
|
virtual KartGFX* getKartGFX() = 0;
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -87,5 +87,7 @@ bool GhostController::action(PlayerAction action, int value, bool dry_run)
|
|||||||
// Watching replay use only
|
// Watching replay use only
|
||||||
if (action == PA_LOOK_BACK)
|
if (action == PA_LOOK_BACK)
|
||||||
m_controls->setLookBack(value!=0);
|
m_controls->setLookBack(value!=0);
|
||||||
|
else if (action == PA_PAUSE_RACE)
|
||||||
|
if (value != 0) StateManager::get()->escapePressed();
|
||||||
return true;
|
return true;
|
||||||
} // action
|
} // action
|
||||||
|
@ -79,6 +79,14 @@ public:
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
unsigned int getCurrentReplayIndex() const
|
unsigned int getCurrentReplayIndex() const
|
||||||
{ return m_current_index; }
|
{ return m_current_index; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
float getTimeAtIndex(unsigned int index) const
|
||||||
|
{
|
||||||
|
assert(index < m_all_times.size());
|
||||||
|
return m_all_times[index];
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Return the display name; if not set, use default display name (kart name) */
|
/** Return the display name; if not set, use default display name (kart name) */
|
||||||
core::stringw getName() const OVERRIDE
|
core::stringw getName() const OVERRIDE
|
||||||
|
@ -16,21 +16,26 @@
|
|||||||
// along with this program; if not, write to the Free Software
|
// along with this program; if not, write to the Free Software
|
||||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
|
||||||
|
#include "items/attachment.hpp"
|
||||||
|
#include "items/powerup.hpp"
|
||||||
#include "karts/ghost_kart.hpp"
|
#include "karts/ghost_kart.hpp"
|
||||||
#include "karts/controller/ghost_controller.hpp"
|
#include "karts/controller/ghost_controller.hpp"
|
||||||
#include "karts/kart_gfx.hpp"
|
#include "karts/kart_gfx.hpp"
|
||||||
#include "karts/kart_model.hpp"
|
#include "karts/kart_model.hpp"
|
||||||
#include "graphics/render_info.hpp"
|
#include "graphics/render_info.hpp"
|
||||||
|
#include "modes/easter_egg_hunt.hpp"
|
||||||
|
#include "modes/linear_world.hpp"
|
||||||
#include "modes/world.hpp"
|
#include "modes/world.hpp"
|
||||||
|
#include "replay/replay_recorder.hpp"
|
||||||
|
|
||||||
#include "LinearMath/btQuaternion.h"
|
#include "LinearMath/btQuaternion.h"
|
||||||
|
|
||||||
GhostKart::GhostKart(const std::string& ident, unsigned int world_kart_id,
|
GhostKart::GhostKart(const std::string& ident, unsigned int world_kart_id,
|
||||||
int position)
|
int position, float color_hue)
|
||||||
: Kart(ident, world_kart_id,
|
: Kart(ident, world_kart_id,
|
||||||
position, btTransform(btQuaternion(0, 0, 0, 1)),
|
position, btTransform(btQuaternion(0, 0, 0, 1)),
|
||||||
PLAYER_DIFFICULTY_NORMAL,
|
PLAYER_DIFFICULTY_NORMAL,
|
||||||
std::make_shared<RenderInfo>(0.0f, true/*transparent*/))
|
std::make_shared<RenderInfo>(color_hue, true/*transparent*/))
|
||||||
{
|
{
|
||||||
} // GhostKart
|
} // GhostKart
|
||||||
|
|
||||||
@ -41,12 +46,14 @@ void GhostKart::reset()
|
|||||||
Kart::reset();
|
Kart::reset();
|
||||||
// This will set the correct start position
|
// This will set the correct start position
|
||||||
update(0);
|
update(0);
|
||||||
|
m_last_egg_idx = 0;
|
||||||
} // reset
|
} // reset
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
void GhostKart::addReplayEvent(float time,
|
void GhostKart::addReplayEvent(float time,
|
||||||
const btTransform &trans,
|
const btTransform &trans,
|
||||||
const ReplayBase::PhysicInfo &pi,
|
const ReplayBase::PhysicInfo &pi,
|
||||||
|
const ReplayBase::BonusInfo &bi,
|
||||||
const ReplayBase::KartReplayEvent &kre)
|
const ReplayBase::KartReplayEvent &kre)
|
||||||
{
|
{
|
||||||
GhostController* gc = dynamic_cast<GhostController*>(getController());
|
GhostController* gc = dynamic_cast<GhostController*>(getController());
|
||||||
@ -54,6 +61,7 @@ void GhostKart::addReplayEvent(float time,
|
|||||||
|
|
||||||
m_all_transform.push_back(trans);
|
m_all_transform.push_back(trans);
|
||||||
m_all_physic_info.push_back(pi);
|
m_all_physic_info.push_back(pi);
|
||||||
|
m_all_bonus_info.push_back(bi);
|
||||||
m_all_replay_events.push_back(kre);
|
m_all_replay_events.push_back(kre);
|
||||||
|
|
||||||
// Use first frame of replay to calculate default suspension
|
// Use first frame of replay to calculate default suspension
|
||||||
@ -130,10 +138,65 @@ void GhostKart::update(int ticks)
|
|||||||
m_all_physic_info[idx].m_steer, m_all_physic_info[idx].m_speed,
|
m_all_physic_info[idx].m_steer, m_all_physic_info[idx].m_speed,
|
||||||
/*lean*/0.0f, idx);
|
/*lean*/0.0f, idx);
|
||||||
|
|
||||||
|
// Attachment management
|
||||||
|
// Note that this doesn't get ticks value from replay file,
|
||||||
|
// and can't get the exact ticks values when it depends from kart
|
||||||
|
// properties. It also doesn't manage the case where a shield is
|
||||||
|
// renewed. These inaccuracies are minor as it is used for
|
||||||
|
// graphical effect only.
|
||||||
|
|
||||||
|
Attachment::AttachmentType attach_type =
|
||||||
|
ReplayRecorder::codeToEnumAttach(m_all_bonus_info[idx].m_attachment);
|
||||||
|
int attach_ticks = 0;
|
||||||
|
if (attach_type == Attachment::ATTACH_BUBBLEGUM_SHIELD)
|
||||||
|
attach_ticks = stk_config->time2Ticks(10);
|
||||||
|
else if (attach_type == Attachment::ATTACH_BOMB)
|
||||||
|
attach_ticks = stk_config->time2Ticks(30);
|
||||||
|
// The replay history will take care of clearing,
|
||||||
|
// just make sure it won't expire by itself
|
||||||
|
else
|
||||||
|
attach_ticks = stk_config->time2Ticks(300);
|
||||||
|
|
||||||
|
if ( attach_type == Attachment::ATTACH_NOTHING )
|
||||||
|
m_attachment->clear();
|
||||||
|
// Setting again reinitialize the graphical size of the attachment,
|
||||||
|
// so do so only if the type change
|
||||||
|
else if ( attach_type != m_attachment->getType())
|
||||||
|
m_attachment->set(attach_type,attach_ticks);
|
||||||
|
|
||||||
|
// So that the attachment's model size is updated
|
||||||
|
m_attachment->update(ticks);
|
||||||
|
|
||||||
|
// Nitro and zipper amount (shown in the GUI in watch-only mode)
|
||||||
|
m_powerup->reset();
|
||||||
|
|
||||||
|
// Update item amount and type
|
||||||
|
PowerupManager::PowerupType item_type =
|
||||||
|
ReplayRecorder::codeToEnumItem(m_all_bonus_info[idx].m_item_type);
|
||||||
|
m_powerup->set(item_type, m_all_bonus_info[idx].m_item_amount);
|
||||||
|
|
||||||
|
// Update special values in easter egg and battle modes
|
||||||
|
if (race_manager->isEggHuntMode())
|
||||||
|
{
|
||||||
|
if (idx > m_last_egg_idx &&
|
||||||
|
m_all_bonus_info[idx].m_special_value >
|
||||||
|
m_all_bonus_info[m_last_egg_idx].m_special_value)
|
||||||
|
{
|
||||||
|
EasterEggHunt *world = dynamic_cast<EasterEggHunt*>(World::getWorld());
|
||||||
|
assert(world);
|
||||||
|
world->collectedEasterEggGhost(getWorldKartId());
|
||||||
|
m_last_egg_idx = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_collected_energy = (1- rd)*m_all_bonus_info[idx ].m_nitro_amount
|
||||||
|
+ rd *m_all_bonus_info[idx + 1].m_nitro_amount;
|
||||||
|
|
||||||
|
// Graphical effects for nitro, zipper and skidding
|
||||||
getKartGFX()->setGFXFromReplay(m_all_replay_events[idx].m_nitro_usage,
|
getKartGFX()->setGFXFromReplay(m_all_replay_events[idx].m_nitro_usage,
|
||||||
m_all_replay_events[idx].m_zipper_usage,
|
m_all_replay_events[idx].m_zipper_usage,
|
||||||
m_all_replay_events[idx].m_skidding_state,
|
m_all_replay_events[idx].m_skidding_effect,
|
||||||
m_all_replay_events[idx].m_red_skidding);
|
m_all_replay_events[idx].m_red_skidding);
|
||||||
getKartGFX()->update(dt);
|
getKartGFX()->update(dt);
|
||||||
|
|
||||||
Vec3 front(0, 0, getKartLength()*0.5f);
|
Vec3 front(0, 0, getKartLength()*0.5f);
|
||||||
@ -159,6 +222,92 @@ float GhostKart::getSpeed() const
|
|||||||
const GhostController* gc =
|
const GhostController* gc =
|
||||||
dynamic_cast<const GhostController*>(getController());
|
dynamic_cast<const GhostController*>(getController());
|
||||||
|
|
||||||
|
unsigned int current_index = gc->getCurrentReplayIndex();
|
||||||
|
const float rd = gc->getReplayDelta();
|
||||||
|
|
||||||
assert(gc->getCurrentReplayIndex() < m_all_physic_info.size());
|
assert(gc->getCurrentReplayIndex() < m_all_physic_info.size());
|
||||||
return m_all_physic_info[gc->getCurrentReplayIndex()].m_speed;
|
|
||||||
|
if (current_index == m_all_physic_info.size())
|
||||||
|
return m_all_physic_info[current_index].m_speed;
|
||||||
|
|
||||||
|
return (1-rd)*m_all_physic_info[current_index ].m_speed
|
||||||
|
+ rd *m_all_physic_info[current_index + 1].m_speed;
|
||||||
} // getSpeed
|
} // getSpeed
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
/** Returns the time at which the kart was at a given distance.
|
||||||
|
* Returns -1.0f if none */
|
||||||
|
float GhostKart::getTimeForDistance(float distance)
|
||||||
|
{
|
||||||
|
const GhostController* gc =
|
||||||
|
dynamic_cast<const GhostController*>(getController());
|
||||||
|
|
||||||
|
int current_index = gc->getCurrentReplayIndex();
|
||||||
|
|
||||||
|
// Second, get the current distance
|
||||||
|
float current_distance = m_all_replay_events[current_index].m_distance;
|
||||||
|
|
||||||
|
// This determines in which direction we will search a matching frame
|
||||||
|
bool search_forward = (current_distance < distance);
|
||||||
|
|
||||||
|
// These are used to compute the time
|
||||||
|
// upper_frame is set to current index so that the timer still runs
|
||||||
|
// after the ghost has finished the race
|
||||||
|
int lower_frame_index = current_index-1;
|
||||||
|
unsigned int upper_frame_index = current_index;
|
||||||
|
float upper_ratio = 0.0f;
|
||||||
|
|
||||||
|
// Third, search frame by frame in the good direction
|
||||||
|
// Binary search is not used because the kart might go backwards
|
||||||
|
// at some point (accident, rescue, wrong interpolation later corrected)
|
||||||
|
// The distances are stored in the replay file rather than being
|
||||||
|
// dynamically computed. Version 3 replay files thus don't support
|
||||||
|
// the live time difference, but this reduces tremendously the overhead
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// If we have reached the end of the replay file without finding the
|
||||||
|
// searched distance, break
|
||||||
|
if (upper_frame_index >= m_all_replay_events.size() ||
|
||||||
|
lower_frame_index < 0 )
|
||||||
|
break;
|
||||||
|
|
||||||
|
// The target distance was reached between those two frames
|
||||||
|
if (m_all_replay_events[lower_frame_index].m_distance <= distance &&
|
||||||
|
m_all_replay_events[upper_frame_index].m_distance >= distance )
|
||||||
|
{
|
||||||
|
float lower_diff =
|
||||||
|
distance - m_all_replay_events[lower_frame_index].m_distance;
|
||||||
|
float upper_diff =
|
||||||
|
m_all_replay_events[upper_frame_index].m_distance - distance;
|
||||||
|
|
||||||
|
if ((lower_diff + upper_diff) == 0)
|
||||||
|
upper_ratio = 0.0f;
|
||||||
|
else
|
||||||
|
upper_ratio = lower_diff/(lower_diff+upper_diff);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search_forward)
|
||||||
|
{
|
||||||
|
lower_frame_index++;
|
||||||
|
upper_frame_index++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lower_frame_index--;
|
||||||
|
upper_frame_index--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float ghost_time;
|
||||||
|
|
||||||
|
if (upper_frame_index >= m_all_replay_events.size() ||
|
||||||
|
lower_frame_index < 0 )
|
||||||
|
ghost_time = -1.0f;
|
||||||
|
else
|
||||||
|
ghost_time = gc->getTimeAtIndex(lower_frame_index)*(1.0f-upper_ratio)
|
||||||
|
+gc->getTimeAtIndex(upper_frame_index)*(upper_ratio);
|
||||||
|
|
||||||
|
return ghost_time;
|
||||||
|
}
|
||||||
|
@ -42,11 +42,15 @@ private:
|
|||||||
|
|
||||||
std::vector<ReplayBase::PhysicInfo> m_all_physic_info;
|
std::vector<ReplayBase::PhysicInfo> m_all_physic_info;
|
||||||
|
|
||||||
|
std::vector<ReplayBase::BonusInfo> m_all_bonus_info;
|
||||||
|
|
||||||
std::vector<ReplayBase::KartReplayEvent> m_all_replay_events;
|
std::vector<ReplayBase::KartReplayEvent> m_all_replay_events;
|
||||||
|
|
||||||
|
unsigned int m_last_egg_idx = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GhostKart(const std::string& ident,
|
GhostKart(const std::string& ident, unsigned int world_kart_id,
|
||||||
unsigned int world_kart_id, int position);
|
int position, float color_hue);
|
||||||
virtual void update(int ticks) OVERRIDE;
|
virtual void update(int ticks) OVERRIDE;
|
||||||
virtual void updateGraphics(float dt) OVERRIDE;
|
virtual void updateGraphics(float dt) OVERRIDE;
|
||||||
virtual void reset();
|
virtual void reset();
|
||||||
@ -66,6 +70,7 @@ public:
|
|||||||
void addReplayEvent(float time,
|
void addReplayEvent(float time,
|
||||||
const btTransform &trans,
|
const btTransform &trans,
|
||||||
const ReplayBase::PhysicInfo &pi,
|
const ReplayBase::PhysicInfo &pi,
|
||||||
|
const ReplayBase::BonusInfo &bi,
|
||||||
const ReplayBase::KartReplayEvent &kre);
|
const ReplayBase::KartReplayEvent &kre);
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Returns whether this kart is a ghost (replay) kart. */
|
/** Returns whether this kart is a ghost (replay) kart. */
|
||||||
@ -76,6 +81,12 @@ public:
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Returns the speed of the kart in meters/second. */
|
/** Returns the speed of the kart in meters/second. */
|
||||||
virtual float getSpeed() const;
|
virtual float getSpeed() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
/** Returns the time at which the kart was at a given distance.
|
||||||
|
* Returns -1.0f if none */
|
||||||
|
float getTimeForDistance(float distance);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
virtual void kartIsInRestNow() {};
|
virtual void kartIsInRestNow() {};
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "io/file_manager.hpp"
|
#include "io/file_manager.hpp"
|
||||||
#include "karts/abstract_kart.hpp"
|
#include "karts/abstract_kart.hpp"
|
||||||
|
#include "replay/replay_play.hpp"
|
||||||
#include "tracks/track.hpp"
|
#include "tracks/track.hpp"
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@ -29,6 +30,7 @@ EasterEggHunt::EasterEggHunt() : LinearWorld()
|
|||||||
WorldStatus::setClockMode(CLOCK_CHRONO);
|
WorldStatus::setClockMode(CLOCK_CHRONO);
|
||||||
m_use_highscores = true;
|
m_use_highscores = true;
|
||||||
m_eggs_found = 0;
|
m_eggs_found = 0;
|
||||||
|
m_only_ghosts = false;
|
||||||
} // EasterEggHunt
|
} // EasterEggHunt
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@ -39,13 +41,19 @@ void EasterEggHunt::init()
|
|||||||
LinearWorld::init();
|
LinearWorld::init();
|
||||||
m_display_rank = false;
|
m_display_rank = false;
|
||||||
|
|
||||||
|
unsigned int gk = 0;
|
||||||
|
if (race_manager->hasGhostKarts())
|
||||||
|
gk = ReplayPlay::get()->getNumGhostKart();
|
||||||
// check for possible problems if AI karts were incorrectly added
|
// check for possible problems if AI karts were incorrectly added
|
||||||
if(getNumKarts() > race_manager->getNumPlayers())
|
if((getNumKarts() - gk) > race_manager->getNumPlayers())
|
||||||
{
|
{
|
||||||
Log::error("EasterEggHunt]", "No AI exists for this game mode");
|
Log::error("EasterEggHunt]", "No AI exists for this game mode");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getNumKarts() == gk)
|
||||||
|
m_only_ghosts = true;
|
||||||
|
|
||||||
m_eggs_collected.resize(m_karts.size(), 0);
|
m_eggs_collected.resize(m_karts.size(), 0);
|
||||||
|
|
||||||
} // EasterEggHunt
|
} // EasterEggHunt
|
||||||
@ -154,6 +162,15 @@ void EasterEggHunt::collectedEasterEgg(const AbstractKart *kart)
|
|||||||
m_eggs_found++;
|
m_eggs_found++;
|
||||||
} // collectedEasterEgg
|
} // collectedEasterEgg
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
/** Called when a ghost kart has collected an egg.
|
||||||
|
* \param world_id The world id of the ghost kart that collected an egg.
|
||||||
|
*/
|
||||||
|
void EasterEggHunt::collectedEasterEggGhost(int world_id)
|
||||||
|
{
|
||||||
|
m_eggs_collected[world_id]++;
|
||||||
|
} // collectedEasterEgg
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Update the world and the track.
|
/** Update the world and the track.
|
||||||
* \param ticks Physics time step size - should be 1.
|
* \param ticks Physics time step size - should be 1.
|
||||||
@ -169,8 +186,16 @@ void EasterEggHunt::update(int ticks)
|
|||||||
*/
|
*/
|
||||||
bool EasterEggHunt::isRaceOver()
|
bool EasterEggHunt::isRaceOver()
|
||||||
{
|
{
|
||||||
if(m_eggs_found == m_number_of_eggs)
|
if(!m_only_ghosts && m_eggs_found == m_number_of_eggs)
|
||||||
return true;
|
return true;
|
||||||
|
else if (m_only_ghosts)
|
||||||
|
{
|
||||||
|
for (unsigned int i=0 ; i<m_eggs_collected.size();i++)
|
||||||
|
{
|
||||||
|
if (m_eggs_collected[i] == m_number_of_eggs)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
if(m_time<0)
|
if(m_time<0)
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
|
@ -40,8 +40,10 @@ private:
|
|||||||
/** Overall number of easter eggs. */
|
/** Overall number of easter eggs. */
|
||||||
int m_number_of_eggs;
|
int m_number_of_eggs;
|
||||||
|
|
||||||
/** Number of eggs found so far. */
|
/** Number of eggs found so far by players. */
|
||||||
int m_eggs_found;
|
int m_eggs_found;
|
||||||
|
|
||||||
|
bool m_only_ghosts;
|
||||||
public:
|
public:
|
||||||
EasterEggHunt();
|
EasterEggHunt();
|
||||||
virtual ~EasterEggHunt();
|
virtual ~EasterEggHunt();
|
||||||
@ -61,8 +63,11 @@ public:
|
|||||||
virtual void getKartsDisplayInfo(
|
virtual void getKartsDisplayInfo(
|
||||||
std::vector<RaceGUIBase::KartIconDisplayInfo> *info) OVERRIDE;
|
std::vector<RaceGUIBase::KartIconDisplayInfo> *info) OVERRIDE;
|
||||||
|
|
||||||
|
const int numberOfEggsFound() { return m_eggs_found; }
|
||||||
|
|
||||||
void updateKartRanks();
|
void updateKartRanks();
|
||||||
void collectedEasterEgg(const AbstractKart *kart);
|
void collectedEasterEgg(const AbstractKart *kart);
|
||||||
|
void collectedEasterEggGhost(int world_id);
|
||||||
void readData(const std::string &filename);
|
void readData(const std::string &filename);
|
||||||
|
|
||||||
virtual void checkForWrongDirection(unsigned int i, float dt) OVERRIDE;
|
virtual void checkForWrongDirection(unsigned int i, float dt) OVERRIDE;
|
||||||
|
@ -52,6 +52,8 @@ LinearWorld::LinearWorld() : WorldWithRank()
|
|||||||
m_last_lap_sfx_played = false;
|
m_last_lap_sfx_played = false;
|
||||||
m_last_lap_sfx_playing = false;
|
m_last_lap_sfx_playing = false;
|
||||||
m_fastest_lap_ticks = INT_MAX;
|
m_fastest_lap_ticks = INT_MAX;
|
||||||
|
m_valid_reference_time = false;
|
||||||
|
m_live_time_difference = 0.0f;
|
||||||
} // LinearWorld
|
} // LinearWorld
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -204,6 +206,10 @@ void LinearWorld::update(int ticks)
|
|||||||
m_kart_info[i].m_estimated_finish =
|
m_kart_info[i].m_estimated_finish =
|
||||||
estimateFinishTimeForKart(m_karts[i]);
|
estimateFinishTimeForKart(m_karts[i]);
|
||||||
}
|
}
|
||||||
|
// If one player and a ghost, or two compared ghosts,
|
||||||
|
// compute the live time difference
|
||||||
|
if(race_manager->hasGhostKarts() && race_manager->getNumberOfKarts() == 2)
|
||||||
|
updateLiveDifference();
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
// Debug output in case that the double position error occurs again.
|
// Debug output in case that the double position error occurs again.
|
||||||
@ -262,6 +268,45 @@ void LinearWorld::updateGraphics(float dt)
|
|||||||
|
|
||||||
} // updateGraphics
|
} // updateGraphics
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
/** This calculate the time difference between the second kart in the
|
||||||
|
* race and the first kart in the race (who must be a ghost)
|
||||||
|
*/
|
||||||
|
void LinearWorld::updateLiveDifference()
|
||||||
|
{
|
||||||
|
// First check that the call requirements are verified
|
||||||
|
assert (race_manager->hasGhostKarts() && race_manager->getNumberOfKarts() >= 2);
|
||||||
|
|
||||||
|
AbstractKart* ghost_kart = getKart(0);
|
||||||
|
|
||||||
|
// Get the distance at which the second kart is
|
||||||
|
float second_kart_distance = getOverallDistance(1);
|
||||||
|
|
||||||
|
// Check when the ghost what at this position
|
||||||
|
float ghost_time;
|
||||||
|
|
||||||
|
// If there are two ghost karts, the view is set to kart 0,
|
||||||
|
// so switch roles in the comparison. Note that
|
||||||
|
// we can't simply multiply the time by -1, as they are assymetrical.
|
||||||
|
// When one kart don't increase its distance (rescue, etc),
|
||||||
|
// the difference increases linearly for one and jump for the other.
|
||||||
|
if (getKart(1)->isGhostKart())
|
||||||
|
{
|
||||||
|
ghost_kart = getKart(1);
|
||||||
|
second_kart_distance = getOverallDistance(0);
|
||||||
|
}
|
||||||
|
ghost_time = ghost_kart->getTimeForDistance(second_kart_distance);
|
||||||
|
|
||||||
|
if (ghost_time >= 0.0f)
|
||||||
|
m_valid_reference_time = true;
|
||||||
|
else
|
||||||
|
m_valid_reference_time = false;
|
||||||
|
|
||||||
|
float current_time = World::getWorld()->getTime();
|
||||||
|
|
||||||
|
m_live_time_difference = current_time - ghost_time;
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Is called by check structures if a kart starts a new lap.
|
/** Is called by check structures if a kart starts a new lap.
|
||||||
* \param kart_index Index of the kart.
|
* \param kart_index Index of the kart.
|
||||||
|
@ -55,6 +55,21 @@ private:
|
|||||||
* get valid finish times estimates. */
|
* get valid finish times estimates. */
|
||||||
float m_distance_increase;
|
float m_distance_increase;
|
||||||
|
|
||||||
|
/** This stores the live time difference between a ghost kart
|
||||||
|
* and a second kart racing against it (normal or ghost).
|
||||||
|
*/
|
||||||
|
|
||||||
|
float m_live_time_difference;
|
||||||
|
|
||||||
|
/** True if the live_time_difference is invalid */
|
||||||
|
bool m_valid_reference_time;
|
||||||
|
|
||||||
|
/** This calculate the time difference between the second kart in the race
|
||||||
|
* (there must be at least two) and the first kart in the race
|
||||||
|
* (who must be a ghost).
|
||||||
|
*/
|
||||||
|
void updateLiveDifference();
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Some additional info that needs to be kept for each kart
|
/** Some additional info that needs to be kept for each kart
|
||||||
* in this kind of race.
|
* in this kind of race.
|
||||||
@ -120,13 +135,14 @@ public:
|
|||||||
|
|
||||||
virtual void update(int ticks) OVERRIDE;
|
virtual void update(int ticks) OVERRIDE;
|
||||||
virtual void updateGraphics(float dt) OVERRIDE;
|
virtual void updateGraphics(float dt) OVERRIDE;
|
||||||
float getDistanceDownTrackForKart(const int kart_id) const;
|
|
||||||
float getDistanceDownTrackForKart(const int kart_id,
|
float getDistanceDownTrackForKart(const int kart_id,
|
||||||
bool account_for_checklines) const;
|
bool account_for_checklines) const;
|
||||||
float getDistanceToCenterForKart(const int kart_id) const;
|
float getDistanceToCenterForKart(const int kart_id) const;
|
||||||
float getEstimatedFinishTime(const int kart_id) const;
|
float getEstimatedFinishTime(const int kart_id) const;
|
||||||
int getLapForKart(const int kart_id) const;
|
int getLapForKart(const int kart_id) const;
|
||||||
int getTicksAtLapForKart(const int kart_id) const;
|
int getTicksAtLapForKart(const int kart_id) const;
|
||||||
|
float getLiveTimeDifference() const { return m_live_time_difference; }
|
||||||
|
bool hasValidTimeDifference() const { return m_valid_reference_time; }
|
||||||
|
|
||||||
virtual void getKartsDisplayInfo(
|
virtual void getKartsDisplayInfo(
|
||||||
std::vector<RaceGUIBase::KartIconDisplayInfo> *info) OVERRIDE;
|
std::vector<RaceGUIBase::KartIconDisplayInfo> *info) OVERRIDE;
|
||||||
|
@ -511,6 +511,20 @@ public:
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
MinorRaceModeType getMinorMode() const { return m_minor_mode; }
|
MinorRaceModeType getMinorMode() const { return m_minor_mode; }
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
std::string getMinorModeName() const
|
||||||
|
{
|
||||||
|
switch (m_minor_mode)
|
||||||
|
{
|
||||||
|
case MINOR_MODE_NORMAL_RACE: return "normal";
|
||||||
|
case MINOR_MODE_TIME_TRIAL: return "time-trial";
|
||||||
|
case MINOR_MODE_FOLLOW_LEADER: return "follow-the-leader";
|
||||||
|
case MINOR_MODE_3_STRIKES: return "battle";
|
||||||
|
case MINOR_MODE_EASTER_EGG: return "egg-hunt";
|
||||||
|
case MINOR_MODE_SOCCER: return "soccer";
|
||||||
|
default: assert(false); return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
unsigned int getNumPlayers() const
|
unsigned int getNumPlayers() const
|
||||||
{
|
{
|
||||||
return (unsigned int) m_player_karts.size();
|
return (unsigned int) m_player_karts.size();
|
||||||
@ -524,8 +538,10 @@ public:
|
|||||||
*/
|
*/
|
||||||
int getNumLaps() const
|
int getNumLaps() const
|
||||||
{
|
{
|
||||||
if(m_minor_mode==MINOR_MODE_3_STRIKES ||
|
if(m_minor_mode==MINOR_MODE_3_STRIKES ||
|
||||||
m_minor_mode==MINOR_MODE_FOLLOW_LEADER )
|
m_minor_mode==MINOR_MODE_FOLLOW_LEADER ||
|
||||||
|
m_minor_mode==MINOR_MODE_SOCCER ||
|
||||||
|
m_minor_mode==MINOR_MODE_EASTER_EGG )
|
||||||
return 9999;
|
return 9999;
|
||||||
return m_num_laps[m_track_number];
|
return m_num_laps[m_track_number];
|
||||||
} // getNumLaps
|
} // getNumLaps
|
||||||
@ -635,7 +651,7 @@ public:
|
|||||||
return m_ai_kart_list;
|
return m_ai_kart_list;
|
||||||
} // getAIKartList
|
} // getAIKartList
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** \brief get information about given mode (returns true if 'mode' is of
|
/** \brief get information about current mode (returns true if 'mode' is of
|
||||||
* linear races type) */
|
* linear races type) */
|
||||||
bool isLinearRaceMode()
|
bool isLinearRaceMode()
|
||||||
{
|
{
|
||||||
@ -645,6 +661,19 @@ public:
|
|||||||
if(id > 999 && id < 2000) return true;
|
if(id > 999 && id < 2000) return true;
|
||||||
else return false;
|
else return false;
|
||||||
} // isLinearRaceMode
|
} // isLinearRaceMode
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
/** \brief get information about given mode (returns true if 'mode' is of
|
||||||
|
* linear races type) */
|
||||||
|
bool isLinearRaceMode(const MinorRaceModeType mode)
|
||||||
|
{
|
||||||
|
const int id = (int)mode;
|
||||||
|
// info is stored in its ID for conveniance, see the macros LINEAR_RACE
|
||||||
|
// and BATTLE_ARENA above for exact meaning.
|
||||||
|
if(id > 999 && id < 2000) return true;
|
||||||
|
else return false;
|
||||||
|
} // isLinearRaceMode
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** \brief Returns true if the current mode is a battle mode. */
|
/** \brief Returns true if the current mode is a battle mode. */
|
||||||
bool isBattleMode()
|
bool isBattleMode()
|
||||||
@ -672,6 +701,18 @@ public:
|
|||||||
{
|
{
|
||||||
return m_minor_mode == MINOR_MODE_TUTORIAL;
|
return m_minor_mode == MINOR_MODE_TUTORIAL;
|
||||||
} // isTutorialMode
|
} // isTutorialMode
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isEggHuntMode()
|
||||||
|
{
|
||||||
|
return m_minor_mode == MINOR_MODE_EASTER_EGG;
|
||||||
|
} // isEggHuntMode
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isTimeTrialMode()
|
||||||
|
{
|
||||||
|
return m_minor_mode == MINOR_MODE_TIME_TRIAL;
|
||||||
|
} // isTimeTrialMode
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** \brief Returns true if the current mode has laps. */
|
/** \brief Returns true if the current mode has laps. */
|
||||||
bool modeHasLaps()
|
bool modeHasLaps()
|
||||||
|
@ -29,10 +29,10 @@ ReplayBase::ReplayBase()
|
|||||||
* \param full_path True if the file is full path.
|
* \param full_path True if the file is full path.
|
||||||
* \return A FILE *, or NULL if the file could not be opened.
|
* \return A FILE *, or NULL if the file could not be opened.
|
||||||
*/
|
*/
|
||||||
FILE* ReplayBase::openReplayFile(bool writeable, bool full_path)
|
FILE* ReplayBase::openReplayFile(bool writeable, bool full_path, int replay_file_number)
|
||||||
{
|
{
|
||||||
FILE *fd = fopen(full_path ? getReplayFilename().c_str() :
|
FILE *fd = fopen(full_path ? getReplayFilename(replay_file_number).c_str() :
|
||||||
(file_manager->getReplayDir() + getReplayFilename()).c_str(),
|
(file_manager->getReplayDir() + getReplayFilename(replay_file_number)).c_str(),
|
||||||
writeable ? "w" : "r");
|
writeable ? "w" : "r");
|
||||||
if (!fd)
|
if (!fd)
|
||||||
{
|
{
|
||||||
|
@ -54,18 +54,43 @@ protected:
|
|||||||
float m_steer;
|
float m_steer;
|
||||||
/** The suspension length of 4 wheels at a certain time. */
|
/** The suspension length of 4 wheels at a certain time. */
|
||||||
float m_suspension_length[4];
|
float m_suspension_length[4];
|
||||||
|
/** The skidding state */
|
||||||
|
int m_skidding_state;
|
||||||
}; // PhysicInfo
|
}; // PhysicInfo
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
struct BonusInfo
|
||||||
|
{
|
||||||
|
/** The attachment. This is stored using a custom format
|
||||||
|
0 = none ; 1 = parachute ; 2 = anvil ; 3 = bomb ;
|
||||||
|
4 = swatter ; 5 = bubblegum
|
||||||
|
This is necessary so replay files are not broken if the
|
||||||
|
game internal attachment format/ordering is changed. */
|
||||||
|
int m_attachment;
|
||||||
|
/** The nitro amount at a certain time. */
|
||||||
|
float m_nitro_amount;
|
||||||
|
/** The number of items at a certain time. */
|
||||||
|
int m_item_amount;
|
||||||
|
/** The type of item at a certain time. */
|
||||||
|
int m_item_type;
|
||||||
|
/** Used to store mode-specific values : eggs in egg-hunt,
|
||||||
|
number of lives in battle-mode. */
|
||||||
|
int m_special_value;
|
||||||
|
}; // StateInfo
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Records all other events. */
|
/** Records all other events. */
|
||||||
struct KartReplayEvent
|
struct KartReplayEvent
|
||||||
{
|
{
|
||||||
|
/** distance on track for the kart recorded. */
|
||||||
|
float m_distance;
|
||||||
/** Nitro usage for the kart recorded. */
|
/** Nitro usage for the kart recorded. */
|
||||||
int m_nitro_usage;
|
int m_nitro_usage;
|
||||||
/** Zipper usage for the kart recorded. */
|
/** Zipper usage for the kart recorded. */
|
||||||
bool m_zipper_usage;
|
bool m_zipper_usage;
|
||||||
/** Skidding state for the kart recorded. */
|
/** Skidding effect for the kart recorded. */
|
||||||
int m_skidding_state;
|
int m_skidding_effect;
|
||||||
/** Kart skidding showing red flame or not. */
|
/** Kart skidding showing red flame or not. */
|
||||||
bool m_red_skidding;
|
bool m_red_skidding;
|
||||||
/** True if the kart recorded is jumping. */
|
/** True if the kart recorded is jumping. */
|
||||||
@ -73,15 +98,19 @@ protected:
|
|||||||
}; // KartReplayEvent
|
}; // KartReplayEvent
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
FILE *openReplayFile(bool writeable, bool full_path = false);
|
FILE *openReplayFile(bool writeable, bool full_path = false, int replay_file_number=1);
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Returns the filename that was opened. */
|
/** Returns the filename that was opened. */
|
||||||
virtual const std::string& getReplayFilename() const = 0;
|
virtual const std::string& getReplayFilename(int replay_file_number = 1) const = 0;
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Returns the version number of the replay file. This is used to check
|
/** Returns the version number of the replay file recorderd by this executable.
|
||||||
* that a loaded replay file can still be understood by this
|
* This is also used as a maximum supported version by this exexcutable. */
|
||||||
* executable. */
|
unsigned int getCurrentReplayVersion() const { return 4; }
|
||||||
unsigned int getReplayVersion() const { return 3; }
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
/** This is used to check that a loaded replay file can still
|
||||||
|
* be understood by this executable. */
|
||||||
|
unsigned int getMinSupportedReplayVersion() const { return 3; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ReplayBase();
|
ReplayBase();
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include <irrlicht.h>
|
#include <irrlicht.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
ReplayPlay::SortOrder ReplayPlay::m_sort_order = ReplayPlay::SO_DEFAULT;
|
ReplayPlay::SortOrder ReplayPlay::m_sort_order = ReplayPlay::SO_DEFAULT;
|
||||||
ReplayPlay *ReplayPlay::m_replay_play = NULL;
|
ReplayPlay *ReplayPlay::m_replay_play = NULL;
|
||||||
@ -39,7 +40,9 @@ ReplayPlay *ReplayPlay::m_replay_play = NULL;
|
|||||||
*/
|
*/
|
||||||
ReplayPlay::ReplayPlay()
|
ReplayPlay::ReplayPlay()
|
||||||
{
|
{
|
||||||
m_current_replay_file = 0;
|
m_current_replay_file = 0;
|
||||||
|
m_second_replay_file = 0;
|
||||||
|
m_second_replay_enabled = false;
|
||||||
} // ReplayPlay
|
} // ReplayPlay
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@ -83,20 +86,23 @@ void ReplayPlay::loadAllReplayFile()
|
|||||||
file_manager->listFiles(files, file_manager->getReplayDir(),
|
file_manager->listFiles(files, file_manager->getReplayDir(),
|
||||||
/*is_full_path*/ false);
|
/*is_full_path*/ false);
|
||||||
|
|
||||||
|
int j=0;
|
||||||
|
|
||||||
for (std::set<std::string>::iterator i = files.begin();
|
for (std::set<std::string>::iterator i = files.begin();
|
||||||
i != files.end(); ++i)
|
i != files.end(); ++i)
|
||||||
{
|
{
|
||||||
if (!addReplayFile(*i))
|
if (!addReplayFile(*i, false, j))
|
||||||
{
|
{
|
||||||
// Skip invalid replay file
|
// Skip invalid replay file
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
j++;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // loadAllReplayFile
|
} // loadAllReplayFile
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
bool ReplayPlay::addReplayFile(const std::string& fn, bool custom_replay)
|
bool ReplayPlay::addReplayFile(const std::string& fn, bool custom_replay, int call_index)
|
||||||
{
|
{
|
||||||
|
|
||||||
char s[1024], s1[1024];
|
char s[1024], s1[1024];
|
||||||
@ -119,14 +125,31 @@ bool ReplayPlay::addReplayFile(const std::string& fn, bool custom_replay)
|
|||||||
fclose(fd);
|
fclose(fd);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (version != getReplayVersion())
|
if (version > getCurrentReplayVersion() ||
|
||||||
|
version < getMinSupportedReplayVersion() )
|
||||||
{
|
{
|
||||||
Log::warn("Replay", "Replay is version '%d'", version);
|
Log::warn("Replay", "Replay is version '%d'", version);
|
||||||
Log::warn("Replay", "STK version is '%d'", getReplayVersion());
|
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());
|
Log::warn("Replay", "Skipped '%s'", fn.c_str());
|
||||||
fclose(fd);
|
fclose(fd);
|
||||||
return false;
|
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)
|
while(true)
|
||||||
{
|
{
|
||||||
@ -160,13 +183,29 @@ bool ReplayPlay::addReplayFile(const std::string& fn, bool custom_replay)
|
|||||||
// (see GhostController::getName)
|
// (see GhostController::getName)
|
||||||
rd.m_name_list.push_back("");
|
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;
|
int reverse = 0;
|
||||||
fgets(s, 1023, fd);
|
fgets(s, 1023, fd);
|
||||||
if(sscanf(s, "reverse: %d", &reverse) != 1)
|
if(sscanf(s, "reverse: %d", &reverse) != 1)
|
||||||
{
|
{
|
||||||
Log::warn("Replay", "Reverse info found in replay file.");
|
Log::warn("Replay", "No reverse info found in replay file.");
|
||||||
fclose(fd);
|
fclose(fd);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -180,6 +219,22 @@ bool ReplayPlay::addReplayFile(const std::string& fn, bool custom_replay)
|
|||||||
return false;
|
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);
|
fgets(s, 1023, fd);
|
||||||
if (sscanf(s, "track: %s", s1) != 1)
|
if (sscanf(s, "track: %s", s1) != 1)
|
||||||
{
|
{
|
||||||
@ -212,6 +267,21 @@ bool ReplayPlay::addReplayFile(const std::string& fn, bool custom_replay)
|
|||||||
fclose(fd);
|
fclose(fd);
|
||||||
return false;
|
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);
|
fclose(fd);
|
||||||
m_replay_file_list.push_back(rd);
|
m_replay_file_list.push_back(rd);
|
||||||
|
|
||||||
@ -228,22 +298,42 @@ bool ReplayPlay::addReplayFile(const std::string& fn, bool custom_replay)
|
|||||||
void ReplayPlay::load()
|
void ReplayPlay::load()
|
||||||
{
|
{
|
||||||
m_ghost_karts.clearAndDeleteAll();
|
m_ghost_karts.clearAndDeleteAll();
|
||||||
|
|
||||||
|
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];
|
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,
|
FILE *fd = openReplayFile(/*writeable*/false,
|
||||||
m_replay_file_list.at(m_current_replay_file).m_custom_replay_file);
|
m_replay_file_list.at(replay_index).m_custom_replay_file, replay_file_number);
|
||||||
|
|
||||||
if(!fd)
|
if(!fd)
|
||||||
{
|
{
|
||||||
Log::error("Replay", "Can't read '%s', ghost replay disabled.",
|
Log::error("Replay", "Can't read '%s', ghost replay disabled.",
|
||||||
getReplayFilename().c_str());
|
getReplayFilename(replay_file_number).c_str());
|
||||||
destroy();
|
destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::info("Replay", "Reading replay file '%s'.", getReplayFilename().c_str());
|
Log::info("Replay", "Reading replay file '%s'.", getReplayFilename(replay_file_number).c_str());
|
||||||
|
|
||||||
const unsigned int line_skipped = getNumGhostKart() + 7;
|
ReplayData &rd = m_replay_file_list[replay_index];
|
||||||
for (unsigned int i = 0; i < line_skipped; i++)
|
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);
|
fgets(s, 1023, fd);
|
||||||
|
|
||||||
// eof actually doesn't trigger here, since it requires first to try
|
// eof actually doesn't trigger here, since it requires first to try
|
||||||
@ -252,27 +342,35 @@ void ReplayPlay::load()
|
|||||||
{
|
{
|
||||||
if(fgets(s, 1023, fd)==NULL) // eof reached
|
if(fgets(s, 1023, fd)==NULL) // eof reached
|
||||||
break;
|
break;
|
||||||
readKartData(fd, s);
|
readKartData(fd, s, second_replay);
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(fd, "Replay file end.\n");
|
|
||||||
fclose(fd);
|
fclose(fd);
|
||||||
} // load
|
} // loadFile
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Reads all data from a replay file for a specific kart.
|
/** Reads all data from a replay file for a specific kart.
|
||||||
* \param fd The file descriptor from which to read.
|
* \param fd The file descriptor from which to read.
|
||||||
*/
|
*/
|
||||||
void ReplayPlay::readKartData(FILE *fd, char *next_line)
|
void ReplayPlay::readKartData(FILE *fd, char *next_line, bool second_replay)
|
||||||
{
|
{
|
||||||
char s[1024];
|
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();
|
const unsigned int kart_num = m_ghost_karts.size();
|
||||||
ReplayData &rd = m_replay_file_list[m_current_replay_file];
|
unsigned int first_loaded_f_num = 0;
|
||||||
m_ghost_karts.push_back(new GhostKart(rd.m_kart_list.at(kart_num),
|
|
||||||
kart_num, kart_num + 1));
|
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(new 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);
|
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]);
|
rd.m_name_list[kart_num-first_loaded_f_num]);
|
||||||
getGhostKart(kart_num)->setController(controller);
|
getGhostKart(kart_num)->setController(controller);
|
||||||
|
|
||||||
unsigned int size;
|
unsigned int size;
|
||||||
@ -283,45 +381,137 @@ void ReplayPlay::readKartData(FILE *fd, char *next_line)
|
|||||||
for(unsigned int i=0; i<size; i++)
|
for(unsigned int i=0; i<size; i++)
|
||||||
{
|
{
|
||||||
fgets(s, 1023, fd);
|
fgets(s, 1023, fd);
|
||||||
float x, y, z, rx, ry, rz, rw, time, speed, steer, w1, w2, w3, w4;
|
float x, y, z, rx, ry, rz, rw, time, speed, steer, w1, w2, w3, w4, nitro_amount, distance;
|
||||||
int nitro, zipper, skidding, red_skidding, jumping;
|
int skidding_state, attachment, item_amount, item_type, special_value,
|
||||||
|
nitro, zipper, skidding, red_skidding, jumping;
|
||||||
|
|
||||||
// Check for EV_TRANSFORM event:
|
// Check for EV_TRANSFORM event:
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
if(sscanf(s, "%f %f %f %f %f %f %f %f %f %f %f %f %f %f %d %d %d %d %d\n",
|
|
||||||
&time,
|
// Up to STK 0.9.3 replays
|
||||||
&x, &y, &z,
|
if (rd.m_replay_version == 3)
|
||||||
&rx, &ry, &rz, &rw,
|
|
||||||
&speed, &steer, &w1, &w2, &w3, &w4,
|
|
||||||
&nitro, &zipper, &skidding, &red_skidding, &jumping
|
|
||||||
)==19)
|
|
||||||
{
|
{
|
||||||
btQuaternion q(rx, ry, rz, rw);
|
if(sscanf(s, "%f %f %f %f %f %f %f %f %f %f %f %f %f %f %d %d %d %d %d\n",
|
||||||
btVector3 xyz(x, y, z);
|
&time,
|
||||||
PhysicInfo pi = {0};
|
&x, &y, &z,
|
||||||
KartReplayEvent kre = {0};
|
&rx, &ry, &rz, &rw,
|
||||||
pi.m_speed = speed;
|
&speed, &steer, &w1, &w2, &w3, &w4,
|
||||||
pi.m_steer = steer;
|
&nitro, &zipper, &skidding, &red_skidding, &jumping
|
||||||
pi.m_suspension_length[0] = w1;
|
)==19)
|
||||||
pi.m_suspension_length[1] = w2;
|
{
|
||||||
pi.m_suspension_length[2] = w3;
|
btQuaternion q(rx, ry, rz, rw);
|
||||||
pi.m_suspension_length[3] = w4;
|
btVector3 xyz(x, y, z);
|
||||||
kre.m_nitro_usage = nitro;
|
PhysicInfo pi = {0};
|
||||||
kre.m_zipper_usage = zipper!=0;
|
BonusInfo bi = {0};
|
||||||
kre.m_skidding_state = skidding;
|
KartReplayEvent kre = {0};
|
||||||
kre.m_red_skidding = red_skidding!=0;
|
|
||||||
kre.m_jumping = jumping != 0;
|
pi.m_speed = speed;
|
||||||
m_ghost_karts[kart_num].addReplayEvent(time,
|
pi.m_steer = steer;
|
||||||
btTransform(q, xyz), pi, kre);
|
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
|
else
|
||||||
{
|
{
|
||||||
// Invalid record found
|
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,
|
||||||
Log::warn("Replay", "Can't read replay data line %d:", i);
|
&x, &y, &z,
|
||||||
Log::warn("Replay", "%s", s);
|
&rx, &ry, &rz, &rw,
|
||||||
Log::warn("Replay", "Ignored.");
|
&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
|
} // for i
|
||||||
|
|
||||||
} // readKartData
|
} // 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
|
||||||
|
@ -46,7 +46,8 @@ public:
|
|||||||
SO_DIFF,
|
SO_DIFF,
|
||||||
SO_LAPS,
|
SO_LAPS,
|
||||||
SO_TIME,
|
SO_TIME,
|
||||||
SO_USER
|
SO_USER,
|
||||||
|
SO_VERSION
|
||||||
};
|
};
|
||||||
|
|
||||||
class ReplayData
|
class ReplayData
|
||||||
@ -54,13 +55,18 @@ public:
|
|||||||
public:
|
public:
|
||||||
std::string m_filename;
|
std::string m_filename;
|
||||||
std::string m_track_name;
|
std::string m_track_name;
|
||||||
|
std::string m_minor_mode;
|
||||||
|
core::stringw m_stk_version;
|
||||||
core::stringw m_user_name;
|
core::stringw m_user_name;
|
||||||
std::vector<std::string> m_kart_list;
|
std::vector<std::string> m_kart_list;
|
||||||
std::vector<core::stringw> m_name_list;
|
std::vector<core::stringw> m_name_list;
|
||||||
|
std::vector<float> m_kart_color; //no sorting for this
|
||||||
bool m_reverse;
|
bool m_reverse;
|
||||||
bool m_custom_replay_file;
|
bool m_custom_replay_file;
|
||||||
unsigned int m_difficulty;
|
unsigned int m_difficulty;
|
||||||
unsigned int m_laps;
|
unsigned int m_laps;
|
||||||
|
unsigned int m_replay_version; //no sorting for this
|
||||||
|
uint64_t m_replay_uid; //no sorting for this
|
||||||
float m_min_time;
|
float m_min_time;
|
||||||
|
|
||||||
bool operator < (const ReplayData& r) const
|
bool operator < (const ReplayData& r) const
|
||||||
@ -88,6 +94,9 @@ public:
|
|||||||
case SO_USER:
|
case SO_USER:
|
||||||
return m_user_name < r.m_user_name;
|
return m_user_name < r.m_user_name;
|
||||||
break;
|
break;
|
||||||
|
case SO_VERSION:
|
||||||
|
return m_stk_version < r.m_stk_version;
|
||||||
|
break;
|
||||||
} // switch
|
} // switch
|
||||||
return true;
|
return true;
|
||||||
} // operator <
|
} // operator <
|
||||||
@ -100,6 +109,10 @@ private:
|
|||||||
|
|
||||||
unsigned int m_current_replay_file;
|
unsigned int m_current_replay_file;
|
||||||
|
|
||||||
|
unsigned int m_second_replay_file;
|
||||||
|
|
||||||
|
bool m_second_replay_enabled;
|
||||||
|
|
||||||
std::vector<ReplayData> m_replay_file_list;
|
std::vector<ReplayData> m_replay_file_list;
|
||||||
|
|
||||||
/** All ghost karts. */
|
/** All ghost karts. */
|
||||||
@ -107,10 +120,11 @@ private:
|
|||||||
|
|
||||||
ReplayPlay();
|
ReplayPlay();
|
||||||
~ReplayPlay();
|
~ReplayPlay();
|
||||||
void readKartData(FILE *fd, char *next_line);
|
void readKartData(FILE *fd, char *next_line, bool second_replay);
|
||||||
public:
|
public:
|
||||||
void reset();
|
void reset();
|
||||||
void load();
|
void load();
|
||||||
|
void loadFile(bool second_replay);
|
||||||
void loadAllReplayFile();
|
void loadAllReplayFile();
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
static void setSortOrder(SortOrder so) { m_sort_order = so; }
|
static void setSortOrder(SortOrder so) { m_sort_order = so; }
|
||||||
@ -124,9 +138,21 @@ public:
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
void setReplayFile(unsigned int n)
|
void setReplayFile(unsigned int n)
|
||||||
{ m_current_replay_file = n; }
|
{ m_current_replay_file = n; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setSecondReplayFile(unsigned int n, bool second_replay_enabled)
|
||||||
|
{ m_second_replay_file = n;
|
||||||
|
m_second_replay_enabled = second_replay_enabled;}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setReplayFileByUID(uint64_t uid);
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
unsigned int getReplayIdByUID(uint64_t uid);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
bool addReplayFile(const std::string& fn,
|
bool addReplayFile(const std::string& fn,
|
||||||
bool custom_replay = false);
|
bool custom_replay = false,
|
||||||
|
int call_index = 0);
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
const ReplayData& getReplayData(unsigned int n) const
|
const ReplayData& getReplayData(unsigned int n) const
|
||||||
{ return m_replay_file_list.at(n); }
|
{ return m_replay_file_list.at(n); }
|
||||||
@ -139,14 +165,25 @@ public:
|
|||||||
const unsigned int getNumGhostKart() const
|
const unsigned int getNumGhostKart() const
|
||||||
{
|
{
|
||||||
assert(m_replay_file_list.size() > 0);
|
assert(m_replay_file_list.size() > 0);
|
||||||
return (unsigned int)m_replay_file_list.at(m_current_replay_file)
|
unsigned int num = m_replay_file_list.at(m_current_replay_file)
|
||||||
.m_kart_list.size();
|
.m_kart_list.size();
|
||||||
|
unsigned int second_file_num = m_replay_file_list.at(m_second_replay_file)
|
||||||
|
.m_kart_list.size();
|
||||||
|
|
||||||
|
num = (m_second_replay_enabled) ? num + second_file_num : num;
|
||||||
|
|
||||||
|
return num;
|
||||||
} // getNumGhostKart
|
} // getNumGhostKart
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
const std::string& getGhostKartName(int n) const
|
const std::string& getGhostKartName(unsigned int n) const
|
||||||
{
|
{
|
||||||
assert(m_replay_file_list.size() > 0);
|
assert(m_replay_file_list.size() > 0);
|
||||||
return m_replay_file_list.at(m_current_replay_file).m_kart_list.at(n);
|
|
||||||
|
unsigned int fkn = m_replay_file_list.at(m_current_replay_file).m_kart_list.size();
|
||||||
|
if (n < fkn)
|
||||||
|
return m_replay_file_list.at(m_current_replay_file).m_kart_list.at(n);
|
||||||
|
else
|
||||||
|
return m_replay_file_list.at(m_second_replay_file).m_kart_list.at(n-fkn);
|
||||||
}
|
}
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Creates a new instance of the replay object. */
|
/** Creates a new instance of the replay object. */
|
||||||
@ -160,10 +197,13 @@ public:
|
|||||||
{ delete m_replay_play; m_replay_play = NULL; }
|
{ delete m_replay_play; m_replay_play = NULL; }
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Returns the filename that was opened. */
|
/** Returns the filename that was opened. */
|
||||||
virtual const std::string& getReplayFilename() const
|
virtual const std::string& getReplayFilename(int replay_file_number = 1) const
|
||||||
{
|
{
|
||||||
assert(m_replay_file_list.size() > 0);
|
assert(m_replay_file_list.size() > 0);
|
||||||
return m_replay_file_list.at(m_current_replay_file).m_filename;
|
if (replay_file_number == 2)
|
||||||
|
return m_replay_file_list.at(m_second_replay_file).m_filename;
|
||||||
|
else
|
||||||
|
return m_replay_file_list.at(m_current_replay_file).m_filename;
|
||||||
}
|
}
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
}; // Replay
|
}; // Replay
|
||||||
|
@ -20,9 +20,16 @@
|
|||||||
|
|
||||||
#include "config/stk_config.hpp"
|
#include "config/stk_config.hpp"
|
||||||
#include "io/file_manager.hpp"
|
#include "io/file_manager.hpp"
|
||||||
|
#include "items/attachment.hpp"
|
||||||
|
#include "items/powerup.hpp"
|
||||||
|
#include "items/powerup_manager.hpp"
|
||||||
#include "guiengine/message_queue.hpp"
|
#include "guiengine/message_queue.hpp"
|
||||||
|
#include "karts/controller/player_controller.hpp"
|
||||||
#include "karts/ghost_kart.hpp"
|
#include "karts/ghost_kart.hpp"
|
||||||
|
#include "karts/skidding.hpp"
|
||||||
#include "karts/kart_gfx.hpp"
|
#include "karts/kart_gfx.hpp"
|
||||||
|
#include "modes/easter_egg_hunt.hpp"
|
||||||
|
#include "modes/linear_world.hpp"
|
||||||
#include "modes/world.hpp"
|
#include "modes/world.hpp"
|
||||||
#include "physics/btKart.hpp"
|
#include "physics/btKart.hpp"
|
||||||
#include "race/race_manager.hpp"
|
#include "race/race_manager.hpp"
|
||||||
@ -31,7 +38,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <karts/controller/player_controller.hpp>
|
#include <cinttypes>
|
||||||
|
|
||||||
ReplayRecorder *ReplayRecorder::m_replay_recorder = NULL;
|
ReplayRecorder *ReplayRecorder::m_replay_recorder = NULL;
|
||||||
|
|
||||||
@ -42,6 +49,10 @@ ReplayRecorder::ReplayRecorder()
|
|||||||
{
|
{
|
||||||
m_complete_replay = false;
|
m_complete_replay = false;
|
||||||
m_incorrect_replay = false;
|
m_incorrect_replay = false;
|
||||||
|
m_previous_steer = 0.0f;
|
||||||
|
|
||||||
|
assert(stk_config->m_replay_max_frames >= 0);
|
||||||
|
m_max_frames = stk_config->m_replay_max_frames;
|
||||||
} // ReplayRecorder
|
} // ReplayRecorder
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@ -58,6 +69,7 @@ void ReplayRecorder::reset()
|
|||||||
m_incorrect_replay = false;
|
m_incorrect_replay = false;
|
||||||
m_transform_events.clear();
|
m_transform_events.clear();
|
||||||
m_physic_info.clear();
|
m_physic_info.clear();
|
||||||
|
m_bonus_info.clear();
|
||||||
m_kart_replay_event.clear();
|
m_kart_replay_event.clear();
|
||||||
m_count_transforms.clear();
|
m_count_transforms.clear();
|
||||||
m_last_saved_time.clear();
|
m_last_saved_time.clear();
|
||||||
@ -78,14 +90,15 @@ void ReplayRecorder::init()
|
|||||||
reset();
|
reset();
|
||||||
m_transform_events.resize(race_manager->getNumberOfKarts());
|
m_transform_events.resize(race_manager->getNumberOfKarts());
|
||||||
m_physic_info.resize(race_manager->getNumberOfKarts());
|
m_physic_info.resize(race_manager->getNumberOfKarts());
|
||||||
|
m_bonus_info.resize(race_manager->getNumberOfKarts());
|
||||||
m_kart_replay_event.resize(race_manager->getNumberOfKarts());
|
m_kart_replay_event.resize(race_manager->getNumberOfKarts());
|
||||||
unsigned int max_frames = (unsigned int)( stk_config->m_replay_max_time
|
|
||||||
/ stk_config->m_replay_dt);
|
|
||||||
for(unsigned int i=0; i<race_manager->getNumberOfKarts(); i++)
|
for(unsigned int i=0; i<race_manager->getNumberOfKarts(); i++)
|
||||||
{
|
{
|
||||||
m_transform_events[i].resize(max_frames);
|
m_transform_events[i].resize(m_max_frames);
|
||||||
m_physic_info[i].resize(max_frames);
|
m_physic_info[i].resize(m_max_frames);
|
||||||
m_kart_replay_event[i].resize(max_frames);
|
m_bonus_info[i].resize(m_max_frames);
|
||||||
|
m_kart_replay_event[i].resize(m_max_frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_count_transforms.resize(race_manager->getNumberOfKarts(), 0);
|
m_count_transforms.resize(race_manager->getNumberOfKarts(), 0);
|
||||||
@ -116,13 +129,122 @@ void ReplayRecorder::update(int ticks)
|
|||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
m_count++;
|
m_count++;
|
||||||
#endif
|
#endif
|
||||||
if (time - m_last_saved_time[i] < stk_config->m_replay_dt)
|
// If one of the tracked kart data has significantly changed
|
||||||
|
// for the kart, update sooner than the usual dt
|
||||||
|
bool force_update = false;
|
||||||
|
|
||||||
|
// Don't save directly the enum value, because any change
|
||||||
|
// to it would break the reading of old replays
|
||||||
|
int attachment = enumToCode(kart->getAttachment()->getType());
|
||||||
|
int powerup_type = enumToCode(kart->getPowerup()->getType());
|
||||||
|
int special_value = 0;
|
||||||
|
|
||||||
|
// In egg hunt mode, use store the number of eggs found so far
|
||||||
|
// This assumes that egg hunt mode is only available in single-player
|
||||||
|
if (race_manager->isEggHuntMode())
|
||||||
|
{
|
||||||
|
EasterEggHunt *easterworld = dynamic_cast<EasterEggHunt*>(World::getWorld());
|
||||||
|
special_value = easterworld->numberOfEggsFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment == -1)
|
||||||
|
{
|
||||||
|
Log::error("ReplayRecorder", "Unknown attachment type");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (powerup_type == -1)
|
||||||
|
{
|
||||||
|
Log::error("ReplayRecorder", "Unknown powerup type");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_count_transforms[i] >= 2)
|
||||||
|
{
|
||||||
|
BonusInfo *b_prev = &(m_bonus_info[i][m_count_transforms[i]-1]);
|
||||||
|
BonusInfo *b_prev2 = &(m_bonus_info[i][m_count_transforms[i]-2]);
|
||||||
|
PhysicInfo *q_prev = &(m_physic_info[i][m_count_transforms[i]-1]);
|
||||||
|
|
||||||
|
// If the kart changes its steering
|
||||||
|
if (fabsf(kart->getControls().getSteer() - m_previous_steer) >
|
||||||
|
stk_config->m_replay_delta_steering)
|
||||||
|
force_update = true;
|
||||||
|
|
||||||
|
// If the kart starts or stops skidding
|
||||||
|
if (kart->getSkidding()->getSkidState() != q_prev->m_skidding_state)
|
||||||
|
force_update = true;
|
||||||
|
// If the kart changes speed significantly
|
||||||
|
float speed_change = fabsf(kart->getSpeed() - q_prev->m_speed);
|
||||||
|
if ( speed_change > stk_config->m_replay_delta_speed )
|
||||||
|
{
|
||||||
|
if (speed_change > 4*stk_config->m_replay_delta_speed)
|
||||||
|
force_update = true;
|
||||||
|
else if (speed_change > 2*stk_config->m_replay_delta_speed &&
|
||||||
|
time - m_last_saved_time[i] > (stk_config->m_replay_dt/8.0f))
|
||||||
|
force_update = true;
|
||||||
|
else if (time - m_last_saved_time[i] > (stk_config->m_replay_dt/3.0f))
|
||||||
|
force_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the attachment has changed
|
||||||
|
if (attachment != b_prev->m_attachment)
|
||||||
|
force_update = true;
|
||||||
|
|
||||||
|
// If the item amount has changed
|
||||||
|
if (kart->getNumPowerup() != b_prev->m_item_amount)
|
||||||
|
force_update = true;
|
||||||
|
|
||||||
|
// If the item type has changed
|
||||||
|
if (powerup_type != b_prev->m_item_type)
|
||||||
|
force_update = true;
|
||||||
|
|
||||||
|
// In egg-hunt mode, if an egg has been collected
|
||||||
|
// In battle mode, if a live has been lost/gained
|
||||||
|
if (special_value != b_prev->m_special_value)
|
||||||
|
force_update = true;
|
||||||
|
|
||||||
|
// If nitro starts being used or is collected
|
||||||
|
if (kart->getEnergy() != b_prev->m_nitro_amount &&
|
||||||
|
b_prev->m_nitro_amount == b_prev2->m_nitro_amount)
|
||||||
|
force_update = true;
|
||||||
|
|
||||||
|
// If nitro stops being used
|
||||||
|
// (also generates an extra transform on collection,
|
||||||
|
// should be negligible and better than heavier checks)
|
||||||
|
if (kart->getEnergy() == b_prev->m_nitro_amount &&
|
||||||
|
b_prev->m_nitro_amount != b_prev2->m_nitro_amount)
|
||||||
|
force_update = true;
|
||||||
|
|
||||||
|
// If close to the end of the race, reduce the time step
|
||||||
|
// for extra precision
|
||||||
|
// TODO : fast updates when close to the last egg in egg hunt
|
||||||
|
if (race_manager->isLinearRaceMode())
|
||||||
|
{
|
||||||
|
float full_distance = race_manager->getNumLaps()
|
||||||
|
* Track::getCurrentTrack()->getTrackLength();
|
||||||
|
|
||||||
|
const LinearWorld *linearworld = dynamic_cast<LinearWorld*>(World::getWorld());
|
||||||
|
if (full_distance + DISTANCE_MAX_UPDATES >= linearworld->getOverallDistance(i) &&
|
||||||
|
full_distance <= linearworld->getOverallDistance(i) + DISTANCE_FAST_UPDATES)
|
||||||
|
{
|
||||||
|
if (fabsf(full_distance - linearworld->getOverallDistance(i)) < DISTANCE_MAX_UPDATES)
|
||||||
|
force_update = true;
|
||||||
|
else if (time - m_last_saved_time[i] > (stk_config->m_replay_dt/5.0f))
|
||||||
|
force_update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ( time - m_last_saved_time[i] < (stk_config->m_replay_dt - stk_config->ticks2Time(1)) &&
|
||||||
|
!force_update)
|
||||||
{
|
{
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
m_count_skipped_time++;
|
m_count_skipped_time++;
|
||||||
#endif
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_previous_steer = kart->getControls().getSteer();
|
||||||
m_last_saved_time[i] = time;
|
m_last_saved_time[i] = time;
|
||||||
m_count_transforms[i]++;
|
m_count_transforms[i]++;
|
||||||
if (m_count_transforms[i] >= m_transform_events[i].size())
|
if (m_count_transforms[i] >= m_transform_events[i].size())
|
||||||
@ -140,6 +262,7 @@ void ReplayRecorder::update(int ticks)
|
|||||||
}
|
}
|
||||||
TransformEvent *p = &(m_transform_events[i][m_count_transforms[i]-1]);
|
TransformEvent *p = &(m_transform_events[i][m_count_transforms[i]-1]);
|
||||||
PhysicInfo *q = &(m_physic_info[i][m_count_transforms[i]-1]);
|
PhysicInfo *q = &(m_physic_info[i][m_count_transforms[i]-1]);
|
||||||
|
BonusInfo *b = &(m_bonus_info[i][m_count_transforms[i]-1]);
|
||||||
KartReplayEvent *r = &(m_kart_replay_event[i][m_count_transforms[i]-1]);
|
KartReplayEvent *r = &(m_kart_replay_event[i][m_count_transforms[i]-1]);
|
||||||
|
|
||||||
p->m_time = World::getWorld()->getTime();
|
p->m_time = World::getWorld()->getTime();
|
||||||
@ -159,9 +282,25 @@ void ReplayRecorder::update(int ticks)
|
|||||||
->getWheelInfo(j).m_raycastInfo.m_suspensionLength;
|
->getWheelInfo(j).m_raycastInfo.m_suspensionLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
q->m_skidding_state = kart->getSkidding()->getSkidState();
|
||||||
|
|
||||||
|
b->m_attachment = attachment;
|
||||||
|
b->m_nitro_amount = kart->getEnergy();
|
||||||
|
b->m_item_amount = kart->getNumPowerup();
|
||||||
|
b->m_item_type = powerup_type;
|
||||||
|
b->m_special_value = special_value;
|
||||||
|
|
||||||
|
//Only saves distance if recording a linear race
|
||||||
|
if (race_manager->isLinearRaceMode())
|
||||||
|
{
|
||||||
|
const LinearWorld *linearworld = dynamic_cast<LinearWorld*>(World::getWorld());
|
||||||
|
r->m_distance = linearworld->getOverallDistance(kart->getWorldKartId());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
r->m_distance = 0.0f;
|
||||||
|
|
||||||
kart->getKartGFX()->getGFXStatus(&(r->m_nitro_usage),
|
kart->getKartGFX()->getGFXStatus(&(r->m_nitro_usage),
|
||||||
&(r->m_zipper_usage), &(r->m_skidding_state), &(r->m_red_skidding));
|
&(r->m_zipper_usage), &(r->m_skidding_effect), &(r->m_red_skidding));
|
||||||
r->m_jumping = kart->isJumping();
|
r->m_jumping = kart->isJumping();
|
||||||
} // for i
|
} // for i
|
||||||
|
|
||||||
@ -172,6 +311,42 @@ void ReplayRecorder::update(int ticks)
|
|||||||
}
|
}
|
||||||
} // update
|
} // update
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
/** Compute the replay's UID ; partly based on race data ; partly randomly
|
||||||
|
*/
|
||||||
|
uint64_t ReplayRecorder::computeUID(float min_time)
|
||||||
|
{
|
||||||
|
uint64_t unique_identifier = 0;
|
||||||
|
|
||||||
|
// First store some basic replay data
|
||||||
|
int min_time_uid = (int) (min_time*1000);
|
||||||
|
min_time_uid = min_time_uid%60000;
|
||||||
|
|
||||||
|
int day, month, year;
|
||||||
|
StkTime::getDate(&day, &month, &year);
|
||||||
|
uint64_t date_uid = year%10;
|
||||||
|
date_uid = date_uid*12 + (month-1);;
|
||||||
|
date_uid = date_uid*31 + (day-1);
|
||||||
|
|
||||||
|
int reverse = race_manager->getReverseTrack() ? 1 : 0;
|
||||||
|
unique_identifier += reverse;
|
||||||
|
unique_identifier += race_manager->getDifficulty()*2;
|
||||||
|
unique_identifier += (race_manager->getNumLaps()-1)*8;
|
||||||
|
unique_identifier += min_time_uid*160;
|
||||||
|
unique_identifier += date_uid*9600000;
|
||||||
|
|
||||||
|
// Add a random value to make sure the identifier is unique
|
||||||
|
// and use it to make the non-random part non-obvious
|
||||||
|
// using magic and arbitrary constants
|
||||||
|
|
||||||
|
int random = rand()%9998 + 2; //avoid 0 and 1
|
||||||
|
unique_identifier += random*47;
|
||||||
|
unique_identifier *= random*10000;
|
||||||
|
unique_identifier += (10000-random);
|
||||||
|
|
||||||
|
return unique_identifier;
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Saves the replay data stored in the internal data structures.
|
/** Saves the replay data stored in the internal data structures.
|
||||||
*/
|
*/
|
||||||
@ -220,7 +395,10 @@ void ReplayRecorder::save()
|
|||||||
(file_manager->getReplayDir() + getReplayFilename()).c_str());
|
(file_manager->getReplayDir() + getReplayFilename()).c_str());
|
||||||
MessageQueue::add(MessageQueue::MT_GENERIC, msg);
|
MessageQueue::add(MessageQueue::MT_GENERIC, msg);
|
||||||
|
|
||||||
fprintf(fd, "version: %d\n", getReplayVersion());
|
fprintf(fd, "version: %d\n", getCurrentReplayVersion());
|
||||||
|
fprintf(fd, "stk_version: %s\n", STK_VERSION);
|
||||||
|
|
||||||
|
unsigned int player_count = 0;
|
||||||
for (unsigned int real_karts = 0; real_karts < num_karts; real_karts++)
|
for (unsigned int real_karts = 0; real_karts < num_karts; real_karts++)
|
||||||
{
|
{
|
||||||
const AbstractKart *kart = world->getKart(real_karts);
|
const AbstractKart *kart = world->getKart(real_karts);
|
||||||
@ -229,30 +407,44 @@ void ReplayRecorder::save()
|
|||||||
// XML encode the username to handle Unicode
|
// XML encode the username to handle Unicode
|
||||||
fprintf(fd, "kart: %s %s\n", kart->getIdent().c_str(),
|
fprintf(fd, "kart: %s %s\n", kart->getIdent().c_str(),
|
||||||
StringUtils::xmlEncode(kart->getController()->getName()).c_str());
|
StringUtils::xmlEncode(kart->getController()->getName()).c_str());
|
||||||
|
|
||||||
|
if (kart->getController()->isPlayerController())
|
||||||
|
{
|
||||||
|
fprintf(fd, "kart_color: %f\n", StateManager::get()->getActivePlayer(player_count)->getConstProfile()->getDefaultKartColor());
|
||||||
|
player_count++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fprintf(fd, "kart_color: 0\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_last_uid = computeUID(min_time);
|
||||||
|
|
||||||
|
int num_laps = race_manager->getNumLaps();
|
||||||
|
if (num_laps == 9999) num_laps = 0; // no lap in that race mode
|
||||||
|
|
||||||
fprintf(fd, "kart_list_end\n");
|
fprintf(fd, "kart_list_end\n");
|
||||||
fprintf(fd, "reverse: %d\n", (int)race_manager->getReverseTrack());
|
fprintf(fd, "reverse: %d\n", (int)race_manager->getReverseTrack());
|
||||||
fprintf(fd, "difficulty: %d\n", race_manager->getDifficulty());
|
fprintf(fd, "difficulty: %d\n", race_manager->getDifficulty());
|
||||||
|
fprintf(fd, "mode: %s\n", race_manager->getMinorModeName().c_str());
|
||||||
fprintf(fd, "track: %s\n", Track::getCurrentTrack()->getIdent().c_str());
|
fprintf(fd, "track: %s\n", Track::getCurrentTrack()->getIdent().c_str());
|
||||||
fprintf(fd, "laps: %d\n", race_manager->getNumLaps());
|
fprintf(fd, "laps: %d\n", num_laps);
|
||||||
fprintf(fd, "min_time: %f\n", min_time);
|
fprintf(fd, "min_time: %f\n", min_time);
|
||||||
|
fprintf(fd, "replay_uid: %" PRIu64 "\n", m_last_uid);
|
||||||
|
|
||||||
unsigned int max_frames = (unsigned int)( stk_config->m_replay_max_time
|
|
||||||
/ stk_config->m_replay_dt );
|
|
||||||
for (unsigned int k = 0; k < num_karts; k++)
|
for (unsigned int k = 0; k < num_karts; k++)
|
||||||
{
|
{
|
||||||
if (world->getKart(k)->isGhostKart()) continue;
|
if (world->getKart(k)->isGhostKart()) continue;
|
||||||
fprintf(fd, "size: %d\n", m_count_transforms[k]);
|
fprintf(fd, "size: %d\n", m_count_transforms[k]);
|
||||||
|
|
||||||
unsigned int num_transforms = std::min(max_frames,
|
unsigned int num_transforms = std::min(m_max_frames,
|
||||||
m_count_transforms[k]);
|
m_count_transforms[k]);
|
||||||
for (unsigned int i = 0; i < num_transforms; i++)
|
for (unsigned int i = 0; i < num_transforms; i++)
|
||||||
{
|
{
|
||||||
const TransformEvent *p = &(m_transform_events[k][i]);
|
const TransformEvent *p = &(m_transform_events[k][i]);
|
||||||
const PhysicInfo *q = &(m_physic_info[k][i]);
|
const PhysicInfo *q = &(m_physic_info[k][i]);
|
||||||
|
const BonusInfo *b = &(m_bonus_info[k][i]);
|
||||||
const KartReplayEvent *r = &(m_kart_replay_event[k][i]);
|
const KartReplayEvent *r = &(m_kart_replay_event[k][i]);
|
||||||
fprintf(fd, "%f %f %f %f %f %f %f %f %f %f %f %f %f %f %d %d %d %d %d\n",
|
fprintf(fd, "%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",
|
||||||
p->m_time,
|
p->m_time,
|
||||||
p->m_transform.getOrigin().getX(),
|
p->m_transform.getOrigin().getX(),
|
||||||
p->m_transform.getOrigin().getY(),
|
p->m_transform.getOrigin().getY(),
|
||||||
@ -267,9 +459,16 @@ void ReplayRecorder::save()
|
|||||||
q->m_suspension_length[1],
|
q->m_suspension_length[1],
|
||||||
q->m_suspension_length[2],
|
q->m_suspension_length[2],
|
||||||
q->m_suspension_length[3],
|
q->m_suspension_length[3],
|
||||||
|
q->m_skidding_state,
|
||||||
|
b->m_attachment,
|
||||||
|
b->m_nitro_amount,
|
||||||
|
b->m_item_amount,
|
||||||
|
b->m_item_type,
|
||||||
|
b->m_special_value,
|
||||||
|
r->m_distance,
|
||||||
r->m_nitro_usage,
|
r->m_nitro_usage,
|
||||||
(int)r->m_zipper_usage,
|
(int)r->m_zipper_usage,
|
||||||
r->m_skidding_state,
|
r->m_skidding_effect,
|
||||||
(int)r->m_red_skidding,
|
(int)r->m_red_skidding,
|
||||||
(int)r->m_jumping
|
(int)r->m_jumping
|
||||||
);
|
);
|
||||||
@ -277,3 +476,79 @@ void ReplayRecorder::save()
|
|||||||
}
|
}
|
||||||
fclose(fd);
|
fclose(fd);
|
||||||
} // save
|
} // save
|
||||||
|
|
||||||
|
/* Returns an encoding value for a given attachment type.
|
||||||
|
* The internal values of the enum for attachments may change if attachments
|
||||||
|
* are introduced, removed or even reordered. To avoid compatibility issues
|
||||||
|
* with previous replay files, we add a layer to encode an enum semantical
|
||||||
|
* value the same way, independently of its internal value.
|
||||||
|
* \param type : the type of attachment to encode */
|
||||||
|
|
||||||
|
int ReplayRecorder::enumToCode (Attachment::AttachmentType type)
|
||||||
|
{
|
||||||
|
int code =
|
||||||
|
(type == Attachment::ATTACH_NOTHING) ? 0 :
|
||||||
|
(type == Attachment::ATTACH_PARACHUTE) ? 1 :
|
||||||
|
(type == Attachment::ATTACH_ANVIL) ? 2 :
|
||||||
|
(type == Attachment::ATTACH_BOMB) ? 3 :
|
||||||
|
(type == Attachment::ATTACH_SWATTER) ? 4 :
|
||||||
|
(type == Attachment::ATTACH_BUBBLEGUM_SHIELD) ? 5 :
|
||||||
|
-1 ;
|
||||||
|
|
||||||
|
return code;
|
||||||
|
} // enumToCode
|
||||||
|
|
||||||
|
/* Returns an encoding value for a given item type
|
||||||
|
* \param type : the type of item to encode */
|
||||||
|
|
||||||
|
int ReplayRecorder::enumToCode (PowerupManager::PowerupType type)
|
||||||
|
{
|
||||||
|
int code =
|
||||||
|
(type == PowerupManager::POWERUP_NOTHING) ? 0 :
|
||||||
|
(type == PowerupManager::POWERUP_BUBBLEGUM) ? 1 :
|
||||||
|
(type == PowerupManager::POWERUP_CAKE) ? 2 :
|
||||||
|
(type == PowerupManager::POWERUP_BOWLING) ? 3 :
|
||||||
|
(type == PowerupManager::POWERUP_ZIPPER) ? 4 :
|
||||||
|
(type == PowerupManager::POWERUP_PLUNGER) ? 5 :
|
||||||
|
(type == PowerupManager::POWERUP_SWITCH) ? 6 :
|
||||||
|
(type == PowerupManager::POWERUP_SWATTER) ? 7 :
|
||||||
|
(type == PowerupManager::POWERUP_RUBBERBALL) ? 8 :
|
||||||
|
(type == PowerupManager::POWERUP_PARACHUTE) ? 9 :
|
||||||
|
-1 ;
|
||||||
|
|
||||||
|
return code;
|
||||||
|
} // enumToCode
|
||||||
|
|
||||||
|
/* Returns the attachment enum value for a given replay code */
|
||||||
|
Attachment::AttachmentType ReplayRecorder::codeToEnumAttach (int code)
|
||||||
|
{
|
||||||
|
Attachment::AttachmentType type =
|
||||||
|
(code == 0) ? Attachment::ATTACH_NOTHING :
|
||||||
|
(code == 1) ? Attachment::ATTACH_PARACHUTE :
|
||||||
|
(code == 2) ? Attachment::ATTACH_ANVIL :
|
||||||
|
(code == 3) ? Attachment::ATTACH_BOMB :
|
||||||
|
(code == 4) ? Attachment::ATTACH_SWATTER :
|
||||||
|
(code == 5) ? Attachment::ATTACH_BUBBLEGUM_SHIELD :
|
||||||
|
Attachment::ATTACH_NOTHING ;
|
||||||
|
|
||||||
|
return type;
|
||||||
|
} // codeToEnumAttach
|
||||||
|
|
||||||
|
/* Returns the item enum value for a given replay code */
|
||||||
|
PowerupManager::PowerupType ReplayRecorder::codeToEnumItem (int code)
|
||||||
|
{
|
||||||
|
PowerupManager::PowerupType type =
|
||||||
|
(code == 0) ? PowerupManager::POWERUP_NOTHING :
|
||||||
|
(code == 1) ? PowerupManager::POWERUP_BUBBLEGUM :
|
||||||
|
(code == 2) ? PowerupManager::POWERUP_CAKE :
|
||||||
|
(code == 3) ? PowerupManager::POWERUP_BOWLING :
|
||||||
|
(code == 4) ? PowerupManager::POWERUP_ZIPPER :
|
||||||
|
(code == 5) ? PowerupManager::POWERUP_PLUNGER :
|
||||||
|
(code == 6) ? PowerupManager::POWERUP_SWITCH :
|
||||||
|
(code == 7) ? PowerupManager::POWERUP_SWATTER :
|
||||||
|
(code == 8) ? PowerupManager::POWERUP_RUBBERBALL :
|
||||||
|
(code == 9) ? PowerupManager::POWERUP_PARACHUTE :
|
||||||
|
PowerupManager::POWERUP_NOTHING ;
|
||||||
|
|
||||||
|
return type;
|
||||||
|
} // codeToEnumItem
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
#ifndef HEADER_REPLAY_RECORDER_HPP
|
#ifndef HEADER_REPLAY_RECORDER_HPP
|
||||||
#define HEADER_REPLAY_RECORDER_HPP
|
#define HEADER_REPLAY_RECORDER_HPP
|
||||||
|
|
||||||
|
#include "items/attachment.hpp"
|
||||||
|
#include "items/powerup_manager.hpp"
|
||||||
#include "karts/controller/kart_control.hpp"
|
#include "karts/controller/kart_control.hpp"
|
||||||
#include "replay/replay_base.hpp"
|
#include "replay/replay_base.hpp"
|
||||||
|
|
||||||
@ -38,6 +40,9 @@ private:
|
|||||||
/** A separate vector of Replay Events for all physic info. */
|
/** A separate vector of Replay Events for all physic info. */
|
||||||
std::vector< std::vector<PhysicInfo> > m_physic_info;
|
std::vector< std::vector<PhysicInfo> > m_physic_info;
|
||||||
|
|
||||||
|
/** A separate vector of Replay Events for all item/nitro info. */
|
||||||
|
std::vector< std::vector<BonusInfo> > m_bonus_info;
|
||||||
|
|
||||||
/** A separate vector of Replay Events for all other events. */
|
/** A separate vector of Replay Events for all other events. */
|
||||||
std::vector< std::vector<KartReplayEvent> > m_kart_replay_event;
|
std::vector< std::vector<KartReplayEvent> > m_kart_replay_event;
|
||||||
|
|
||||||
@ -54,6 +59,18 @@ private:
|
|||||||
|
|
||||||
bool m_incorrect_replay;
|
bool m_incorrect_replay;
|
||||||
|
|
||||||
|
unsigned int m_max_frames;
|
||||||
|
|
||||||
|
// Stores the steering value at the previous transform.
|
||||||
|
// Used to trigger the recording of new transforms.
|
||||||
|
float m_previous_steer = 0.0f;
|
||||||
|
|
||||||
|
const float DISTANCE_FAST_UPDATES = 10.0f;
|
||||||
|
|
||||||
|
const float DISTANCE_MAX_UPDATES = 1.0f;
|
||||||
|
|
||||||
|
uint64_t m_last_uid;
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
/** Counts overall number of events stored. */
|
/** Counts overall number of events stored. */
|
||||||
unsigned int m_count;
|
unsigned int m_count;
|
||||||
@ -65,6 +82,9 @@ private:
|
|||||||
unsigned int m_count_skipped_interpolation;
|
unsigned int m_count_skipped_interpolation;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/** Compute the replay's UID ; partly based on race data ; partly randomly */
|
||||||
|
uint64_t computeUID(float min_time);
|
||||||
|
|
||||||
|
|
||||||
ReplayRecorder();
|
ReplayRecorder();
|
||||||
~ReplayRecorder();
|
~ReplayRecorder();
|
||||||
@ -74,6 +94,16 @@ public:
|
|||||||
void save();
|
void save();
|
||||||
void update(int ticks);
|
void update(int ticks);
|
||||||
|
|
||||||
|
const uint64_t getLastUID() { return m_last_uid; }
|
||||||
|
|
||||||
|
/** Functions to encode and decode attahcments and item types,
|
||||||
|
so that the stored value is independent from internal
|
||||||
|
representation and resilient to such changes. */
|
||||||
|
static int enumToCode (Attachment::AttachmentType type);
|
||||||
|
static int enumToCode (PowerupManager::PowerupType type);
|
||||||
|
static Attachment::AttachmentType codeToEnumAttach (int code);
|
||||||
|
static PowerupManager::PowerupType codeToEnumItem (int code);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Creates a new instance of the replay object. */
|
/** Creates a new instance of the replay object. */
|
||||||
static void create() {
|
static void create() {
|
||||||
@ -89,8 +119,9 @@ public:
|
|||||||
static void destroy() { delete m_replay_recorder; m_replay_recorder=NULL; }
|
static void destroy() { delete m_replay_recorder; m_replay_recorder=NULL; }
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Returns the filename that was opened. */
|
/** Returns the filename that was opened. */
|
||||||
virtual const std::string& getReplayFilename() const { return m_filename; }
|
virtual const std::string& getReplayFilename(int replay_file_number = 1) const { return m_filename; }
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
}; // ReplayRecorder
|
}; // ReplayRecorder
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -19,24 +19,59 @@
|
|||||||
#include "states_screens/dialogs/ghost_replay_info_dialog.hpp"
|
#include "states_screens/dialogs/ghost_replay_info_dialog.hpp"
|
||||||
|
|
||||||
#include "config/player_manager.hpp"
|
#include "config/player_manager.hpp"
|
||||||
|
#include "graphics/stk_tex_manager.hpp"
|
||||||
#include "replay/replay_play.hpp"
|
#include "replay/replay_play.hpp"
|
||||||
#include "states_screens/ghost_replay_selection.hpp"
|
#include "states_screens/ghost_replay_selection.hpp"
|
||||||
#include "states_screens/state_manager.hpp"
|
#include "states_screens/state_manager.hpp"
|
||||||
|
#include "tracks/track.hpp"
|
||||||
|
#include "tracks/track_manager.hpp"
|
||||||
|
|
||||||
using namespace GUIEngine;
|
using namespace GUIEngine;
|
||||||
using namespace irr::core;
|
using namespace irr::core;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
GhostReplayInfoDialog::GhostReplayInfoDialog(unsigned int replay_id)
|
GhostReplayInfoDialog::GhostReplayInfoDialog(unsigned int replay_id,
|
||||||
: ModalDialog(0.8f,0.5f), m_replay_id(replay_id)
|
uint64_t compare_replay_uid, bool compare_ghost)
|
||||||
|
: ModalDialog(0.85f,0.65f), m_replay_id(replay_id)
|
||||||
{
|
{
|
||||||
m_self_destroy = false;
|
m_self_destroy = false;
|
||||||
m_record_race = false;
|
m_record_race = false;
|
||||||
m_watch_only = false;
|
m_watch_only = false;
|
||||||
|
|
||||||
|
m_compare_ghost = compare_ghost;
|
||||||
|
m_compare_replay_uid = compare_replay_uid;
|
||||||
|
|
||||||
m_rd = ReplayPlay::get()->getReplayData(m_replay_id);
|
m_rd = ReplayPlay::get()->getReplayData(m_replay_id);
|
||||||
|
|
||||||
loadFromFile("ghost_replay_info_dialog.stkgui");
|
loadFromFile("ghost_replay_info_dialog.stkgui");
|
||||||
|
|
||||||
|
Track* track = track_manager->getTrack(m_rd.m_track_name);
|
||||||
|
|
||||||
|
m_track_screenshot_widget = getWidget<IconButtonWidget>("track_screenshot");
|
||||||
|
m_track_screenshot_widget->setFocusable(false);
|
||||||
|
m_track_screenshot_widget->m_tab_stop = false;
|
||||||
|
|
||||||
|
// temporary icon, will replace it just after (but it will be shown if the given icon is not found)
|
||||||
|
m_track_screenshot_widget->m_properties[PROP_ICON] = "gui/main_help.png";
|
||||||
|
|
||||||
|
irr::video::ITexture* image = STKTexManager::getInstance()
|
||||||
|
->getTexture(track->getScreenshotFile(),
|
||||||
|
"While loading screenshot for track '%s':", track->getFilename());
|
||||||
|
if(!image)
|
||||||
|
{
|
||||||
|
image = STKTexManager::getInstance()->getTexture("main_help.png",
|
||||||
|
"While loading screenshot for track '%s':", track->getFilename());
|
||||||
|
}
|
||||||
|
if (image != NULL)
|
||||||
|
m_track_screenshot_widget->setImage(image);
|
||||||
|
|
||||||
|
// TODO : small refinement, add the possibility to tab stops for lists
|
||||||
|
// to make this unselectable by keyboard/mouse
|
||||||
|
m_replay_info_widget = getWidget<GUIEngine::ListWidget>("current_replay_info");
|
||||||
|
assert(m_replay_info_widget != NULL);
|
||||||
|
|
||||||
|
updateReplayDisplayedInfo();
|
||||||
|
|
||||||
LabelWidget *name = getWidget<LabelWidget>("name");
|
LabelWidget *name = getWidget<LabelWidget>("name");
|
||||||
assert(name);
|
assert(name);
|
||||||
name->setText(stringw((m_rd.m_custom_replay_file ? StringUtils::getBasename
|
name->setText(stringw((m_rd.m_custom_replay_file ? StringUtils::getBasename
|
||||||
@ -50,6 +85,7 @@ GhostReplayInfoDialog::GhostReplayInfoDialog(unsigned int replay_id)
|
|||||||
m_action_widget = getWidget<RibbonWidget>("actions");
|
m_action_widget = getWidget<RibbonWidget>("actions");
|
||||||
m_record_widget = getWidget<CheckBoxWidget>("record-race");
|
m_record_widget = getWidget<CheckBoxWidget>("record-race");
|
||||||
m_watch_widget = getWidget<CheckBoxWidget>("watch-only");
|
m_watch_widget = getWidget<CheckBoxWidget>("watch-only");
|
||||||
|
m_compare_widget = getWidget<CheckBoxWidget>("compare-ghost");
|
||||||
|
|
||||||
if (race_manager->getNumLocalPlayers() > 1)
|
if (race_manager->getNumLocalPlayers() > 1)
|
||||||
{
|
{
|
||||||
@ -59,17 +95,96 @@ GhostReplayInfoDialog::GhostReplayInfoDialog(unsigned int replay_id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_record_widget->setState(false);
|
m_record_widget->setState(false);
|
||||||
m_watch_widget->setState(false);
|
m_watch_widget->setState(m_compare_ghost);
|
||||||
|
m_compare_widget->setState(m_compare_ghost);
|
||||||
|
|
||||||
|
if (m_compare_ghost)
|
||||||
|
{
|
||||||
|
m_watch_only = true;
|
||||||
|
m_record_race = false;
|
||||||
|
m_record_widget->setState(false);
|
||||||
|
m_record_widget->setVisible(!m_watch_only);
|
||||||
|
getWidget<LabelWidget>("record-race-text")->setVisible(!m_watch_only);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display this checkbox only if there is another replay file to compare with
|
||||||
|
getWidget<LabelWidget>("compare-ghost-text")->setVisible(m_compare_ghost);
|
||||||
|
m_compare_widget->setVisible(m_compare_ghost);
|
||||||
|
|
||||||
|
|
||||||
m_action_widget->setFocusForPlayer(PLAYER_ID_GAME_MASTER);
|
m_action_widget->setFocusForPlayer(PLAYER_ID_GAME_MASTER);
|
||||||
m_action_widget->select("start", PLAYER_ID_GAME_MASTER);
|
m_action_widget->select("start", PLAYER_ID_GAME_MASTER);
|
||||||
} // GhostReplayInfoDialog
|
} // GhostReplayInfoDialog
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
GhostReplayInfoDialog::~GhostReplayInfoDialog()
|
GhostReplayInfoDialog::~GhostReplayInfoDialog()
|
||||||
{
|
{
|
||||||
} // ~GhostReplayInfoDialog
|
} // ~GhostReplayInfoDialog
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
void GhostReplayInfoDialog::updateReplayDisplayedInfo()
|
||||||
|
{
|
||||||
|
m_replay_info_widget->clear();
|
||||||
|
|
||||||
|
bool is_linear = GhostReplaySelection::getInstance()->isActiveModeLinear();
|
||||||
|
std::vector<GUIEngine::ListWidget::ListCell> row;
|
||||||
|
|
||||||
|
// Display the header in normal cells
|
||||||
|
// as the header doesn't work with modal dialogs
|
||||||
|
if (is_linear)
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(_("Reverse"), -1, 3, true));
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(_("Difficulty"), -1, 4, true));
|
||||||
|
if (is_linear)
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(_("Laps"), -1, 3, true));
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(_("Time"), -1, 4, true));
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(_("User"), -1, 5, true));
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(_("Version"), -1, 3, true));
|
||||||
|
|
||||||
|
m_replay_info_widget->addItem(StringUtils::toString(0), row);
|
||||||
|
|
||||||
|
// Now display the data from the selected replay,
|
||||||
|
// and if applicable from the replay to compare with
|
||||||
|
int num_replays_to_list = (m_compare_ghost) ? 2 : 1;
|
||||||
|
|
||||||
|
for (int i=1; i<=num_replays_to_list; i++)
|
||||||
|
{
|
||||||
|
row.clear();
|
||||||
|
|
||||||
|
int id;
|
||||||
|
|
||||||
|
if (i==1)
|
||||||
|
id = m_replay_id;
|
||||||
|
else
|
||||||
|
id = ReplayPlay::get()->getReplayIdByUID(m_compare_replay_uid);
|
||||||
|
|
||||||
|
const ReplayPlay::ReplayData& rd = ReplayPlay::get()->getReplayData(id);
|
||||||
|
|
||||||
|
if (is_linear)
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(rd.m_reverse ? _("Yes") : _("No"), -1, 3, true));
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(race_manager->
|
||||||
|
getDifficultyName((RaceManager::Difficulty) rd.m_difficulty),
|
||||||
|
-1, 4, true));
|
||||||
|
if (is_linear)
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(StringUtils::toWString(rd.m_laps), -1, 3, true));
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(StringUtils::toWString(rd.m_min_time) + L"s", -1, 4, true));
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(rd.m_user_name.empty() ? " " : rd.m_user_name, -1, 5, true));
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(rd.m_stk_version.empty() ? " " : rd.m_stk_version, -1, 3, true));
|
||||||
|
|
||||||
|
m_replay_info_widget->addItem(StringUtils::toString(i), row);
|
||||||
|
} // for num_replays_to_list
|
||||||
|
} // updateReplayDisplayedInfo
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
GUIEngine::EventPropagation
|
GUIEngine::EventPropagation
|
||||||
GhostReplayInfoDialog::processEvent(const std::string& event_source)
|
GhostReplayInfoDialog::processEvent(const std::string& event_source)
|
||||||
@ -82,6 +197,9 @@ GUIEngine::EventPropagation
|
|||||||
|
|
||||||
if(selection == "start")
|
if(selection == "start")
|
||||||
{
|
{
|
||||||
|
// Make sure to enable the correct race mode
|
||||||
|
race_manager->setMinorMode(GhostReplaySelection::getInstance()->getActiveMode());
|
||||||
|
|
||||||
bool reverse = m_rd.m_reverse;
|
bool reverse = m_rd.m_reverse;
|
||||||
std::string track_name = m_rd.m_track_name;
|
std::string track_name = m_rd.m_track_name;
|
||||||
int laps = m_rd.m_laps;
|
int laps = m_rd.m_laps;
|
||||||
@ -89,11 +207,21 @@ GUIEngine::EventPropagation
|
|||||||
|
|
||||||
race_manager->setRecordRace(m_record_race);
|
race_manager->setRecordRace(m_record_race);
|
||||||
race_manager->setWatchingReplay(m_watch_only);
|
race_manager->setWatchingReplay(m_watch_only);
|
||||||
|
|
||||||
ModalDialog::dismiss();
|
|
||||||
ReplayPlay::get()->setReplayFile(replay_id);
|
ReplayPlay::get()->setReplayFile(replay_id);
|
||||||
|
if (m_compare_ghost)
|
||||||
|
{
|
||||||
|
int second_replay_id = ReplayPlay::get()->getReplayIdByUID(m_compare_replay_uid);
|
||||||
|
ReplayPlay::get()->setSecondReplayFile(second_replay_id, /* use a second replay*/ true);
|
||||||
|
m_compare_ghost = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ReplayPlay::get()->setSecondReplayFile(0, /* use a second replay*/ false);
|
||||||
|
|
||||||
race_manager->setRaceGhostKarts(true);
|
race_manager->setRaceGhostKarts(true);
|
||||||
|
|
||||||
|
// The race manager automatically adds karts for the ghosts
|
||||||
|
// so only set it to the number of human players
|
||||||
race_manager->setNumKarts(race_manager->getNumLocalPlayers());
|
race_manager->setNumKarts(race_manager->getNumLocalPlayers());
|
||||||
|
|
||||||
// Disable accidentally unlocking of a challenge
|
// Disable accidentally unlocking of a challenge
|
||||||
@ -101,6 +229,11 @@ GUIEngine::EventPropagation
|
|||||||
|
|
||||||
race_manager->setReverseTrack(reverse);
|
race_manager->setReverseTrack(reverse);
|
||||||
|
|
||||||
|
//Reset comparison if active
|
||||||
|
GhostReplaySelection::getInstance()->setCompare(false);
|
||||||
|
|
||||||
|
ModalDialog::dismiss();
|
||||||
|
|
||||||
if (race_manager->isWatchingReplay())
|
if (race_manager->isWatchingReplay())
|
||||||
race_manager->startWatchingReplay(track_name, laps);
|
race_manager->startWatchingReplay(track_name, laps);
|
||||||
else
|
else
|
||||||
@ -108,6 +241,19 @@ GUIEngine::EventPropagation
|
|||||||
|
|
||||||
return GUIEngine::EVENT_BLOCK;
|
return GUIEngine::EVENT_BLOCK;
|
||||||
}
|
}
|
||||||
|
else if(selection == "add-ghost-to-compare")
|
||||||
|
{
|
||||||
|
// First set values for comparison
|
||||||
|
m_compare_replay_uid = m_rd.m_replay_uid;
|
||||||
|
|
||||||
|
m_compare_ghost = true;
|
||||||
|
|
||||||
|
refreshMainScreen();
|
||||||
|
|
||||||
|
// Now quit the dialog
|
||||||
|
m_self_destroy = true;
|
||||||
|
return GUIEngine::EVENT_BLOCK;
|
||||||
|
}
|
||||||
else if(selection == "remove")
|
else if(selection == "remove")
|
||||||
{
|
{
|
||||||
std::string fn = m_rd.m_filename;
|
std::string fn = m_rd.m_filename;
|
||||||
@ -135,6 +281,32 @@ GUIEngine::EventPropagation
|
|||||||
m_record_widget->setState(false);
|
m_record_widget->setState(false);
|
||||||
m_record_widget->setVisible(!m_watch_only);
|
m_record_widget->setVisible(!m_watch_only);
|
||||||
getWidget<LabelWidget>("record-race-text")->setVisible(!m_watch_only);
|
getWidget<LabelWidget>("record-race-text")->setVisible(!m_watch_only);
|
||||||
|
if (!m_watch_only && m_compare_ghost)
|
||||||
|
{
|
||||||
|
m_compare_ghost = false;
|
||||||
|
m_compare_widget->setState(false);
|
||||||
|
refreshMainScreen();
|
||||||
|
|
||||||
|
m_replay_id = ReplayPlay::get()->getReplayIdByUID(m_rd.m_replay_uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (event_source == "compare-ghost")
|
||||||
|
{
|
||||||
|
m_compare_ghost = m_compare_widget->getState();
|
||||||
|
m_record_race = false;
|
||||||
|
m_record_widget->setState(false);
|
||||||
|
if (m_compare_ghost)
|
||||||
|
{
|
||||||
|
m_watch_only = true;
|
||||||
|
m_watch_widget->setState(true);
|
||||||
|
}
|
||||||
|
m_record_widget->setVisible(!m_watch_only);
|
||||||
|
getWidget<LabelWidget>("record-race-text")->setVisible(!m_watch_only);
|
||||||
|
|
||||||
|
refreshMainScreen();
|
||||||
|
|
||||||
|
m_replay_id = ReplayPlay::get()->getReplayIdByUID(m_rd.m_replay_uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GUIEngine::EVENT_LET;
|
return GUIEngine::EVENT_LET;
|
||||||
@ -153,7 +325,18 @@ void GhostReplayInfoDialog::onUpdate(float dt)
|
|||||||
{
|
{
|
||||||
if (m_self_destroy)
|
if (m_self_destroy)
|
||||||
{
|
{
|
||||||
|
ModalDialog::clearWindow();
|
||||||
ModalDialog::dismiss();
|
ModalDialog::dismiss();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} // onUpdate
|
} // onUpdate
|
||||||
|
|
||||||
|
void GhostReplayInfoDialog::refreshMainScreen()
|
||||||
|
{
|
||||||
|
GhostReplaySelection::getInstance()->setCompare(m_compare_ghost);
|
||||||
|
GhostReplaySelection::getInstance()->setCompareReplayUid(m_compare_replay_uid);
|
||||||
|
|
||||||
|
// Refresh the list to have only compatible replays
|
||||||
|
dynamic_cast<GhostReplaySelection*>(GUIEngine::getCurrentScreen())
|
||||||
|
->refresh();
|
||||||
|
}
|
||||||
|
@ -20,9 +20,7 @@
|
|||||||
#define HEADER_GHOST_REPLAY_INFO_DIALOG_HPP
|
#define HEADER_GHOST_REPLAY_INFO_DIALOG_HPP
|
||||||
|
|
||||||
#include "guiengine/modaldialog.hpp"
|
#include "guiengine/modaldialog.hpp"
|
||||||
#include "guiengine/widgets/check_box_widget.hpp"
|
#include "guiengine/widgets.hpp"
|
||||||
#include "guiengine/widgets/icon_button_widget.hpp"
|
|
||||||
#include "guiengine/widgets/ribbon_widget.hpp"
|
|
||||||
#include "replay/replay_play.hpp"
|
#include "replay/replay_play.hpp"
|
||||||
|
|
||||||
/** \brief Dialog that allows a user to do action with ghost replay file
|
/** \brief Dialog that allows a user to do action with ghost replay file
|
||||||
@ -33,21 +31,32 @@ class GhostReplayInfoDialog : public GUIEngine::ModalDialog
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
bool m_self_destroy;
|
bool m_self_destroy;
|
||||||
|
|
||||||
bool m_record_race;
|
bool m_record_race;
|
||||||
bool m_watch_only;
|
bool m_watch_only;
|
||||||
|
bool m_compare_ghost;
|
||||||
|
|
||||||
|
unsigned int m_replay_id; // May be updated on list refreshes
|
||||||
|
|
||||||
|
uint64_t m_compare_replay_uid;
|
||||||
|
|
||||||
const unsigned int m_replay_id;
|
|
||||||
ReplayPlay::ReplayData m_rd;
|
ReplayPlay::ReplayData m_rd;
|
||||||
|
|
||||||
GUIEngine::RibbonWidget* m_action_widget;
|
GUIEngine::RibbonWidget* m_action_widget;
|
||||||
GUIEngine::IconButtonWidget* m_back_widget;
|
GUIEngine::IconButtonWidget* m_back_widget;
|
||||||
GUIEngine::CheckBoxWidget* m_record_widget;
|
GUIEngine::CheckBoxWidget* m_record_widget;
|
||||||
GUIEngine::CheckBoxWidget* m_watch_widget;
|
GUIEngine::CheckBoxWidget* m_watch_widget;
|
||||||
|
GUIEngine::CheckBoxWidget* m_compare_widget;
|
||||||
|
|
||||||
|
GUIEngine::ListWidget* m_replay_info_widget;
|
||||||
|
GUIEngine::IconButtonWidget* m_track_screenshot_widget;
|
||||||
|
|
||||||
|
void updateReplayDisplayedInfo();
|
||||||
|
void refreshMainScreen();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GhostReplayInfoDialog(unsigned int replay_id);
|
GhostReplayInfoDialog(unsigned int replay_id, uint64_t compare_replay_uid, bool compare_ghost);
|
||||||
~GhostReplayInfoDialog();
|
~GhostReplayInfoDialog();
|
||||||
|
|
||||||
GUIEngine::EventPropagation processEvent(const std::string& eventSource);
|
GUIEngine::EventPropagation processEvent(const std::string& eventSource);
|
||||||
|
@ -33,6 +33,8 @@ using namespace GUIEngine;
|
|||||||
GhostReplaySelection::GhostReplaySelection() : Screen("ghost_replay_selection.stkgui")
|
GhostReplaySelection::GhostReplaySelection() : Screen("ghost_replay_selection.stkgui")
|
||||||
{
|
{
|
||||||
m_sort_desc = true;
|
m_sort_desc = true;
|
||||||
|
m_is_comparing = false;
|
||||||
|
m_replay_to_compare_uid = 0;
|
||||||
} // GhostReplaySelection
|
} // GhostReplaySelection
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -45,11 +47,22 @@ GhostReplaySelection::~GhostReplaySelection()
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
/** Triggers a refresh of the replay file list.
|
/** Triggers a refresh of the replay file list.
|
||||||
*/
|
*/
|
||||||
void GhostReplaySelection::refresh(bool forced_update)
|
void GhostReplaySelection::refresh(bool forced_update, bool update_columns)
|
||||||
{
|
{
|
||||||
if (ReplayPlay::get()->getNumReplayFile() == 0 || forced_update)
|
if (ReplayPlay::get()->getNumReplayFile() == 0 || forced_update)
|
||||||
ReplayPlay::get()->loadAllReplayFile();
|
ReplayPlay::get()->loadAllReplayFile();
|
||||||
loadList();
|
loadList();
|
||||||
|
|
||||||
|
// Allow to disable a comparison, but not to start one
|
||||||
|
m_compare_toggle_widget->setVisible(m_is_comparing);
|
||||||
|
m_compare_toggle_widget->setState(m_is_comparing);
|
||||||
|
getWidget<LabelWidget>("compare-toggle-text")->setVisible(m_is_comparing);
|
||||||
|
|
||||||
|
if (update_columns)
|
||||||
|
{
|
||||||
|
m_replay_list_widget->clearColumns();
|
||||||
|
beforeAddingWidget();//Reload the columns used
|
||||||
|
}
|
||||||
} // refresh
|
} // refresh
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -60,10 +73,32 @@ void GhostReplaySelection::loadedFromFile()
|
|||||||
m_replay_list_widget = getWidget<GUIEngine::ListWidget>("replay_list");
|
m_replay_list_widget = getWidget<GUIEngine::ListWidget>("replay_list");
|
||||||
assert(m_replay_list_widget != NULL);
|
assert(m_replay_list_widget != NULL);
|
||||||
m_replay_list_widget->setColumnListener(this);
|
m_replay_list_widget->setColumnListener(this);
|
||||||
|
|
||||||
m_replay_difficulty_toggle_widget =
|
m_replay_difficulty_toggle_widget =
|
||||||
getWidget<GUIEngine::CheckBoxWidget>("replay_difficulty_toggle");
|
getWidget<GUIEngine::CheckBoxWidget>("replay_difficulty_toggle");
|
||||||
m_replay_difficulty_toggle_widget->setState(true);
|
m_replay_difficulty_toggle_widget->setState(/* default value */ true);
|
||||||
m_same_difficulty = m_replay_difficulty_toggle_widget->getState();
|
m_same_difficulty = m_replay_difficulty_toggle_widget->getState();
|
||||||
|
|
||||||
|
m_replay_version_toggle_widget =
|
||||||
|
getWidget<GUIEngine::CheckBoxWidget>("replay_version_toggle");
|
||||||
|
m_replay_version_toggle_widget->setState(/* default value */ true);
|
||||||
|
m_same_version = m_replay_version_toggle_widget->getState();
|
||||||
|
|
||||||
|
m_best_times_toggle_widget =
|
||||||
|
getWidget<GUIEngine::CheckBoxWidget>("best_times_toggle");
|
||||||
|
m_best_times_toggle_widget->setState(/* default value */ false);
|
||||||
|
m_best_times = m_best_times_toggle_widget->getState();
|
||||||
|
|
||||||
|
m_compare_toggle_widget =
|
||||||
|
getWidget<GUIEngine::CheckBoxWidget>("compare_toggle");
|
||||||
|
m_compare_toggle_widget->setState(/* default value */ false);
|
||||||
|
m_is_comparing = false;
|
||||||
|
m_compare_toggle_widget->setVisible(false);
|
||||||
|
getWidget<LabelWidget>("compare-toggle-text")->setVisible(false);
|
||||||
|
|
||||||
|
m_mode_tabs = getWidget<GUIEngine::RibbonWidget>("race_mode");
|
||||||
|
m_active_mode = RaceManager::MINOR_MODE_TIME_TRIAL;
|
||||||
|
m_active_mode_is_linear = true;
|
||||||
} // loadedFromFile
|
} // loadedFromFile
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -71,14 +106,20 @@ void GhostReplaySelection::loadedFromFile()
|
|||||||
*/
|
*/
|
||||||
void GhostReplaySelection::beforeAddingWidget()
|
void GhostReplaySelection::beforeAddingWidget()
|
||||||
{
|
{
|
||||||
m_replay_list_widget->clearColumns();
|
m_replay_list_widget->addColumn( _("Track"), 9 );
|
||||||
m_replay_list_widget->addColumn( _("Track"), 3 );
|
m_replay_list_widget->addColumn( _("Players"), 3);
|
||||||
m_replay_list_widget->addColumn( _("Players"), 1);
|
if (m_active_mode_is_linear)
|
||||||
m_replay_list_widget->addColumn( _("Reverse"), 1);
|
m_replay_list_widget->addColumn( _("Reverse"), 3);
|
||||||
m_replay_list_widget->addColumn( _("Difficulty"), 1);
|
if (!m_same_difficulty)
|
||||||
m_replay_list_widget->addColumn( _("Laps"), 1);
|
m_replay_list_widget->addColumn( _("Difficulty"), 4);
|
||||||
m_replay_list_widget->addColumn( _("Finish Time"), 1);
|
if (m_active_mode_is_linear)
|
||||||
m_replay_list_widget->addColumn( _("User"), 1);
|
m_replay_list_widget->addColumn( _("Laps"), 3);
|
||||||
|
m_replay_list_widget->addColumn( _("Time"), 4);
|
||||||
|
m_replay_list_widget->addColumn( _("User"), 5);
|
||||||
|
if (!m_same_version)
|
||||||
|
m_replay_list_widget->addColumn( _("Version"), 3);
|
||||||
|
|
||||||
|
m_replay_list_widget->createHeader();
|
||||||
} // beforeAddingWidget
|
} // beforeAddingWidget
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -86,7 +127,7 @@ void GhostReplaySelection::init()
|
|||||||
{
|
{
|
||||||
Screen::init();
|
Screen::init();
|
||||||
m_cur_difficulty = race_manager->getDifficulty();
|
m_cur_difficulty = race_manager->getDifficulty();
|
||||||
refresh(/*forced_update*/false);
|
refresh(/*reload replay files*/ false, /* update columns */ true);
|
||||||
} // init
|
} // init
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -97,36 +138,193 @@ void GhostReplaySelection::loadList()
|
|||||||
{
|
{
|
||||||
ReplayPlay::get()->sortReplay(m_sort_desc);
|
ReplayPlay::get()->sortReplay(m_sort_desc);
|
||||||
m_replay_list_widget->clear();
|
m_replay_list_widget->clear();
|
||||||
|
|
||||||
|
if (ReplayPlay::get()->getNumReplayFile() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_best_times)
|
||||||
|
{
|
||||||
|
//First of all, clear the best time index
|
||||||
|
m_best_times_index.clear();
|
||||||
|
|
||||||
|
// This is in O(N*M) ; N being the number of replay files
|
||||||
|
// and M the number of different configuration (which is
|
||||||
|
// the final size of the best times list).
|
||||||
|
// Each time has to be compared against the previous best times
|
||||||
|
// up until all are checked or a replay with the same configuration
|
||||||
|
// is found.
|
||||||
|
for (unsigned int i = 0; i < ReplayPlay::get()->getNumReplayFile() ; i++)
|
||||||
|
{
|
||||||
|
const ReplayPlay::ReplayData& rd = ReplayPlay::get()->getReplayData(i);
|
||||||
|
|
||||||
|
if (m_same_difficulty && m_cur_difficulty !=
|
||||||
|
(RaceManager::Difficulty)rd.m_difficulty)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
core::stringw current_version = STK_VERSION;
|
||||||
|
if (m_same_version && current_version != rd.m_stk_version)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Track* track = track_manager->getTrack(rd.m_track_name);
|
||||||
|
|
||||||
|
if (track == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If no other replay with the same configuration is found in the index
|
||||||
|
// this is the best time
|
||||||
|
bool is_best_time = true;
|
||||||
|
bool replace_old_best = false;
|
||||||
|
// This is the position inside the best_times_index itself,
|
||||||
|
// not inside the full list of replay files
|
||||||
|
unsigned int index_old_best = 0;
|
||||||
|
|
||||||
|
for (unsigned int j = 0; j < m_best_times_index.size() ; j++)
|
||||||
|
{
|
||||||
|
// The replay in the best times index conform to the version and difficulty settings,
|
||||||
|
// no need to check this again.
|
||||||
|
const ReplayPlay::ReplayData& bt = ReplayPlay::get()->getReplayData(m_best_times_index[j]);
|
||||||
|
|
||||||
|
// If it's not the same track, check further in the index
|
||||||
|
if (rd.m_track_name != bt.m_track_name)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If it's not the same difficulty, check further in the index
|
||||||
|
if (rd.m_difficulty != bt.m_difficulty)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If it's not the same direction, check further in the index
|
||||||
|
if (rd.m_reverse != bt.m_reverse)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If it's not the same lap numbers, check further in the index
|
||||||
|
if (rd.m_laps != bt.m_laps)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// The replay data have the same properties, compare the times
|
||||||
|
if (rd.m_min_time < bt.m_min_time)
|
||||||
|
{
|
||||||
|
replace_old_best = true;
|
||||||
|
index_old_best = j;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
is_best_time = false;
|
||||||
|
|
||||||
|
//No need to compare against other best times
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_best_time)
|
||||||
|
{
|
||||||
|
// Update the value
|
||||||
|
if (replace_old_best)
|
||||||
|
{
|
||||||
|
m_best_times_index[index_old_best] = i;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_best_times_index.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the best times index for faster lookup
|
||||||
|
if (m_best_times && m_best_times_index.size() > 1)
|
||||||
|
{
|
||||||
|
// std::sort sorts by default in ascending order (0,1,2...)
|
||||||
|
std::sort (m_best_times_index.begin(), m_best_times_index.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int best_index = 0;
|
||||||
|
|
||||||
|
// getReplayIdByUID will send 0 if the UID is incorrect,
|
||||||
|
// and m_is_comparing will be false if it is incorrect,
|
||||||
|
// so it always work
|
||||||
|
unsigned int compare_index = ReplayPlay::get()->getReplayIdByUID(m_replay_to_compare_uid);
|
||||||
|
const ReplayPlay::ReplayData& rd_compare = ReplayPlay::get()->getReplayData(compare_index);
|
||||||
|
|
||||||
for (unsigned int i = 0; i < ReplayPlay::get()->getNumReplayFile() ; i++)
|
for (unsigned int i = 0; i < ReplayPlay::get()->getNumReplayFile() ; i++)
|
||||||
{
|
{
|
||||||
|
if (m_best_times)
|
||||||
|
{
|
||||||
|
// All best times have already been added
|
||||||
|
if (best_index + 1 > m_best_times_index.size())
|
||||||
|
break;
|
||||||
|
// This works because the best times index is already sorted
|
||||||
|
else if (m_best_times_index[best_index] == i)
|
||||||
|
best_index++;
|
||||||
|
// There are still best times to display
|
||||||
|
// The current i don't correspond to a best time
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const ReplayPlay::ReplayData& rd = ReplayPlay::get()->getReplayData(i);
|
const ReplayPlay::ReplayData& rd = ReplayPlay::get()->getReplayData(i);
|
||||||
|
|
||||||
if (m_same_difficulty && m_cur_difficulty !=
|
if (m_same_difficulty && m_cur_difficulty !=
|
||||||
(RaceManager::Difficulty)rd.m_difficulty)
|
(RaceManager::Difficulty)rd.m_difficulty)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
core::stringw current_version = STK_VERSION;
|
||||||
|
if (m_same_version && current_version != rd.m_stk_version)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Only display replays comparable with the replay selected for comparison
|
||||||
|
if (m_is_comparing)
|
||||||
|
{
|
||||||
|
// If it's not the same track, check further in the index
|
||||||
|
if (rd.m_track_name != rd_compare.m_track_name)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If it's not the same direction, check further in the index
|
||||||
|
if (rd.m_reverse != rd_compare.m_reverse)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If it's not the same lap numbers, check further in the index
|
||||||
|
if (rd.m_laps != rd_compare.m_laps)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Don't compare a replay with itself
|
||||||
|
if (compare_index == i)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Only display replays matching the current mode
|
||||||
|
if (m_active_mode == RaceManager::MINOR_MODE_TIME_TRIAL &&
|
||||||
|
rd.m_minor_mode != "time-trial")
|
||||||
|
continue;
|
||||||
|
else if (m_active_mode == RaceManager::MINOR_MODE_EASTER_EGG &&
|
||||||
|
rd.m_minor_mode != "egg-hunt")
|
||||||
|
continue;
|
||||||
|
|
||||||
Track* track = track_manager->getTrack(rd.m_track_name);
|
Track* track = track_manager->getTrack(rd.m_track_name);
|
||||||
|
|
||||||
if (track == NULL)
|
if (track == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::vector<GUIEngine::ListWidget::ListCell> row;
|
std::vector<GUIEngine::ListWidget::ListCell> row;
|
||||||
|
//The third argument should match the numbers used in beforeAddingWidget
|
||||||
row.push_back(GUIEngine::ListWidget::ListCell
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
(translations->fribidize(track->getName()) , -1, 3));
|
(translations->fribidize(track->getName()) , -1, 9));
|
||||||
row.push_back(GUIEngine::ListWidget::ListCell
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
(StringUtils::toWString(rd.m_kart_list.size()), -1, 1, true));
|
(StringUtils::toWString(rd.m_kart_list.size()), -1, 3, true));
|
||||||
|
if (m_active_mode_is_linear)
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(rd.m_reverse ? _("Yes") : _("No"), -1, 3, true));
|
||||||
|
if (!m_same_difficulty)
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(race_manager->
|
||||||
|
getDifficultyName((RaceManager::Difficulty) rd.m_difficulty),
|
||||||
|
-1, 4, true));
|
||||||
|
if (m_active_mode_is_linear)
|
||||||
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
|
(StringUtils::toWString(rd.m_laps), -1, 3, true));
|
||||||
row.push_back(GUIEngine::ListWidget::ListCell
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
(rd.m_reverse ? _("Yes") : _("No"), -1, 1, true));
|
(StringUtils::toWString(rd.m_min_time) + L"s", -1, 4, true));
|
||||||
row.push_back(GUIEngine::ListWidget::ListCell
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
(rd.m_difficulty == 3 ? _("SuperTux") : rd.m_difficulty == 2 ?
|
(rd.m_user_name.empty() ? " " : rd.m_user_name, -1, 5, true));
|
||||||
_("Expert") : rd.m_difficulty == 1 ?
|
if (!m_same_version)
|
||||||
_("Intermediate") : _("Novice") , -1, 1, true));
|
row.push_back(GUIEngine::ListWidget::ListCell
|
||||||
row.push_back(GUIEngine::ListWidget::ListCell
|
(rd.m_stk_version.empty() ? " " : rd.m_stk_version, -1, 3, true));
|
||||||
(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.empty() ? " " : rd.m_user_name, -1, 1, true));
|
|
||||||
m_replay_list_widget->addItem(StringUtils::toString(i), row);
|
m_replay_list_widget->addItem(StringUtils::toString(i), row);
|
||||||
}
|
}
|
||||||
} // loadList
|
} // loadList
|
||||||
@ -156,17 +354,48 @@ void GhostReplaySelection::eventCallback(GUIEngine::Widget* widget,
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new GhostReplayInfoDialog(selected_index);
|
|
||||||
|
new GhostReplayInfoDialog(selected_index, m_replay_to_compare_uid, m_is_comparing);
|
||||||
} // click on replay file
|
} // click on replay file
|
||||||
|
else if (name == "race_mode")
|
||||||
|
{
|
||||||
|
std::string selection = ((RibbonWidget*)widget)->getSelectionIDString(PLAYER_ID_GAME_MASTER);
|
||||||
|
|
||||||
|
if (selection == "tab_time_trial")
|
||||||
|
m_active_mode = RaceManager::MINOR_MODE_TIME_TRIAL;
|
||||||
|
else if (selection == "tab_egg_hunt")
|
||||||
|
m_active_mode = RaceManager::MINOR_MODE_EASTER_EGG;
|
||||||
|
|
||||||
|
m_active_mode_is_linear = race_manager->isLinearRaceMode(m_active_mode);
|
||||||
|
m_is_comparing = false;
|
||||||
|
m_compare_toggle_widget->setState(false);
|
||||||
|
refresh(/*reload replay files*/ false, /* update columns */ true);
|
||||||
|
}
|
||||||
else if (name == "record-ghost")
|
else if (name == "record-ghost")
|
||||||
{
|
{
|
||||||
race_manager->setRecordRace(true);
|
race_manager->setRecordRace(true);
|
||||||
|
race_manager->setMinorMode(m_active_mode);
|
||||||
TracksScreen::getInstance()->push();
|
TracksScreen::getInstance()->push();
|
||||||
}
|
}
|
||||||
else if (name == "replay_difficulty_toggle")
|
else if (name == "replay_difficulty_toggle")
|
||||||
{
|
{
|
||||||
m_same_difficulty = m_replay_difficulty_toggle_widget->getState();
|
m_same_difficulty = m_replay_difficulty_toggle_widget->getState();
|
||||||
refresh(/*forced_update*/false);
|
refresh(/*reload replay files*/ false, /* update columns */ true);
|
||||||
|
}
|
||||||
|
else if (name == "replay_version_toggle")
|
||||||
|
{
|
||||||
|
m_same_version = m_replay_version_toggle_widget->getState();
|
||||||
|
refresh(/*reload replay files*/ false, /* update columns */ true);
|
||||||
|
}
|
||||||
|
else if (name == "best_times_toggle")
|
||||||
|
{
|
||||||
|
m_best_times = m_best_times_toggle_widget->getState();
|
||||||
|
refresh(/*reload replay files*/ false);
|
||||||
|
}
|
||||||
|
else if (name == "compare_toggle")
|
||||||
|
{
|
||||||
|
m_is_comparing = m_compare_toggle_widget->getState();
|
||||||
|
refresh(/*reload replay files*/ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // eventCallback
|
} // eventCallback
|
||||||
@ -197,33 +426,39 @@ void GhostReplaySelection::onConfirm()
|
|||||||
*/
|
*/
|
||||||
void GhostReplaySelection::onColumnClicked(int column_id)
|
void GhostReplaySelection::onColumnClicked(int column_id)
|
||||||
{
|
{
|
||||||
switch (column_id)
|
int diff_difficulty = m_same_difficulty ? 1 : 0;
|
||||||
{
|
int diff_linear = m_active_mode_is_linear ? 0 : 1;
|
||||||
case 0:
|
|
||||||
ReplayPlay::setSortOrder(ReplayPlay::SO_TRACK);
|
if (column_id >= 2)
|
||||||
break;
|
column_id += diff_linear;
|
||||||
case 1:
|
|
||||||
ReplayPlay::setSortOrder(ReplayPlay::SO_KART_NUM);
|
if (column_id >= 3)
|
||||||
break;
|
column_id += diff_difficulty;
|
||||||
case 2:
|
|
||||||
ReplayPlay::setSortOrder(ReplayPlay::SO_REV);
|
if (column_id >= 4)
|
||||||
break;
|
column_id += diff_linear;
|
||||||
case 3:
|
|
||||||
ReplayPlay::setSortOrder(ReplayPlay::SO_DIFF);
|
if (column_id == 0)
|
||||||
break;
|
ReplayPlay::setSortOrder(ReplayPlay::SO_TRACK);
|
||||||
case 4:
|
else if (column_id == 1)
|
||||||
ReplayPlay::setSortOrder(ReplayPlay::SO_LAPS);
|
ReplayPlay::setSortOrder(ReplayPlay::SO_KART_NUM);
|
||||||
break;
|
else if (column_id == 2)
|
||||||
case 5:
|
ReplayPlay::setSortOrder(ReplayPlay::SO_REV);
|
||||||
ReplayPlay::setSortOrder(ReplayPlay::SO_TIME);
|
else if (column_id == 3)
|
||||||
break;
|
ReplayPlay::setSortOrder(ReplayPlay::SO_DIFF);
|
||||||
case 6:
|
else if (column_id == 4)
|
||||||
ReplayPlay::setSortOrder(ReplayPlay::SO_USER);
|
ReplayPlay::setSortOrder(ReplayPlay::SO_LAPS);
|
||||||
break;
|
else if (column_id == 5)
|
||||||
default:
|
ReplayPlay::setSortOrder(ReplayPlay::SO_TIME);
|
||||||
assert(0);
|
else if (column_id == 6)
|
||||||
break;
|
ReplayPlay::setSortOrder(ReplayPlay::SO_USER);
|
||||||
} // switch
|
else if (column_id == 7)
|
||||||
|
ReplayPlay::setSortOrder(ReplayPlay::SO_VERSION);
|
||||||
|
else
|
||||||
|
assert(0);
|
||||||
|
|
||||||
|
printf("column_id is %d\n", column_id);
|
||||||
|
|
||||||
/** \brief Toggle the sort order after column click **/
|
/** \brief Toggle the sort order after column click **/
|
||||||
m_sort_desc = !m_sort_desc;
|
m_sort_desc = !m_sort_desc;
|
||||||
loadList();
|
loadList();
|
||||||
|
@ -44,20 +44,41 @@ private:
|
|||||||
|
|
||||||
GUIEngine::ListWidget* m_replay_list_widget;
|
GUIEngine::ListWidget* m_replay_list_widget;
|
||||||
GUIEngine::CheckBoxWidget* m_replay_difficulty_toggle_widget;
|
GUIEngine::CheckBoxWidget* m_replay_difficulty_toggle_widget;
|
||||||
|
GUIEngine::CheckBoxWidget* m_replay_version_toggle_widget;
|
||||||
|
GUIEngine::CheckBoxWidget* m_best_times_toggle_widget;
|
||||||
|
GUIEngine::CheckBoxWidget* m_compare_toggle_widget;
|
||||||
|
GUIEngine::RibbonWidget* m_mode_tabs;
|
||||||
RaceManager::Difficulty m_cur_difficulty;
|
RaceManager::Difficulty m_cur_difficulty;
|
||||||
std::string m_file_to_be_deleted;
|
std::string m_file_to_be_deleted;
|
||||||
|
std::vector<unsigned int> m_best_times_index;
|
||||||
bool m_same_difficulty;
|
bool m_same_difficulty;
|
||||||
|
bool m_same_version;
|
||||||
|
bool m_best_times;
|
||||||
bool m_sort_desc;
|
bool m_sort_desc;
|
||||||
|
bool m_is_comparing;
|
||||||
|
bool m_active_mode_is_linear;
|
||||||
|
RaceManager::MinorRaceModeType m_active_mode;
|
||||||
|
// The index id of a replay file can change with sorting, etc.
|
||||||
|
// Using the UID guarantees exact matchess
|
||||||
|
uint64_t m_replay_to_compare_uid;
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void refresh(bool forced_update = true);
|
void setCompareReplayUid(uint64_t uid) { m_replay_to_compare_uid = uid; }
|
||||||
|
void setCompare(bool compare) { m_is_comparing = compare; }
|
||||||
|
|
||||||
|
void refresh(bool forced_update = true, bool update_columns = false);
|
||||||
|
|
||||||
/** Load the addons into the main list.*/
|
/** Load the addons into the main list.*/
|
||||||
void loadList();
|
void loadList();
|
||||||
|
|
||||||
void onDeleteReplay(std::string& filename);
|
void onDeleteReplay(std::string& filename);
|
||||||
|
|
||||||
|
const RaceManager::MinorRaceModeType getActiveMode() { return m_active_mode; }
|
||||||
|
|
||||||
|
const bool isActiveModeLinear() { return m_active_mode_is_linear; }
|
||||||
|
|
||||||
/** \brief implement callback from parent class GUIEngine::Screen */
|
/** \brief implement callback from parent class GUIEngine::Screen */
|
||||||
virtual void loadedFromFile() OVERRIDE;
|
virtual void loadedFromFile() OVERRIDE;
|
||||||
|
|
||||||
|
@ -71,10 +71,19 @@ RaceGUI::RaceGUI()
|
|||||||
// Determine maximum length of the rank/lap text, in order to
|
// Determine maximum length of the rank/lap text, in order to
|
||||||
// align those texts properly on the right side of the viewport.
|
// align those texts properly on the right side of the viewport.
|
||||||
gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
|
gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
|
||||||
core::dimension2du area = font->getDimension(L"99:99:99");
|
core::dimension2du area = font->getDimension(L"99:99.99");
|
||||||
m_timer_width = area.Width;
|
m_timer_width = area.Width;
|
||||||
m_font_height = area.Height;
|
m_font_height = area.Height;
|
||||||
|
|
||||||
|
area = font->getDimension(L"99.999");
|
||||||
|
m_small_precise_timer_width = area.Width;
|
||||||
|
|
||||||
|
area = font->getDimension(L"99:99.999");
|
||||||
|
m_big_precise_timer_width = area.Width;
|
||||||
|
|
||||||
|
area = font->getDimension(L"-");
|
||||||
|
m_negative_timer_additional_width = area.Width;
|
||||||
|
|
||||||
if (race_manager->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER ||
|
if (race_manager->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER ||
|
||||||
race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
|
race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
|
||||||
race_manager->getNumLaps() > 9)
|
race_manager->getNumLaps() > 9)
|
||||||
@ -243,6 +252,11 @@ void RaceGUI::renderGlobal(float dt)
|
|||||||
if (world->getPhase()<WorldStatus::DELAY_FINISH_PHASE)
|
if (world->getPhase()<WorldStatus::DELAY_FINISH_PHASE)
|
||||||
drawGlobalTimer();
|
drawGlobalTimer();
|
||||||
|
|
||||||
|
if (race_manager->isLinearRaceMode() &&
|
||||||
|
race_manager->hasGhostKarts() &&
|
||||||
|
race_manager->getNumberOfKarts() >= 2 )
|
||||||
|
drawLiveDifference();
|
||||||
|
|
||||||
if(world->getPhase() == WorldStatus::GO_PHASE ||
|
if(world->getPhase() == WorldStatus::GO_PHASE ||
|
||||||
world->getPhase() == WorldStatus::MUSIC_PHASE)
|
world->getPhase() == WorldStatus::MUSIC_PHASE)
|
||||||
{
|
{
|
||||||
@ -349,7 +363,7 @@ void RaceGUI::drawScores()
|
|||||||
} // drawScores
|
} // drawScores
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Displays the racing time on the screen.s
|
/** Displays the racing time on the screen.
|
||||||
*/
|
*/
|
||||||
void RaceGUI::drawGlobalTimer()
|
void RaceGUI::drawGlobalTimer()
|
||||||
{
|
{
|
||||||
@ -392,8 +406,10 @@ void RaceGUI::drawGlobalTimer()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
core::rect<s32> pos(irr_driver->getActualScreenSize().Width - dist_from_right, 30,
|
core::rect<s32> pos(irr_driver->getActualScreenSize().Width - dist_from_right,
|
||||||
irr_driver->getActualScreenSize().Width , 50);
|
irr_driver->getActualScreenSize().Height*2/100,
|
||||||
|
irr_driver->getActualScreenSize().Width,
|
||||||
|
irr_driver->getActualScreenSize().Height*6/100);
|
||||||
|
|
||||||
// special case : when 3 players play, use available 4th space for such things
|
// special case : when 3 players play, use available 4th space for such things
|
||||||
if (race_manager->getIfEmptyScreenSpaceExists())
|
if (race_manager->getIfEmptyScreenSpaceExists())
|
||||||
@ -411,6 +427,70 @@ void RaceGUI::drawGlobalTimer()
|
|||||||
|
|
||||||
} // drawGlobalTimer
|
} // drawGlobalTimer
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
/** Displays the live difference with a ghost on screen.
|
||||||
|
*/
|
||||||
|
void RaceGUI::drawLiveDifference()
|
||||||
|
{
|
||||||
|
assert(World::getWorld() != NULL);
|
||||||
|
|
||||||
|
if (!World::getWorld()->shouldDrawTimer())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LinearWorld *linearworld = dynamic_cast<LinearWorld*>(World::getWorld());
|
||||||
|
assert(linearworld != NULL);
|
||||||
|
|
||||||
|
// Don't display the live difference timer if its time is wrong
|
||||||
|
// (before crossing the start line at start or after crossing it at end)
|
||||||
|
if (!linearworld->hasValidTimeDifference())
|
||||||
|
return;
|
||||||
|
|
||||||
|
float live_difference = linearworld->getLiveTimeDifference();
|
||||||
|
|
||||||
|
int timer_width = m_small_precise_timer_width;
|
||||||
|
|
||||||
|
// 59.9995 is the smallest number of seconds that could get rounded to 1 minute
|
||||||
|
// when rounding at the closest ms
|
||||||
|
if (fabsf(live_difference) >= 59.9995f)
|
||||||
|
timer_width = m_big_precise_timer_width;
|
||||||
|
|
||||||
|
if (live_difference < 0.0f)
|
||||||
|
timer_width += m_negative_timer_additional_width;
|
||||||
|
|
||||||
|
core::stringw sw;
|
||||||
|
video::SColor time_color;
|
||||||
|
|
||||||
|
// Change color depending on value
|
||||||
|
if (live_difference > 1.0f)
|
||||||
|
time_color = video::SColor(255, 255, 0, 0);
|
||||||
|
else if (live_difference > 0.0f)
|
||||||
|
time_color = video::SColor(255, 255, 160, 0);
|
||||||
|
else if (live_difference > -1.0f)
|
||||||
|
time_color = video::SColor(255, 160, 255, 0);
|
||||||
|
else
|
||||||
|
time_color = video::SColor(255, 0, 255, 0);
|
||||||
|
|
||||||
|
int dist_from_right = 10 + timer_width;
|
||||||
|
|
||||||
|
sw = core::stringw (StringUtils::timeToString(live_difference,3,
|
||||||
|
/* display_minutes_if_zero */ false).c_str() );
|
||||||
|
|
||||||
|
core::rect<s32> pos(irr_driver->getActualScreenSize().Width - dist_from_right,
|
||||||
|
irr_driver->getActualScreenSize().Height*7/100,
|
||||||
|
irr_driver->getActualScreenSize().Width,
|
||||||
|
irr_driver->getActualScreenSize().Height*11/100);
|
||||||
|
|
||||||
|
gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
|
||||||
|
font->setShadow(video::SColor(255, 128, 0, 0));
|
||||||
|
font->setScale(1.0f);
|
||||||
|
font->draw(sw.c_str(), pos, time_color, false, false, NULL,
|
||||||
|
true /* ignore RTL */);
|
||||||
|
|
||||||
|
} // drawLiveDifference
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Draws the mini map and the position of all karts on it.
|
/** Draws the mini map and the position of all karts on it.
|
||||||
*/
|
*/
|
||||||
|
@ -78,6 +78,16 @@ private:
|
|||||||
/** Maximum string length for the timer */
|
/** Maximum string length for the timer */
|
||||||
int m_timer_width;
|
int m_timer_width;
|
||||||
|
|
||||||
|
/** Maximum string length for a small precise timer
|
||||||
|
* (like the live difference timer under a minute) */
|
||||||
|
int m_small_precise_timer_width;
|
||||||
|
|
||||||
|
/** Maximum string length for a big precise timer
|
||||||
|
* (like the live difference timer over a minute) */
|
||||||
|
int m_big_precise_timer_width;
|
||||||
|
|
||||||
|
int m_negative_timer_additional_width;
|
||||||
|
|
||||||
/** Height of the digit font. */
|
/** Height of the digit font. */
|
||||||
int m_font_height;
|
int m_font_height;
|
||||||
|
|
||||||
@ -120,6 +130,7 @@ private:
|
|||||||
/** Display items that are shown once only (for all karts). */
|
/** Display items that are shown once only (for all karts). */
|
||||||
void drawGlobalMiniMap ();
|
void drawGlobalMiniMap ();
|
||||||
void drawGlobalTimer ();
|
void drawGlobalTimer ();
|
||||||
|
void drawLiveDifference ();
|
||||||
void drawScores();
|
void drawScores();
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,6 +49,8 @@
|
|||||||
#include "network/stk_host.hpp"
|
#include "network/stk_host.hpp"
|
||||||
#include "network/protocols/client_lobby.hpp"
|
#include "network/protocols/client_lobby.hpp"
|
||||||
#include "race/highscores.hpp"
|
#include "race/highscores.hpp"
|
||||||
|
#include "replay/replay_play.hpp"
|
||||||
|
#include "replay/replay_recorder.hpp"
|
||||||
#include "scriptengine/property_animator.hpp"
|
#include "scriptengine/property_animator.hpp"
|
||||||
#include "states_screens/feature_unlocked.hpp"
|
#include "states_screens/feature_unlocked.hpp"
|
||||||
#include "states_screens/main_menu_screen.hpp"
|
#include "states_screens/main_menu_screen.hpp"
|
||||||
@ -224,7 +226,10 @@ void RaceResultGUI::enableAllButtons()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
top->setText(_("Setup New Race"));
|
if (race_manager->isRecordingRace())
|
||||||
|
top->setText(_("Race against the new ghost replay"));
|
||||||
|
else
|
||||||
|
top->setText(_("Setup New Race"));
|
||||||
top->setVisible(true);
|
top->setVisible(true);
|
||||||
bottom->setText(_("Back to the menu"));
|
bottom->setText(_("Back to the menu"));
|
||||||
}
|
}
|
||||||
@ -385,6 +390,12 @@ void RaceResultGUI::eventCallback(GUIEngine::Widget* widget,
|
|||||||
StateManager::get()->popMenu();
|
StateManager::get()->popMenu();
|
||||||
if (name == "top") // Setup new race
|
if (name == "top") // Setup new race
|
||||||
{
|
{
|
||||||
|
// Save current race data for race against new ghost
|
||||||
|
std::string track_name = race_manager->getTrackName();
|
||||||
|
int laps = race_manager->getNumLaps();
|
||||||
|
bool reverse = race_manager->getReverseTrack();
|
||||||
|
bool new_ghost_race = race_manager->isRecordingRace();
|
||||||
|
|
||||||
race_manager->exitRace();
|
race_manager->exitRace();
|
||||||
race_manager->setAIKartOverride("");
|
race_manager->setAIKartOverride("");
|
||||||
|
|
||||||
@ -394,6 +405,24 @@ void RaceResultGUI::eventCallback(GUIEngine::Widget* widget,
|
|||||||
StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance());
|
StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance());
|
||||||
OverWorld::enterOverWorld();
|
OverWorld::enterOverWorld();
|
||||||
}
|
}
|
||||||
|
// Special case : race against a newly saved ghost
|
||||||
|
else if (new_ghost_race)
|
||||||
|
{
|
||||||
|
ReplayPlay::get()->loadAllReplayFile();
|
||||||
|
unsigned long long int last_uid = ReplayRecorder::get()->getLastUID();
|
||||||
|
ReplayPlay::get()->setReplayFileByUID(last_uid);
|
||||||
|
|
||||||
|
race_manager->setRecordRace(true);
|
||||||
|
race_manager->setRaceGhostKarts(true);
|
||||||
|
|
||||||
|
race_manager->setNumKarts(race_manager->getNumLocalPlayers());
|
||||||
|
|
||||||
|
// Disable accidentally unlocking of a challenge
|
||||||
|
PlayerManager::getCurrentPlayer()->setCurrentChallenge("");
|
||||||
|
|
||||||
|
race_manager->setReverseTrack(reverse);
|
||||||
|
race_manager->startSingleRace(track_name, laps, false);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Screen* newStack[] = { MainMenuScreen::getInstance(),
|
Screen* newStack[] = { MainMenuScreen::getInstance(),
|
||||||
|
@ -499,37 +499,117 @@ namespace StringUtils
|
|||||||
} // ticksTimeToString(ticks)
|
} // ticksTimeToString(ticks)
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Converts a time in seconds into a string of the form mm:ss:hh (minutes,
|
/** Converts a time in seconds into a string of the form mm:ss.hhh (minutes,
|
||||||
* seconds, 1/100 seconds.
|
* seconds, milliseconds)
|
||||||
* \param time Time in seconds.
|
* \param time Time in seconds.
|
||||||
|
* \param precision The number of seconds decimals - 3 to display ms, default 2
|
||||||
*/
|
*/
|
||||||
std::string timeToString(float time)
|
std::string timeToString(float time, unsigned int precision, bool display_minutes_if_zero)
|
||||||
{
|
{
|
||||||
int int_time = (int)(time*100.0f+0.5f);
|
//Time more detailed than ms are mostly meaningless
|
||||||
|
if (precision > 3)
|
||||||
|
precision = 3;
|
||||||
|
|
||||||
|
int int_time;
|
||||||
|
int precision_power = 1;
|
||||||
|
|
||||||
|
for (unsigned int i=0;i<precision;i++)
|
||||||
|
{
|
||||||
|
precision_power *=10;
|
||||||
|
}
|
||||||
|
|
||||||
|
float fprecision_power = (float) precision_power;
|
||||||
|
|
||||||
|
bool negative_time = (time < 0.0f);
|
||||||
|
|
||||||
|
// If the time is negative, make it positve
|
||||||
|
// And add a "-" later
|
||||||
|
if (negative_time) time *= -1.0f;
|
||||||
|
|
||||||
|
// cast to int truncates the value,
|
||||||
|
// so add 0.5f to get the closest int
|
||||||
|
|
||||||
|
int_time = (int)(time*fprecision_power+0.5f);
|
||||||
|
|
||||||
// Avoid problems if time is negative or way too large (which
|
// Avoid problems if time is negative or way too large (which
|
||||||
// should only happen if something is broken in a track elsewhere,
|
// should only happen if something is broken in a track elsewhere,
|
||||||
// and an incorrect finishing time is estimated.
|
// and an incorrect finishing time is estimated.
|
||||||
if(int_time<0)
|
if(int_time<0)
|
||||||
return std::string("00:00:00");
|
{
|
||||||
else if(int_time >= 10000*60) // up to 99:59.99
|
std::string final_append;
|
||||||
return std::string("99:59:99");
|
if (precision == 3)
|
||||||
|
final_append = ".000";
|
||||||
|
else if (precision == 2)
|
||||||
|
final_append = ".00";
|
||||||
|
else if (precision == 1)
|
||||||
|
final_append = ".0";
|
||||||
|
else
|
||||||
|
final_append = "";
|
||||||
|
// concatenate the strings with +
|
||||||
|
if (display_minutes_if_zero)
|
||||||
|
return (std::string("00:00") + final_append);
|
||||||
|
else
|
||||||
|
return (std::string("00") + final_append);
|
||||||
|
}
|
||||||
|
else if(int_time >= 100*60*precision_power) // up to 99:59.999
|
||||||
|
{
|
||||||
|
std::string final_append;
|
||||||
|
if (precision == 3)
|
||||||
|
final_append = ".999";
|
||||||
|
else if (precision == 2)
|
||||||
|
final_append = ".99";
|
||||||
|
else if (precision == 1)
|
||||||
|
final_append = ".9";
|
||||||
|
else
|
||||||
|
final_append = "";
|
||||||
|
// concatenate the strings with +
|
||||||
|
return (std::string("99:59") + final_append);
|
||||||
|
}
|
||||||
|
|
||||||
int min = int_time / 6000;
|
// Principle of the computation in pseudo-code
|
||||||
int sec = (int_time-min*6000)/100;
|
// 1) Divide by (current_time_unit_duration/next_smaller_unit_duration)
|
||||||
int hundredths = (int_time - min*6000-sec*100);
|
// (1 if no smaller)
|
||||||
// The proper c++ way would be:
|
// 2) Apply modulo (next_bigger_time_unit_duration/current_time_unit_duration)
|
||||||
// std::ostringstream s;
|
// (no modulo if no bigger)
|
||||||
// s<<std::setw(2)<<std::setfill(' ')<<min<<":"
|
int subseconds = int_time % precision_power;
|
||||||
// <<std::setw(2)<<std::setfill('0')<<sec<<":"
|
int_time = int_time/precision_power;
|
||||||
// <<std::setw(2)<<std::setfill(' ')<<hundredths;
|
int sec = int_time % 60;
|
||||||
// return s.str();
|
int_time = int_time/60;
|
||||||
// but that appears to be awfully complicated and slow, compared to
|
if (int_time >= 100) int_time = 99;
|
||||||
// which admittedly only works for min < 100000 - which is about 68
|
int min = int_time;
|
||||||
// days - good enough.
|
|
||||||
char s[12];
|
// Convert the times to string and add the missing zeroes if any
|
||||||
sprintf(s, "%02d:%02d:%02d", min, sec, hundredths);
|
|
||||||
return std::string(s);
|
std::string s_min = std::to_string(min);
|
||||||
|
if (min < 10)
|
||||||
|
s_min = std::string("0") + s_min;
|
||||||
|
std::string s_sec = std::to_string(sec);
|
||||||
|
if (sec < 10)
|
||||||
|
s_sec = std::string("0") + s_sec;
|
||||||
|
std::string s_subsec = std::to_string(subseconds);
|
||||||
|
|
||||||
|
// If subseconds is 0 ; it is already in the string,
|
||||||
|
// so skip one step
|
||||||
|
for (unsigned int i=1;i<precision;i++)
|
||||||
|
{
|
||||||
|
precision_power = precision_power/10;
|
||||||
|
if (subseconds < precision_power)
|
||||||
|
s_subsec = std::string("0") + s_subsec;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string s_neg = "";
|
||||||
|
|
||||||
|
if(negative_time)
|
||||||
|
s_neg = "-";
|
||||||
|
|
||||||
|
std::string s_min_and_sec = s_sec;
|
||||||
|
if (display_minutes_if_zero || min > 0)
|
||||||
|
s_min_and_sec = s_min + std::string(":") + s_sec;
|
||||||
|
|
||||||
|
if (precision == 0)
|
||||||
|
return (s_neg + s_min_and_sec);
|
||||||
|
else
|
||||||
|
return (s_neg + s_min_and_sec + std::string(".") + s_subsec);
|
||||||
} // timeToString
|
} // timeToString
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -46,7 +46,7 @@ namespace StringUtils
|
|||||||
|
|
||||||
bool notEmpty(const irr::core::stringw& input);
|
bool notEmpty(const irr::core::stringw& input);
|
||||||
std::string ticksTimeToString(int time);
|
std::string ticksTimeToString(int time);
|
||||||
std::string timeToString(float time);
|
std::string timeToString(float time, unsigned int precision=2, bool display_minutes_if_zero = true);
|
||||||
irr::core::stringw loadingDots(float interval = 0.5f, int max_dots = 3);
|
irr::core::stringw loadingDots(float interval = 0.5f, int max_dots = 3);
|
||||||
irr::core::stringw loadingDots(const wchar_t *s);
|
irr::core::stringw loadingDots(const wchar_t *s);
|
||||||
std::string toUpperCase(const std::string&);
|
std::string toUpperCase(const std::string&);
|
||||||
|
Loading…
Reference in New Issue
Block a user