commit
8da8390773
@ -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;
|
||||||
|
@ -80,7 +80,7 @@ public:
|
|||||||
/** \brief Get the players that are in the game
|
/** \brief Get the players that are in the game
|
||||||
* \return A vector containing pointers on the players profiles. */
|
* \return A vector containing pointers on the players profiles. */
|
||||||
std::vector<std::shared_ptr<NetworkPlayerProfile> >
|
std::vector<std::shared_ptr<NetworkPlayerProfile> >
|
||||||
getConnectedPlayers() const
|
getConnectedPlayers(bool same_index_for_disconnected = false) const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(m_players_mutex);
|
std::lock_guard<std::mutex> lock(m_players_mutex);
|
||||||
std::vector<std::shared_ptr<NetworkPlayerProfile> > players;
|
std::vector<std::shared_ptr<NetworkPlayerProfile> > players;
|
||||||
@ -88,6 +88,8 @@ public:
|
|||||||
{
|
{
|
||||||
if (auto player_connected = player_weak.lock())
|
if (auto player_connected = player_weak.lock())
|
||||||
players.push_back(player_connected);
|
players.push_back(player_connected);
|
||||||
|
else if (same_index_for_disconnected)
|
||||||
|
players.push_back(nullptr);
|
||||||
}
|
}
|
||||||
return players;
|
return players;
|
||||||
} // getConnectedPlayers
|
} // getConnectedPlayers
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
#include "network/rewind_info.hpp"
|
#include "network/rewind_info.hpp"
|
||||||
#include "physics/physics.hpp"
|
#include "physics/physics.hpp"
|
||||||
#include "race/history.hpp"
|
#include "race/history.hpp"
|
||||||
|
#include "tracks/track.hpp"
|
||||||
|
#include "tracks/track_object_manager.hpp"
|
||||||
#include "utils/log.hpp"
|
#include "utils/log.hpp"
|
||||||
#include "utils/profiler.hpp"
|
#include "utils/profiler.hpp"
|
||||||
|
|
||||||
@ -318,6 +320,8 @@ void RewindManager::playEventsTill(int world_ticks, int *ticks)
|
|||||||
void RewindManager::rewindTo(int rewind_ticks, int now_ticks)
|
void RewindManager::rewindTo(int rewind_ticks, int now_ticks)
|
||||||
{
|
{
|
||||||
assert(!m_is_rewinding);
|
assert(!m_is_rewinding);
|
||||||
|
// TODO Do it properly for track objects like soccer ball
|
||||||
|
Track::getCurrentTrack()->getTrackObjectManager()->removeForRewind();
|
||||||
bool is_history = history->replayHistory();
|
bool is_history = history->replayHistory();
|
||||||
history->setReplayHistory(false);
|
history->setReplayHistory(false);
|
||||||
|
|
||||||
@ -384,5 +388,6 @@ void RewindManager::rewindTo(int rewind_ticks, int now_ticks)
|
|||||||
}
|
}
|
||||||
|
|
||||||
history->setReplayHistory(is_history);
|
history->setReplayHistory(is_history);
|
||||||
|
Track::getCurrentTrack()->getTrackObjectManager()->addForRewind();
|
||||||
m_is_rewinding = false;
|
m_is_rewinding = false;
|
||||||
} // rewindTo
|
} // rewindTo
|
||||||
|
@ -206,6 +206,8 @@ public:
|
|||||||
const Material **material, btVector3 *normal,
|
const Material **material, btVector3 *normal,
|
||||||
bool interpolate_normal) const;
|
bool interpolate_normal) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isDynamic() const { return m_is_dynamic; }
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Returns the ID of this physical object. */
|
/** Returns the ID of this physical object. */
|
||||||
std::string getID() { return m_id; }
|
std::string getID() { return m_id; }
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -529,11 +529,12 @@ namespace Scripting
|
|||||||
m_time = time;
|
m_time = time;
|
||||||
m_callback_delegate = callback_delegate;
|
m_callback_delegate = callback_delegate;
|
||||||
|
|
||||||
// This may be not needed in future angelscript versions
|
#if ANGELSCRIPT_VERSION < 23300
|
||||||
if (strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY"))
|
if (strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY"))
|
||||||
{
|
{
|
||||||
callback_delegate->AddRef();
|
callback_delegate->AddRef();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -63,7 +63,6 @@ void CustomVideoSettingsDialog::beforeAddingWidgets()
|
|||||||
particles_effects->addLabel(_("Important only"));
|
particles_effects->addLabel(_("Important only"));
|
||||||
particles_effects->addLabel(_("Enabled"));
|
particles_effects->addLabel(_("Enabled"));
|
||||||
particles_effects->setValue(UserConfigParams::m_particles_effects);
|
particles_effects->setValue(UserConfigParams::m_particles_effects);
|
||||||
particles_effects->setMin(1);
|
|
||||||
|
|
||||||
SpinnerWidget* geometry_level = getWidget<SpinnerWidget>("geometry_detail");
|
SpinnerWidget* geometry_level = getWidget<SpinnerWidget>("geometry_detail");
|
||||||
//I18N: Geometry level disabled : lowest level, no details
|
//I18N: Geometry level disabled : lowest level, no details
|
||||||
|
@ -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(),
|
||||||
|
@ -535,6 +535,7 @@ void TrackObject::updateGraphics(float dt)
|
|||||||
// have been converted to use separate updateGraphics() calls.
|
// have been converted to use separate updateGraphics() calls.
|
||||||
|
|
||||||
if (m_physical_object) m_physical_object->updateGraphics(dt);
|
if (m_physical_object) m_physical_object->updateGraphics(dt);
|
||||||
|
if (m_animator) m_animator->update(dt);
|
||||||
|
|
||||||
} // update
|
} // update
|
||||||
|
|
||||||
@ -546,10 +547,8 @@ void TrackObject::updateGraphics(float dt)
|
|||||||
void TrackObject::update(float dt)
|
void TrackObject::update(float dt)
|
||||||
{
|
{
|
||||||
if (m_presentation) m_presentation->update(dt);
|
if (m_presentation) m_presentation->update(dt);
|
||||||
|
|
||||||
if (m_physical_object) m_physical_object->update(dt);
|
if (m_physical_object) m_physical_object->update(dt);
|
||||||
|
|
||||||
if (m_animator) m_animator->update(dt);
|
|
||||||
} // update
|
} // update
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -238,3 +238,25 @@ void TrackObjectManager::removeObject(TrackObject* obj)
|
|||||||
m_all_objects.remove(obj);
|
m_all_objects.remove(obj);
|
||||||
delete obj;
|
delete obj;
|
||||||
} // removeObject
|
} // removeObject
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
void TrackObjectManager::removeForRewind()
|
||||||
|
{
|
||||||
|
for (TrackObject* curr : m_all_objects)
|
||||||
|
{
|
||||||
|
if (curr->getPhysicalObject() &&
|
||||||
|
curr->getPhysicalObject()->isDynamic())
|
||||||
|
curr->getPhysicalObject()->removeBody();
|
||||||
|
}
|
||||||
|
} // removeForRewind
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
void TrackObjectManager::addForRewind()
|
||||||
|
{
|
||||||
|
for (TrackObject* curr : m_all_objects)
|
||||||
|
{
|
||||||
|
if (curr->getPhysicalObject() &&
|
||||||
|
curr->getPhysicalObject()->isDynamic())
|
||||||
|
curr->getPhysicalObject()->addBody();
|
||||||
|
}
|
||||||
|
} // addForRewind
|
||||||
|
@ -76,6 +76,8 @@ public:
|
|||||||
|
|
||||||
PtrVector<TrackObject>& getObjects() { return m_all_objects; }
|
PtrVector<TrackObject>& getObjects() { return m_all_objects; }
|
||||||
const PtrVector<TrackObject>& getObjects() const { return m_all_objects; }
|
const PtrVector<TrackObject>& getObjects() const { return m_all_objects; }
|
||||||
|
void removeForRewind();
|
||||||
|
void addForRewind();
|
||||||
|
|
||||||
}; // class TrackObjectManager
|
}; // class TrackObjectManager
|
||||||
|
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user