Add time to replays

This commit is contained in:
Miloslav Ciz 2025-01-15 21:56:06 +01:00
parent 9d2c6108b1
commit 8d0adf66eb
2 changed files with 44 additions and 13 deletions

View file

@ -1,12 +1,13 @@
=========== GENERAL ==============
- replay validation
- add time slow down setting
- the horizon on background seems too low? maybe add setting to shift it a bit?
- add argc/argv to gameInit? could be used to quickly start maps, verify
replays etc.
- maybe each map could have a target time embedded: when beaten, the map would
be marked as such
- player name (modifyable via resource file)
- player name (modifiable via resource file)
- popup messages? would be useful for several things: showing checkpoint times,
showing changes in menu etc.
- make the racing module usable by itself, e.g. to allow making tools for
@ -17,7 +18,6 @@
lower memory)
- at the end check error handling, make sure the game handles garbage data in
resource file etc.
- replay format
=========== BUGS =================
@ -27,6 +27,7 @@
=========== HANDLED ==============
- allow stopping car rotation in air like in Trackmania
- replay format
- prevent time overflow! stop incrementing level frame once it's at maximum
- car shadow? probably would have to be done as screen space effect with
z-buffer (shadow as 3D model would require collision detection and would make

View file

@ -13,13 +13,14 @@
because the offset is too big (input didn't change for more than 2^12
frames), there must simply be inserted an extra word that just copies the
current input state.
- Replay text format: first there is hexadeciaml hash of the map (exactly 8
characters), then the name of the map follows immediately, then the
character ';', then the replay data, i.e. the sries of 16 bit words in
hexadecimal. The blocks (but nothing else) may be preceeded or followed by
blank characters. All hexadecimal letters must be lowercase. The word
00000000 may optinally be used to terminate the replay, the rest of the
string will be ignored.
- Replay text format: first there is the name of the map terminated by ';',
then hexadecimal hash of the map follows (exactly 8 characters), then
blank character follows, then achieved time as a series of decimal digits
expressing the number of milliseconds, then a non-decimal character follows,
then the replay data, i.e. the series of 16 bit words in hexadecimal. The
blocks (but nothing else) may be preceeded or followed by blank characters.
All hexadecimal letters must be lowercase. The word 00000000 may optinally
be used to terminate the replay, the rest of the string will be ignored.
*/
typedef int32_t LCR_GameUnit; ///< abstract game unit
@ -70,11 +71,13 @@ typedef int32_t LCR_GameUnit; ///< abstract game unit
#define LCR_CAR_GRASS_FACTOR 5
#define LCR_CAR_DIRT_FACTOR 3
#define LCR_CAR_ICE_FACTOR 1
#define LCR_CAR_DRIFT_FACTOR 2 ///< only affects steering friction
#define LCR_CAR_DRIFT_FACTOR 2 ///< only affects steering friction
#define LCR_CAR_JOINTS 5
#define LCR_CAR_CONNECTIONS 10
#define LCR_REPLAY_EVENT_END 0xff ///< special event fed to replay at the end
struct
{
TPE_World physicsWorld;
@ -114,6 +117,7 @@ struct
// for playing
uint16_t currentEvent;
uint16_t currentFrame;
uint32_t achievedTime;
} LCR_replay;
/**
@ -137,6 +141,7 @@ void LCR_replayInitRecording(void)
{
LCR_LOG1("initializing replay recording");
LCR_replay.eventCount = 0;
LCR_replay.achievedTime = 0;
}
void LCR_replayInitPlaying(void)
@ -172,6 +177,15 @@ void LCR_replayOutputStr(void (*printChar)(char))
hash /= 16;
}
printChar(' ');
// 8 decimal digits are enough to record 24 hours
#define PUTD(order) printChar('0' + (LCR_replay.achievedTime / order) % 10);
PUTD(10000000) PUTD(1000000) PUTD(100000) PUTD(10000)
PUTD(1000) PUTD(100) PUTD(10) PUTD(1)
#undef PUTD
for (int i = 0; i < LCR_replay.eventCount; ++i)
{
uint16_t e = LCR_replay.events[i];
@ -179,8 +193,8 @@ void LCR_replayOutputStr(void (*printChar)(char))
for (int j = 0; j < 4; ++j)
{
printChar(_LCR_hexDigit(e % 16));
e /= 16;
printChar(_LCR_hexDigit((e >> 12) & 0x0f));
e <<= 4;
}
}
@ -216,12 +230,23 @@ int LCR_replayHasFinished(void)
/**
Records another input event. Returns 1 on success, or 0 if the event couldn't
be recorded. The event is only added if necessary, i.e. this function can
safely be called every frame without worrying about inflating its size.
(and must) be called every frame without worrying about inflating its size.
When the run ends, the LCR_REPLAY_EVENT_END has to be fed!
*/
int LCR_replayRecordEvent(uint32_t frame, uint8_t input)
{
LCR_LOG2("recording replay event");
if (LCR_replay.achievedTime)
return 1; // already finished
if (input == LCR_REPLAY_EVENT_END)
{
LCR_replay.achievedTime = frame;
LCR_LOG1("replay recording finished");
return 1;
}
#if LCR_SETTING_REPLAY_MAX_SIZE > 0
uint8_t previousInput = 0;
uint32_t previousFrame = 0;
@ -1363,7 +1388,12 @@ uint32_t LCR_racingStep(unsigned int input)
}
if (valid)
{
result |= LCR_RACING_EVENT_FINISHED;
if (!LCR_racing.playingReplay)
LCR_replayRecordEvent(LCR_racing.tick,LCR_REPLAY_EVENT_END);
}
}
}