1) Cleaned up implementation of history function.
2) Improved history file format to collect control information (like steering etc) to allow replaying based on the physics (and not only replaying recorded kart positions). 3) Added some constants (time till finish, time for music credits) to the stk_config.dat file. 4) Made name of the menu background picture configureable in stk_config.dat file. git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/trunk/supertuxkart@2328 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
parent
6a9a943292
commit
167107fcbc
@ -10,6 +10,10 @@
|
||||
;; points 1st, 0 is least points
|
||||
;; 1st
|
||||
(title-music "main_theme.music")
|
||||
(menu-background "st_title_screen.rgb")
|
||||
(max-history 10000) ;; maximum number of history frames.
|
||||
(delay-finish-time 10) ;; delay till race results are displayed.
|
||||
(music-credit-time 10) ;; time for which the music credits are displayed.
|
||||
|
||||
;; Attachment related parameters
|
||||
;; -----------------------------
|
||||
|
@ -17,13 +17,9 @@
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
#ifndef HEADER_CONSTANTS_H
|
||||
#define HEADER_CONSTANTS_H
|
||||
#ifndef HEADER_CONSTANTS_HPP
|
||||
#define HEADER_CONSTANTS_HPP
|
||||
|
||||
|
||||
#define MAX_HISTORY 50000 /* number of history events */
|
||||
#define TIME_DELAY_TILL_FINISH 10.0f /* time after all player karts finish */
|
||||
#define TIME_MUSIC_DESCRIPTION 10.0f /* duration music description is displayed */
|
||||
/*
|
||||
All final units are in meters (or meters/sec or meters/sec^2)
|
||||
and degrees (or degrees/sec).
|
||||
|
@ -39,7 +39,7 @@ float Flyable::m_st_force_updown[COLLECT_MAX];
|
||||
btVector3 Flyable::m_st_extend[COLLECT_MAX];
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
Flyable::Flyable(Kart *kart, CollectableType type, float mass) : Moveable(false)
|
||||
Flyable::Flyable(Kart *kart, CollectableType type, float mass) : Moveable()
|
||||
{
|
||||
// get the appropriate data from the static fields
|
||||
m_speed = m_st_speed[type];
|
||||
|
223
src/history.cpp
223
src/history.cpp
@ -18,45 +18,136 @@
|
||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
#include "history.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "kart.hpp"
|
||||
#include "track.hpp"
|
||||
#include "race_manager.hpp"
|
||||
#include "modes/world.hpp"
|
||||
#include "network/network_manager.hpp"
|
||||
|
||||
History* history = 0;
|
||||
|
||||
void History::SetSize(int n)
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Initialises the history object and sets the mode to none.
|
||||
*/
|
||||
History::History()
|
||||
{
|
||||
m_size = n;
|
||||
m_all_deltas = new float[m_size];
|
||||
m_current = -1;
|
||||
m_wrapped = false;
|
||||
m_replay_mode = HISTORY_NONE;
|
||||
} // History
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void History::StoreDelta(float delta)
|
||||
/** Starts replay from the history file in the current directory.
|
||||
*/
|
||||
void History::startReplay()
|
||||
{
|
||||
this->m_current++;
|
||||
if(m_current>=m_size)
|
||||
Load();
|
||||
} // initReplay
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Initialise the history for a new recording. It especially allocates memory
|
||||
* to store the history.
|
||||
*/
|
||||
void History::initRecording()
|
||||
{
|
||||
allocateMemory(stk_config->m_max_history);
|
||||
m_current = -1;
|
||||
m_wrapped = false;
|
||||
m_size = 0;
|
||||
} // initRecording
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Allocates memory for the history. This is used when recording as well
|
||||
* as when replaying (since in replay the data is read into memory first).
|
||||
* \param number_of_frames Maximum number of frames to store.
|
||||
*/
|
||||
void History::allocateMemory(int number_of_frames)
|
||||
{
|
||||
m_all_deltas.resize (number_of_frames);
|
||||
m_all_controls.resize (number_of_frames*race_manager->getNumKarts());
|
||||
m_all_xyz.resize (number_of_frames*race_manager->getNumKarts());
|
||||
m_all_rotations.resize(number_of_frames*race_manager->getNumKarts());
|
||||
} // allocateMemory
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Depending on mode either saves the data for the current time step, or
|
||||
* replays the data.
|
||||
* /param dt Time step.
|
||||
*/
|
||||
void History::update(float dt)
|
||||
{
|
||||
if(m_replay_mode==HISTORY_NONE)
|
||||
updateSaving(dt);
|
||||
else
|
||||
updateReplay(dt);
|
||||
} // update
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Saves the current history.
|
||||
* \param dt Time step size.
|
||||
*/
|
||||
void History::updateSaving(float dt)
|
||||
{
|
||||
m_current++;
|
||||
if(m_current>=(int)m_all_deltas.size())
|
||||
{
|
||||
m_wrapped = true;
|
||||
m_current = 0;
|
||||
}
|
||||
m_all_deltas[m_current] = delta;
|
||||
} // StoreDT
|
||||
else
|
||||
{
|
||||
m_size ++;
|
||||
}
|
||||
m_all_deltas[m_current] = dt;
|
||||
|
||||
unsigned int max_num_karts = race_manager->getNumKarts();
|
||||
// n<=max_num_karts, e.g. if karts are eliminated
|
||||
unsigned int n = race_manager->getNumKarts();
|
||||
|
||||
for(unsigned int i=0; i<n; i++)
|
||||
{
|
||||
unsigned int index=m_current*max_num_karts+i;
|
||||
const Kart *kart = RaceManager::getKart(i);
|
||||
m_all_controls[index] = kart->getControls();
|
||||
m_all_xyz[index] = kart->getXYZ();
|
||||
m_all_rotations[index] = kart->getRotation();
|
||||
} // for i
|
||||
} // updateSaving
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
float History::GetNextDelta()
|
||||
/** Sets the kart position and controls to the recorded history value.
|
||||
* \param dt Time step size.
|
||||
*/
|
||||
void History::updateReplay(float dt)
|
||||
{
|
||||
m_current++;
|
||||
if(m_current>=m_size)
|
||||
if(m_current>=(int)m_all_deltas.size())
|
||||
{
|
||||
fprintf(stderr,"History: finished.\n");
|
||||
exit(-3);
|
||||
printf("Replay finished.\n");
|
||||
exit(2);
|
||||
}
|
||||
return m_all_deltas[m_current];
|
||||
} // GetNextDT
|
||||
unsigned int num_karts = race_manager->getNumKarts();
|
||||
for(unsigned k=0; k<num_karts; k++)
|
||||
{
|
||||
Kart *kart = RaceManager::getKart(k);
|
||||
unsigned int index=m_current*num_karts+k;
|
||||
if(m_replay_mode==HISTORY_POSITION)
|
||||
{
|
||||
kart->setXYZ(m_all_xyz[index]);
|
||||
kart->setRotation(m_all_rotations[index]);
|
||||
}
|
||||
else
|
||||
{
|
||||
kart->setControls(m_all_controls[index]);
|
||||
}
|
||||
}
|
||||
} // updateReplay
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Saves the history stored in the internal data structures into a file called
|
||||
* history.dat.
|
||||
*/
|
||||
void History::Save()
|
||||
{
|
||||
FILE *fd = fopen("history.dat","w");
|
||||
@ -72,12 +163,12 @@ void History::Save()
|
||||
int k;
|
||||
for(k=0; k<nKarts; k++)
|
||||
{
|
||||
fprintf(fd, "model %d: %s\n",k, RaceManager::getKart(k)->getName().c_str());
|
||||
fprintf(fd, "model %d: %s\n",k, RaceManager::getKart(k)->getIdent().c_str());
|
||||
}
|
||||
fprintf(fd, "size: %d\n", GetCount());
|
||||
fprintf(fd, "size: %d\n", m_size);
|
||||
|
||||
int j = m_wrapped ? m_current : 0;
|
||||
for(int i=0; i<GetCount(); i++)
|
||||
for(int i=0; i<m_size; i++)
|
||||
{
|
||||
fprintf(fd, "delta: %f\n",m_all_deltas[i]);
|
||||
j=(j+1)%m_size;
|
||||
@ -86,12 +177,19 @@ void History::Save()
|
||||
for(int k=0; k<nKarts; k++)
|
||||
{
|
||||
Kart* kart= RaceManager::getKart(k);
|
||||
char s[1024];
|
||||
j = m_wrapped ? m_current : 0;
|
||||
for(int i=0; i<GetCount(); i++)
|
||||
for(int i=0; i<m_size; i++)
|
||||
{
|
||||
kart->WriteHistory(s, k, j);
|
||||
fprintf(fd, "%s\n",s);
|
||||
// FIXME: kart number is not really necessary
|
||||
fprintf(fd, "%d %f %f %d %f %f %f %f %f %f %f\n",
|
||||
k,
|
||||
m_all_controls[j].lr,
|
||||
m_all_controls[j].accel,
|
||||
m_all_controls[j].getButtonsCompressed(),
|
||||
m_all_xyz[j].getX(), m_all_xyz[j].getY(),
|
||||
m_all_xyz[j].getZ(),
|
||||
m_all_rotations[j].getX(), m_all_rotations[j].getY(),
|
||||
m_all_rotations[j].getZ(), m_all_rotations[j].getW() );
|
||||
j=(j+1)%m_size;
|
||||
} // for i
|
||||
} // for k
|
||||
@ -100,13 +198,15 @@ void History::Save()
|
||||
} // Save
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Loads a history from history.dat in the current directory.
|
||||
*/
|
||||
void History::Load()
|
||||
{
|
||||
char s[1024], s1[1024];
|
||||
int n, numKarts;
|
||||
m_fd = fopen("history.dat","r");
|
||||
int n;
|
||||
FILE *fd = fopen("history.dat","r");
|
||||
|
||||
fgets(s, 1023, m_fd);
|
||||
fgets(s, 1023, fd);
|
||||
if(sscanf(s,"Version: %s",s1)!=1)
|
||||
{
|
||||
fprintf(stderr, "WARNING: no Version information found in history file.\n");
|
||||
@ -119,15 +219,16 @@ void History::Load()
|
||||
fprintf(stderr, " tuxracer version is '%s'\n",VERSION);
|
||||
}
|
||||
#endif
|
||||
fgets(s, 1023, m_fd);
|
||||
if(sscanf(s, "numkarts: %d",&numKarts)!=1)
|
||||
fgets(s, 1023, fd);
|
||||
unsigned int num_karts;
|
||||
if(sscanf(s, "numkarts: %d",&num_karts)!=1)
|
||||
{
|
||||
fprintf(stderr,"WARNING: No number of karts found in history file.\n");
|
||||
exit(-2);
|
||||
}
|
||||
race_manager->setNumKarts(numKarts);
|
||||
race_manager->setNumKarts(num_karts);
|
||||
|
||||
fgets(s, 1023, m_fd);
|
||||
fgets(s, 1023, fd);
|
||||
if(sscanf(s, "numplayers: %d",&n)!=1)
|
||||
{
|
||||
fprintf(stderr,"WARNING: No number of players found in history file.\n");
|
||||
@ -135,7 +236,7 @@ void History::Load()
|
||||
}
|
||||
race_manager->setNumPlayers(n);
|
||||
|
||||
fgets(s, 1023, m_fd);
|
||||
fgets(s, 1023, fd);
|
||||
if(sscanf(s, "difficulty: %d",&n)!=1)
|
||||
{
|
||||
fprintf(stderr,"WARNING: No difficulty found in history file.\n");
|
||||
@ -143,7 +244,7 @@ void History::Load()
|
||||
}
|
||||
race_manager->setDifficulty((RaceManager::Difficulty)n);
|
||||
|
||||
fgets(s, 1023, m_fd);
|
||||
fgets(s, 1023, fd);
|
||||
if(sscanf(s, "track: %s",s1)!=1)
|
||||
{
|
||||
fprintf(stderr,"WARNING: Track not found in history file.\n");
|
||||
@ -153,40 +254,58 @@ void History::Load()
|
||||
// the racing phase can switch to 'ending'
|
||||
race_manager->setNumLaps(10);
|
||||
|
||||
for(int i=0; i<numKarts; i++)
|
||||
for(unsigned int i=0; i<num_karts; i++)
|
||||
{
|
||||
fgets(s, 1023, m_fd);
|
||||
fgets(s, 1023, fd);
|
||||
if(sscanf(s, "model %d: %s",&n, s1)!=2)
|
||||
{
|
||||
fprintf(stderr,"WARNING: No model information for kart %d found.\n",
|
||||
i);
|
||||
exit(-2);
|
||||
}
|
||||
if(i<race_manager->getNumPlayers())
|
||||
{
|
||||
race_manager->setLocalKartInfo(i, s1);
|
||||
}
|
||||
} // for i<nKarts
|
||||
// JH: The model information is currently ignored
|
||||
fgets(s, 1023, m_fd);
|
||||
if(sscanf(s,"size: %d",&n)!=1)
|
||||
// FIXME: The model information is currently ignored
|
||||
fgets(s, 1023, fd);
|
||||
if(sscanf(s,"size: %d",&m_size)!=1)
|
||||
{
|
||||
fprintf(stderr,"WARNING: Number of records not found in history file.\n");
|
||||
exit(-2);
|
||||
}
|
||||
SetSize(n);
|
||||
allocateMemory(m_size);
|
||||
m_current = -1;
|
||||
|
||||
for(int i=0; i<m_size; i++)
|
||||
{
|
||||
fgets(s, 1023, m_fd);
|
||||
sscanf(s, "delta: %f\n",m_all_deltas+i);
|
||||
fgets(s, 1023, fd);
|
||||
sscanf(s, "delta: %f\n",&m_all_deltas[i]);
|
||||
}
|
||||
m_current = -1;
|
||||
|
||||
for(unsigned int k=0; k<num_karts; k++)
|
||||
{
|
||||
int j=0;
|
||||
for(int i=0; i<m_size; i++)
|
||||
{
|
||||
fgets(s, 1023, fd);
|
||||
int buttonsCompressed;
|
||||
float x,y,z,rx,ry,rz,rw;
|
||||
sscanf(s, "%d %f %f %d %f %f %f %f %f %f %f\n",
|
||||
&j,
|
||||
&m_all_controls[i].lr,
|
||||
&m_all_controls[i].accel,
|
||||
&buttonsCompressed,
|
||||
&x, &y, &z, &rx, &ry, &rz, &rw);
|
||||
m_all_xyz[i] = Vec3(x,y,z);
|
||||
m_all_rotations[i] = btQuaternion(rx,ry,rz,rw);
|
||||
m_all_controls[i].setButtonsCompressed(char(buttonsCompressed));
|
||||
} // for i
|
||||
} // for k
|
||||
fprintf(fd, "History file end.\n");
|
||||
fclose(fd);
|
||||
network_manager->setupPlayerKartInfo();
|
||||
|
||||
} // Load
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void History::LoadKartData(Kart* k, int kartNumber)
|
||||
{
|
||||
char s[1024];
|
||||
for(int i=0; i<m_size; i++)
|
||||
{
|
||||
fgets(s, 1023, m_fd);
|
||||
k->ReadHistory(s, kartNumber, i);
|
||||
} // for i<m_current
|
||||
} // LoadKartData
|
||||
|
||||
|
@ -17,35 +17,62 @@
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
#ifndef HEADER_HISTORY_H
|
||||
#define HEADER_HISTORY_H
|
||||
#ifndef HEADER_HISTORY_HPP
|
||||
#define HEADER_HISTORY_HPP
|
||||
|
||||
#include <cstdio>
|
||||
#include "constants.hpp"
|
||||
#include <vector>
|
||||
|
||||
#include "LinearMath/btQuaternion.h"
|
||||
#include "kart_control.hpp"
|
||||
#include "vec3.hpp"
|
||||
|
||||
class Kart;
|
||||
|
||||
class History
|
||||
{
|
||||
protected:
|
||||
// maximum number of history events to store
|
||||
int m_size;
|
||||
int m_current;
|
||||
bool m_wrapped;
|
||||
FILE* m_fd;
|
||||
|
||||
float * m_all_deltas;
|
||||
public:
|
||||
History () { SetSize(MAX_HISTORY);}
|
||||
int GetCount () { return m_wrapped ? m_size : m_current+1; }
|
||||
int GetCurrentIndex() { return m_current;}
|
||||
int GetSize () { return m_size; }
|
||||
void SetSize (int n);
|
||||
void StoreDelta (float delta);
|
||||
/** Determines which replay mode is selected:
|
||||
* HISTORY_NONE: no history replay.
|
||||
* HISTORY_POSITION: replay the positions and orientations of the karts,
|
||||
* but don't simulate the physics.
|
||||
* HISTORY_PHYSICS: Simulate the phyics based on the recorded actions.
|
||||
* These values can be used together, e.g. HISTORY_POSITION|HISTORY_CONTROL
|
||||
*/
|
||||
enum HistoryReplayMode { HISTORY_NONE = 0,
|
||||
HISTORY_POSITION = 1,
|
||||
HISTORY_PHYSICS = 2 };
|
||||
private:
|
||||
// maximum number of history events to store
|
||||
HistoryReplayMode m_replay_mode;
|
||||
int m_current;
|
||||
bool m_wrapped;
|
||||
int m_size;
|
||||
std::vector<float> m_all_deltas;
|
||||
std::vector<KartControl> m_all_controls;
|
||||
std::vector<Vec3> m_all_xyz;
|
||||
std::vector<btQuaternion> m_all_rotations;
|
||||
void allocateMemory(int number_of_frames);
|
||||
void updateSaving(float dt);
|
||||
void updateReplay(float dt);
|
||||
public:
|
||||
History ();
|
||||
void startReplay ();
|
||||
void initRecording ();
|
||||
void update (float dt);
|
||||
void Save ();
|
||||
void Load ();
|
||||
void LoadKartData (Kart* k, int kartNumber);
|
||||
float GetNextDelta ();
|
||||
float getNextDelta () const { return m_all_deltas[m_current]; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
/** Returns if a history is replayed, i.e. the history mode is not none. */
|
||||
bool replayHistory () const { return m_replay_mode != HISTORY_NONE; }
|
||||
// ------------------------------------------------------------------------
|
||||
/** Enable replaying a history, enabled from the command line. */
|
||||
void doReplayHistory(HistoryReplayMode m) {m_replay_mode = m; }
|
||||
// ------------------------------------------------------------------------
|
||||
/** Returns true if the physics should not be simulated in replay mode.
|
||||
* I.e. either no replay mode, or physics replay mode. */
|
||||
bool dontDoPhysics () const { return m_replay_mode == HISTORY_POSITION;}
|
||||
};
|
||||
|
||||
extern History* history;
|
||||
|
10
src/kart.cpp
10
src/kart.cpp
@ -53,14 +53,16 @@
|
||||
#include "utils/ssg_help.hpp"
|
||||
|
||||
|
||||
Kart::Kart (const std::string& kart_name, int position,
|
||||
const btTransform& init_transform)
|
||||
: TerrainInfo(1),
|
||||
#if defined(WIN32) && !defined(__CYGWIN__)
|
||||
// Disable warning for using 'this' in base member initializer list
|
||||
# pragma warning(disable:4355)
|
||||
#endif
|
||||
Moveable(true), m_attachment(this), m_collectable(this)
|
||||
|
||||
Kart::Kart (const std::string& kart_name, int position,
|
||||
const btTransform& init_transform)
|
||||
: TerrainInfo(1),
|
||||
Moveable(), m_attachment(this), m_collectable(this)
|
||||
|
||||
#if defined(WIN32) && !defined(__CYGWIN__)
|
||||
# pragma warning(1:4355)
|
||||
#endif
|
||||
|
@ -178,6 +178,8 @@ public:
|
||||
float getSteerPercent () const {return m_controls.lr; }
|
||||
const KartControl&
|
||||
getControls () const {return m_controls; }
|
||||
/** Sets the kart controls. Used e.g. by replaying history. */
|
||||
void setControls(const KartControl &c) { m_controls = c; }
|
||||
float getMaxSpeed () const {return m_max_speed; }
|
||||
void createPhysics (ssgEntity *obj);
|
||||
float getKartLength () const {return m_kart_properties->getKartLength();}
|
||||
|
@ -60,23 +60,37 @@ public:
|
||||
{
|
||||
m->addFloat(lr);
|
||||
m->addFloat(accel);
|
||||
m->addChar( brake ? 1 : 0
|
||||
+ wheelie ? 2 : 0
|
||||
+ jump ? 4 : 0
|
||||
+ rescue ? 8 : 0
|
||||
+ fire ? 16 : 0);
|
||||
m->addChar(getButtonsCompressed());
|
||||
} // compress
|
||||
// ------------------------------------------------------------------------
|
||||
void uncompress(char *c)
|
||||
{
|
||||
lr = ((float*)c)[0];
|
||||
accel = ((float*)c)[1];
|
||||
brake = (c[8] & 1) != 0;
|
||||
wheelie = (c[8] & 2) != 0;
|
||||
jump = (c[8] & 4) != 0;
|
||||
rescue = (c[8] & 8) != 0;
|
||||
fire = (c[8] & 16) != 0;
|
||||
setButtonsCompressed(c[8]);
|
||||
} // uncompress
|
||||
// ------------------------------------------------------------------------
|
||||
/** Compresses all buttons into a single integer value. */
|
||||
char getButtonsCompressed() const
|
||||
{
|
||||
return brake ? 1 : 0
|
||||
+ wheelie ? 2 : 0
|
||||
+ jump ? 4 : 0
|
||||
+ rescue ? 8 : 0
|
||||
+ fire ? 16 : 0;
|
||||
} // getButtonsCompressed
|
||||
// ------------------------------------------------------------------------
|
||||
/** Sets the buttons from a compressed representation.
|
||||
* /param c Character containing the compressed representation.
|
||||
*/
|
||||
void setButtonsCompressed(char c)
|
||||
{
|
||||
brake = (c & 1) != 0;
|
||||
wheelie = (c & 2) != 0;
|
||||
jump = (c & 4) != 0;
|
||||
rescue = (c & 8) != 0;
|
||||
fire = (c & 16) != 0;
|
||||
} // setButtonsCompressed
|
||||
};
|
||||
|
||||
#endif
|
||||
|
13
src/main.cpp
13
src/main.cpp
@ -109,6 +109,9 @@ void cmdLineHelp (char* invocation)
|
||||
// " --profile=n Enable automatic driven profile mode for n seconds\n"
|
||||
// " if n<0 --> (-n) = number of laps to drive
|
||||
// " --history Replay history file 'history.dat'\n"
|
||||
// " --history=n Replay history file 'history.dat' using mode:\n"
|
||||
// " n=1: use recorded positions\n"
|
||||
// " n=2: use recorded key strokes\n"
|
||||
" --server[=port] This is the server (running on the specified port)\n"
|
||||
" --client=ip This is a client, connect to the specified ip address\n"
|
||||
" --port=n Port number to use\n"
|
||||
@ -374,7 +377,7 @@ int handleCmdLine(int argc, char **argv)
|
||||
else if( !strcmp(argv[i], "--log=file"))
|
||||
{
|
||||
user_config->m_log_errors=true;
|
||||
}else if( sscanf(argv[i], "--profile=%d", &n)==1)
|
||||
} else if( sscanf(argv[i], "--profile=%d", &n)==1)
|
||||
{
|
||||
user_config->m_profile=n;
|
||||
if(n<0)
|
||||
@ -392,9 +395,13 @@ int handleCmdLine(int argc, char **argv)
|
||||
{
|
||||
user_config->m_profile=20;
|
||||
}
|
||||
else if( sscanf(argv[i], "--history=%d", &n)==1)
|
||||
{
|
||||
history->doReplayHistory( (History::HistoryReplayMode)n);
|
||||
}
|
||||
else if( !strcmp(argv[i], "--history") )
|
||||
{
|
||||
user_config->m_replay_history=true;
|
||||
history->doReplayHistory(History::HISTORY_POSITION);
|
||||
}
|
||||
else if( !strcmp(argv[i], "--herring") && i+1<argc )
|
||||
{
|
||||
@ -519,7 +526,7 @@ int main(int argc, char *argv[] )
|
||||
|
||||
// Replay a race
|
||||
// =============
|
||||
if(user_config->m_replay_history)
|
||||
if(history->replayHistory())
|
||||
{
|
||||
// This will setup the race manager etc.
|
||||
history->Load();
|
||||
|
@ -62,7 +62,7 @@ MainLoop::~MainLoop()
|
||||
void MainLoop::run()
|
||||
{
|
||||
const GLuint TITLE_SCREEN_TEXTURE =
|
||||
material_manager->getMaterial("st_title_screen.rgb")->getState()->getTextureHandle();
|
||||
material_manager->getMaterial(stk_config->m_menu_background)->getState()->getTextureHandle();
|
||||
|
||||
bool music_on = false;
|
||||
m_curr_time = SDL_GetTicks();
|
||||
@ -134,6 +134,7 @@ void MainLoop::run()
|
||||
|
||||
if ( RaceManager::getWorld()->getPhase() != LIMBO_PHASE)
|
||||
{
|
||||
history->update(dt);
|
||||
RaceManager::getWorld()->update(dt);
|
||||
|
||||
if(user_config->m_profile>0)
|
||||
@ -146,7 +147,7 @@ void MainLoop::run()
|
||||
printf("Number of frames: %d time %f, Average FPS: %f\n",
|
||||
m_frame_count, SDL_GetTicks() * 0.001,
|
||||
(float)m_frame_count/(SDL_GetTicks() * 0.001));
|
||||
if(!user_config->m_replay_history) history->Save();
|
||||
if(!history->replayHistory()) history->Save();
|
||||
std::exit(-2);
|
||||
} // if profile finished
|
||||
} // if m_profile
|
||||
|
@ -117,7 +117,8 @@ void TimedRace::update(const float dt)
|
||||
m_auxiliary_timer += dt;
|
||||
break;
|
||||
case MUSIC_PHASE:
|
||||
if(m_auxiliary_timer>TIME_MUSIC_DESCRIPTION) // how long to display the 'music' message
|
||||
// how long to display the 'music' message
|
||||
if(m_auxiliary_timer>stk_config->m_music_credit_time)
|
||||
m_phase=RACE_PHASE;
|
||||
m_auxiliary_timer += dt;
|
||||
break;
|
||||
@ -126,7 +127,7 @@ void TimedRace::update(const float dt)
|
||||
m_auxiliary_timer += dt;
|
||||
|
||||
// Nothing more to do if delay time is not over yet
|
||||
if(m_auxiliary_timer < TIME_DELAY_TILL_FINISH) break;
|
||||
if(m_auxiliary_timer < stk_config->m_delay_finish_time) break;
|
||||
|
||||
m_phase = FINISH_PHASE;
|
||||
break;
|
||||
|
@ -140,10 +140,7 @@ World::World() : TimedRace()
|
||||
break;
|
||||
}
|
||||
} // if !user_config->m_profile
|
||||
if(user_config->m_replay_history)
|
||||
{
|
||||
history->LoadKartData(newkart, i);
|
||||
}
|
||||
|
||||
newkart -> getModelTransform() -> clrTraversalMaskBits(SSGTRAV_ISECT|SSGTRAV_HOT);
|
||||
|
||||
scene->add ( newkart -> getModelTransform() ) ;
|
||||
@ -162,6 +159,7 @@ World::World() : TimedRace()
|
||||
|
||||
m_track->startMusic();
|
||||
|
||||
if(!history->replayHistory()) history->initRecording();
|
||||
network_manager->worldLoaded();
|
||||
} // World
|
||||
|
||||
@ -257,13 +255,17 @@ void World::resetAllKarts()
|
||||
//-----------------------------------------------------------------------------
|
||||
void World::update(float dt)
|
||||
{
|
||||
if(history->replayHistory()) dt=history->getNextDelta();
|
||||
TimedRace::update(dt);
|
||||
// Clear race state so that new information can be stored
|
||||
race_state->clear();
|
||||
if(user_config->m_replay_history) dt=history->GetNextDelta();
|
||||
|
||||
if(!user_config->m_replay_history) history->StoreDelta(dt);
|
||||
if(network_manager->getMode()!=NetworkManager::NW_CLIENT) m_physics->update(dt);
|
||||
if(network_manager->getMode()!=NetworkManager::NW_CLIENT &&
|
||||
!history->dontDoPhysics())
|
||||
{
|
||||
m_physics->update(dt);
|
||||
}
|
||||
|
||||
const int kart_amount = m_kart.size();
|
||||
for (int i = 0 ; i < kart_amount; ++i)
|
||||
{
|
||||
|
108
src/moveable.cpp
108
src/moveable.cpp
@ -22,9 +22,8 @@
|
||||
#include "material_manager.hpp"
|
||||
#include "material.hpp"
|
||||
#include "user_config.hpp"
|
||||
#include "history.hpp"
|
||||
|
||||
Moveable::Moveable (bool bHasHistory)
|
||||
Moveable::Moveable()
|
||||
{
|
||||
m_body = 0;
|
||||
m_motion_state = 0;
|
||||
@ -33,17 +32,6 @@ Moveable::Moveable (bool bHasHistory)
|
||||
m_model_transform = new ssgTransform();
|
||||
|
||||
m_model_transform->ref();
|
||||
|
||||
if(bHasHistory)
|
||||
{
|
||||
m_history_velocity = new sgCoord[history->GetSize()];
|
||||
m_history_position = new sgCoord[history->GetSize()];
|
||||
}
|
||||
else
|
||||
{
|
||||
m_history_velocity = NULL;
|
||||
m_history_position = NULL;
|
||||
}
|
||||
} // Moveable
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -52,11 +40,6 @@ Moveable::~Moveable()
|
||||
// The body is being removed from the world in kart/projectile
|
||||
if(m_body) delete m_body;
|
||||
if(m_motion_state) delete m_motion_state;
|
||||
if(m_history_velocity)
|
||||
{
|
||||
delete [] m_history_velocity;
|
||||
delete [] m_history_position;
|
||||
}
|
||||
// FIXME what about model?
|
||||
} // ~Moveable
|
||||
|
||||
@ -106,52 +89,6 @@ void Moveable::update (float dt)
|
||||
m_velocityLC = getVelocity()*getTrans().getBasis();
|
||||
m_hpr.setHPR(m_transform.getBasis());
|
||||
|
||||
if(m_history_velocity)
|
||||
{
|
||||
if(user_config->m_replay_history)
|
||||
{
|
||||
sgCoord tmp;
|
||||
sgCopyCoord(&tmp, &(m_history_velocity[history->GetCurrentIndex()]));
|
||||
|
||||
#undef IGNORE_Z_IN_HISTORY
|
||||
#ifdef IGNORE_Z_IN_HISTORY
|
||||
const float DUMMY=m_velocity.xyz[2];
|
||||
sgCopyCoord(&m_velocity, &tmp);
|
||||
m_velocity.xyz[2]=DUMMY;
|
||||
#else
|
||||
m_velocityLC.setValue(tmp.xyz[0],tmp.xyz[1],tmp.xyz[2]);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
m_history_velocity[history->GetCurrentIndex()].xyz[0]=m_velocityLC.getX();
|
||||
m_history_velocity[history->GetCurrentIndex()].xyz[1]=m_velocityLC.getY();
|
||||
m_history_velocity[history->GetCurrentIndex()].xyz[2]=m_velocityLC.getZ();
|
||||
}
|
||||
} // if m_history_velocity
|
||||
|
||||
if(m_history_position)
|
||||
{
|
||||
if(user_config->m_replay_history)
|
||||
{
|
||||
sgCoord tmp;
|
||||
sgCopyCoord(&tmp, &(m_history_position[history->GetCurrentIndex()]));
|
||||
Vec3 hpr(tmp.hpr);
|
||||
hpr.degreeToRad();
|
||||
btMatrix3x3 rotation;
|
||||
rotation.setEulerZYX(hpr.getPitch(), hpr.getRoll(), hpr.getHeading());
|
||||
m_transform.setBasis(rotation);
|
||||
m_transform.setOrigin(Vec3(tmp.xyz));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Coord c(m_transform);
|
||||
sgCopyCoord(&(m_history_position[history->GetCurrentIndex()]), &c.toSgCoord());
|
||||
}
|
||||
} // if m_history_position
|
||||
|
||||
|
||||
updateGraphics(Vec3(0,0,0), Vec3(0,0,0));
|
||||
m_first_time = false ;
|
||||
} // update
|
||||
@ -166,46 +103,3 @@ void Moveable::updateGraphics(const Vec3& off_xyz, const Vec3& off_hpr)
|
||||
m_model_transform->setTransform(&c);
|
||||
} // updateGraphics
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Moveable::WriteHistory(char* s, int kartNumber, int indx)
|
||||
{
|
||||
sprintf(s, "Kart %d: v=%f,%f,%f,%f,%f,%f, p=%f,%f,%f,%f,%f,%f", kartNumber,
|
||||
m_history_velocity[indx].xyz[0],
|
||||
m_history_velocity[indx].xyz[1],
|
||||
m_history_velocity[indx].xyz[2],
|
||||
m_history_velocity[indx].hpr[0],
|
||||
m_history_velocity[indx].hpr[1],
|
||||
m_history_velocity[indx].hpr[2],
|
||||
m_history_position[indx].xyz[0],
|
||||
m_history_position[indx].xyz[1],
|
||||
m_history_position[indx].xyz[2],
|
||||
m_history_position[indx].hpr[0],
|
||||
m_history_position[indx].hpr[1],
|
||||
m_history_position[indx].hpr[2]);
|
||||
} // WriteHistory
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Moveable::ReadHistory(char* s, int kartNumber, int indx)
|
||||
{
|
||||
int k;
|
||||
sscanf(s, "Kart %d: v=%f,%f,%f,%f,%f,%f, p=%f,%f,%f,%f,%f,%f", &k,
|
||||
m_history_velocity[indx].xyz+0,
|
||||
m_history_velocity[indx].xyz+1,
|
||||
m_history_velocity[indx].xyz+2,
|
||||
m_history_velocity[indx].hpr+0,
|
||||
m_history_velocity[indx].hpr+1,
|
||||
m_history_velocity[indx].hpr+2,
|
||||
m_history_position[indx].xyz+0,
|
||||
m_history_position[indx].xyz+1,
|
||||
m_history_position[indx].xyz+2,
|
||||
m_history_position[indx].hpr+0,
|
||||
m_history_position[indx].hpr+1,
|
||||
m_history_position[indx].hpr+2);
|
||||
if(k!=kartNumber)
|
||||
{
|
||||
fprintf(stderr,"WARNING: tried reading data for kart %d, found:\n",
|
||||
kartNumber);
|
||||
fprintf(stderr,"%s\n",s);
|
||||
exit(-2);
|
||||
}
|
||||
} // ReadHistory
|
||||
|
@ -43,18 +43,16 @@ private:
|
||||
Vec3 m_hpr;
|
||||
protected:
|
||||
UserPointer m_user_pointer;
|
||||
sgVec4* m_normal_hot; /* plane on which HOT was computed */
|
||||
Material* m_material_hot; /* Material at HOT */
|
||||
ssgTransform* m_model_transform; // The transform where the model is under
|
||||
ssgTransform* m_shadow;
|
||||
sgVec4 *m_normal_hot; /* plane on which HOT was computed */
|
||||
Material *m_material_hot; /* Material at HOT */
|
||||
ssgTransform *m_model_transform; // The transform where the model is under
|
||||
ssgTransform *m_shadow;
|
||||
int m_first_time ;
|
||||
sgCoord* m_history_velocity;
|
||||
sgCoord* m_history_position;
|
||||
btRigidBody* m_body;
|
||||
btDefaultMotionState* m_motion_state;
|
||||
btRigidBody *m_body;
|
||||
btDefaultMotionState *m_motion_state;
|
||||
public:
|
||||
|
||||
Moveable (bool bHasHistory=false);
|
||||
Moveable ();
|
||||
virtual ~Moveable();
|
||||
|
||||
ssgTransform* getModelTransform() {return m_model_transform; }
|
||||
@ -77,8 +75,6 @@ public:
|
||||
virtual void handleZipper () {};
|
||||
virtual void reset ();
|
||||
virtual void update (float dt) ;
|
||||
void WriteHistory (char* s, int kartNumber, int indx);
|
||||
void ReadHistory (char* s, int kartNumber, int indx);
|
||||
btRigidBody* getBody () const {return m_body; }
|
||||
void createBody(float mass, btTransform& trans,
|
||||
btCollisionShape *shape);
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "constants.hpp"
|
||||
#include "audio/sfx_manager.hpp"
|
||||
#include "audio/sfx_base.hpp"
|
||||
#include "history.hpp"
|
||||
#include "player_kart.hpp"
|
||||
#include "player.hpp"
|
||||
#include "sdldrv.hpp"
|
||||
@ -169,7 +170,11 @@ void PlayerKart::steer(float dt, int steer_val)
|
||||
//-----------------------------------------------------------------------------
|
||||
void PlayerKart::update(float dt)
|
||||
{
|
||||
steer(dt, m_steer_val);
|
||||
// Don't do steering if it's replay. In position only replay it doesn't
|
||||
// matter, but if it's physics replay the gradual steering causes
|
||||
// incorrect results, since the stored values are already adjusted.
|
||||
if(!history->replayHistory())
|
||||
steer(dt, m_steer_val);
|
||||
|
||||
if(RaceManager::getWorld()->isStartPhase())
|
||||
{
|
||||
|
@ -37,7 +37,7 @@ void STKConfig::load(const std::string filename)
|
||||
// Check that all necessary values are indeed set
|
||||
// -----------------------------------------------
|
||||
|
||||
#define CHECK_NEG( a,strA) if(a==UNDEFINED) { \
|
||||
#define CHECK_NEG( a,strA) if(a<=UNDEFINED) { \
|
||||
fprintf(stderr,"Missing default value for '%s' in '%s'.\n", \
|
||||
strA,filename.c_str());exit(-1); \
|
||||
}
|
||||
@ -69,6 +69,11 @@ void STKConfig::load(const std::string filename)
|
||||
fprintf(stderr,"No follow leader interval(s) defined in stk_config");
|
||||
exit(-1);
|
||||
}
|
||||
if(m_menu_background.size()==0)
|
||||
{
|
||||
fprintf(stderr,"No menu background defined in stk_config");
|
||||
exit(-1);
|
||||
}
|
||||
CHECK_NEG(m_max_karts, "max-karts" );
|
||||
CHECK_NEG(m_grid_order, "grid-order" );
|
||||
|
||||
@ -127,6 +132,9 @@ void STKConfig::load(const std::string filename)
|
||||
CHECK_NEG(m_camera_max_accel, "camera-max-accel" );
|
||||
CHECK_NEG(m_camera_max_brake, "camera-max-brake" );
|
||||
CHECK_NEG(m_camera_distance, "camera-distance" );
|
||||
CHECK_NEG(m_max_history, "max-history" );
|
||||
CHECK_NEG(m_delay_finish_time, "delay-finish-time" );
|
||||
CHECK_NEG(m_music_credit_time, "music-credit-time" );
|
||||
|
||||
} // load
|
||||
|
||||
@ -147,7 +155,7 @@ void STKConfig::init_defaults()
|
||||
m_wheelie_restore_rate = m_wheelie_speed_boost =
|
||||
m_bomb_time = m_bomb_time_increase= m_anvil_time =
|
||||
m_zipper_time = m_zipper_force = m_zipper_speed_gain =
|
||||
m_shortcut_length =
|
||||
m_shortcut_length = m_music_credit_time = m_delay_finish_time =
|
||||
//bullet physics data
|
||||
m_suspension_stiffness = m_wheel_damping_relaxation =
|
||||
m_wheel_damping_compression = m_friction_slip = m_roll_influence =
|
||||
@ -164,6 +172,7 @@ void STKConfig::init_defaults()
|
||||
m_rear_wheel_connection = Vec3(UNDEFINED);
|
||||
m_max_karts = -100;
|
||||
m_grid_order = -100;
|
||||
m_max_history = -100;
|
||||
m_title_music = NULL;
|
||||
m_scores.clear();
|
||||
m_leader_intervals.clear();
|
||||
@ -193,7 +202,11 @@ void STKConfig::getAllData(const lisp::Lisp* lisp)
|
||||
lisp->get("explosion-impulse-objects", m_explosion_impulse_objects);
|
||||
lisp->get("max-karts", m_max_karts );
|
||||
lisp->get("grid-order", m_grid_order );
|
||||
lisp->getVector("scores", m_scores);
|
||||
lisp->getVector("scores", m_scores );
|
||||
lisp->get("max-history", m_max_history );
|
||||
lisp->get("delay-finish-time", m_delay_finish_time );
|
||||
lisp->get("music-credit-time", m_music_credit_time );
|
||||
lisp->get("menu-background", m_menu_background );
|
||||
std::string title_music;
|
||||
lisp->get("title-music", title_music );
|
||||
m_title_music = new MusicInformation(file_manager->getMusicFile(title_music));
|
||||
|
@ -21,39 +21,61 @@
|
||||
#define HEADER_STKCONFIG_H
|
||||
|
||||
#include "kart_properties.hpp"
|
||||
#include "lisp/lisp.hpp"
|
||||
|
||||
class Lisp;
|
||||
class MusicInformation;
|
||||
|
||||
/** Global STK configuration information. Parameters here can be tuned without
|
||||
* recompilation, but the user shouldn't actually modify them. It also
|
||||
* includes the list of default kart physics parameters which are used for
|
||||
* each kart (but which can be overwritten for each kart, too).
|
||||
*/
|
||||
class STKConfig : public KartProperties
|
||||
{
|
||||
public:
|
||||
static float UNDEFINED;
|
||||
float m_anvil_weight; // Additional kart weight if anvil is attached
|
||||
float m_anvil_speed_factor; // To decrease speed once when attached
|
||||
float m_parachute_friction; // Increased air friction when parachute
|
||||
float m_parachute_done_fraction; // fraction of speed when lost will detach parachute
|
||||
float m_parachute_time; // time a parachute is active
|
||||
float m_parachute_time_other; // time a parachute attached to other karts is active
|
||||
float m_bomb_time; // time before a bomb explodes
|
||||
float m_bomb_time_increase; // time added to bomb timer when it's passed on
|
||||
float m_anvil_time; // time an anvil is active
|
||||
float m_zipper_time; // duration a zipper is active
|
||||
float m_zipper_force; // additional force added to the acceleration
|
||||
float m_zipper_speed_gain; // initial one time speed gain
|
||||
float m_shortcut_length; // skipping more than this number of segments is
|
||||
// considered to be a shortcut
|
||||
float m_explosion_impulse; // impulse affecting each non-hit kart
|
||||
float m_explosion_impulse_objects;// impulse of explosion on moving objects, e.g. road cones, ...
|
||||
int m_max_karts; // maximum number of karts
|
||||
int m_grid_order; // whether grand prix grid is in point or reverse point order
|
||||
float m_anvil_weight; /**<Additional kart weight if anvil is
|
||||
attached. */
|
||||
float m_anvil_speed_factor; /**<Speed decrease when attached first. */
|
||||
float m_parachute_friction; /**<Increased parachute air friction. */
|
||||
float m_parachute_done_fraction; /**<Fraction of speed when lost will
|
||||
detach parachute. */
|
||||
float m_parachute_time; /**<Time a parachute is active. */
|
||||
float m_parachute_time_other; /**<Time a parachute attached to other
|
||||
karts is active. */
|
||||
float m_bomb_time; /**<Time before a bomb explodes. */
|
||||
float m_bomb_time_increase; /**<Time added to bomb timer when it's
|
||||
passed on. */
|
||||
float m_anvil_time; /**<Time an anvil is active. */
|
||||
float m_zipper_time; /**<Duration a zipper is active. */
|
||||
float m_zipper_force; /**<Additional force added to the
|
||||
acceleration. */
|
||||
float m_zipper_speed_gain; /**<Initial one time speed gain. */
|
||||
float m_shortcut_length; /**<Skipping more than this distance
|
||||
in segments triggers a shortcut. */
|
||||
float m_explosion_impulse; /**<Impulse affecting each non-hit kart.*/
|
||||
float m_explosion_impulse_objects;/**<Impulse of explosion on moving
|
||||
objects, e.g. road cones, ... */
|
||||
float m_delay_finish_time; /**<Delay after a race finished before
|
||||
the results are displayed. */
|
||||
float m_music_credit_time; /**<Time the music credits are
|
||||
displayed. */
|
||||
int m_max_karts; /**<Maximum number of karts. */
|
||||
int m_grid_order; /**<Whether grand prix grid is in point
|
||||
or reverse point order. */
|
||||
int m_max_history; /**<Maximum number of frames to save in
|
||||
a history files. */
|
||||
|
||||
std::vector<float>
|
||||
m_leader_intervals; // interval in follow the leader till last kart is reomved
|
||||
std::vector<int> m_scores; // scores depending on position
|
||||
m_leader_intervals; /**<Interval in follow the leader till
|
||||
last kart is reomved. */
|
||||
std::vector<int> m_scores; /**<Scores depending on position. */
|
||||
|
||||
MusicInformation* m_title_music; // filename of the title music to play
|
||||
MusicInformation* m_title_music; /**<Filename of the title music to play.*/
|
||||
std::string m_menu_background; /**<Picture used as menu background. */
|
||||
|
||||
STKConfig() : KartProperties() {};
|
||||
/** Empty constructor. The actual work is done in load. */
|
||||
STKConfig() : KartProperties() {};
|
||||
void init_defaults ();
|
||||
void getAllData (const lisp::Lisp* lisp);
|
||||
void load (const std::string filename);
|
||||
|
@ -444,16 +444,17 @@ btTransform Track::getStartTransform(unsigned int pos) const {
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Determines if a kart moving from sector OLDSEC to sector NEWSEC
|
||||
* would be taking a shortcut, i.e. if the distance is larger
|
||||
* than a certain detla
|
||||
* than a certain delta
|
||||
*/
|
||||
bool Track::isShortcut(const int OLDSEC, const int NEWSEC) const
|
||||
{
|
||||
// If the kart was off the road, don't do any shortcuts
|
||||
if(OLDSEC==UNKNOWN_SECTOR || NEWSEC==UNKNOWN_SECTOR) return false;
|
||||
int distance_sectors = m_distance_from_start[std::max(NEWSEC, OLDSEC)] - m_distance_from_start[std::min(NEWSEC, OLDSEC)];
|
||||
int distance_sectors = (int)(m_distance_from_start[std::max(NEWSEC, OLDSEC)] -
|
||||
m_distance_from_start[std::min(NEWSEC, OLDSEC)] );
|
||||
|
||||
// Handle 'warp around'
|
||||
const int track_length = m_distance_from_start[m_driveline.size()-1];
|
||||
const int track_length = (int)m_distance_from_start[m_driveline.size()-1];
|
||||
if( distance_sectors < 0 ) distance_sectors += track_length;
|
||||
else if( distance_sectors > track_length/2) distance_sectors -= track_length;
|
||||
|
||||
|
@ -109,7 +109,6 @@ void UserConfig::setDefaults()
|
||||
m_max_fps = 120;
|
||||
m_sfx_volume = 1.0f;
|
||||
m_use_kph = false;
|
||||
m_replay_history = false;
|
||||
m_width = 800;
|
||||
m_height = 600;
|
||||
m_prev_width = m_width;
|
||||
|
@ -180,7 +180,6 @@ public:
|
||||
std::string m_track_group; // Track group used last
|
||||
std::string m_server_address;
|
||||
int m_server_port;
|
||||
bool m_replay_history;
|
||||
bool m_use_kph;
|
||||
int m_width;
|
||||
int m_height;
|
||||
|
Loading…
Reference in New Issue
Block a user