From 0a91d5f54e4be35815897f59d03af34047da2179 Mon Sep 17 00:00:00 2001 From: Miloslav Ciz Date: Tue, 21 Jan 2025 22:46:56 +0100 Subject: [PATCH] Start ghost --- TODO.txt | 3 +++ game.h | 72 ++++++++++++++++++++++++++++++++++++++++++++++++------ racing.h | 24 ++++++++++++++++-- settings.h | 15 ++++++++++++ 4 files changed, 105 insertions(+), 9 deletions(-) diff --git a/TODO.txt b/TODO.txt index 0f2dbb9..c2a15bd 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,6 +1,9 @@ =========== GENERAL ============== - replay validation +- ghosts: if the replay for the ghost is too long (too many samples for the + preallocated array), we can subdivide the sample resolution (i.e. "stretch" + the samples) to cover the whole replay (ofc for the price of worse quality). - 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 diff --git a/game.h b/game.h index 1508f31..82174dc 100644 --- a/game.h +++ b/game.h @@ -211,6 +211,12 @@ static inline void LCR_drawPixelXYSafe(unsigned int x, unsigned int y, #define LCR_MENU_MAX_ITEMS 9 // don't change #define LCR_RESOURCE_ITEM_CHUNK (LCR_MENU_MAX_ITEMS - 1) #define LCR_MENU_TABS 4 + +#if LCR_SETTING_GHOST_MAX_SAMPLES == 0 + #undef LCR_MENU_TABS + #define LCR_MENU_TABS 3 +#endif + #define LCR_MENU_STRING_SIZE 16 #define LCR_RESOURCE_FILE_SEPARATOR '#' @@ -266,6 +272,23 @@ struct unsigned int firstItemIndex; unsigned int itemsTotal; } dataFile; + +#define LCR_GHOST_SAMPLE_SIZE 5 + +#if LCR_SETTING_GHOST_MAX_SAMPLES != 0 + struct + { + uint8_t samples[LCR_SETTING_GHOST_MAX_SAMPLES * LCR_GHOST_SAMPLE_SIZE]; + /**< Samples, each 5 bytes: 9 bits for X and Z, 10 for Z, + 4 for each rotation component. */ + uint8_t stretch; /**< Stretch of the base sample step, as a bit shift + (i.e. 1 means the step will be 2x as long etc.). This + is to allow ghosts for even long replays. */ + const uint8_t *currentSample; + uint16_t nextSampleIn; + } ghost; +#endif + } LCR_game; uint8_t LCR_gameMusicOn(void) @@ -314,13 +337,6 @@ void LCR_gameSetState(uint8_t state) LCR_game.stateStartTime = LCR_game.time; } -LCR_GameUnit LCR_carSpeedKMH(void) -{ - return // we use 28/8 as an approximation of 3.6 to convers MPS to KMH - (28 * LCR_SETTING_CMS_PER_BLOCK * LCR_racingGetCarSpeedUnsigned() * - LCR_RACING_FPS) / (800 * LCR_GAME_UNIT); -} - void LCR_gameResetRun(uint8_t replay) { LCR_GameUnit carTransform[6]; @@ -336,6 +352,42 @@ void LCR_gameResetRun(uint8_t replay) LCR_game.runTimeMS = 0; } +void _LCR_gamePrepareGhost(void) +{ + LCR_GameUnit carTransform[6]; + LCR_LOG1("preparing ghost"); + + LCR_gameResetRun(1); + + LCR_game.ghost.stretch = 0; + + while (((int) LCR_replay.achievedTime) > + (LCR_SETTING_GHOST_STEP << LCR_game.ghost.stretch) * + LCR_SETTING_GHOST_MAX_SAMPLES) + { + LCR_LOG2("stretching replay step"); + LCR_game.ghost.stretch++; + } + + while (!LCR_replayHasFinished()) + { + if (LCR_racing.tick % (LCR_SETTING_GHOST_STEP << LCR_game.ghost.stretch) + == 0) + { + LCR_racingGetCarTransform(carTransform,carTransform + 3,0); + } + + LCR_racingStep(0); + } +} + +LCR_GameUnit LCR_carSpeedKMH(void) +{ + return // we use 28/8 as an approximation of 3.6 to convers MPS to KMH + (28 * LCR_SETTING_CMS_PER_BLOCK * LCR_racingGetCarSpeedUnsigned() * + LCR_RACING_FPS) / (800 * LCR_GAME_UNIT); +} + /** Rewinds the global data file reading head to the beginning. */ @@ -1014,6 +1066,12 @@ uint8_t LCR_gameStep(uint32_t time) if (_LCR_gameIsLoading()) { LCR_rendererLoadMap(); + + if (LCR_game.state == LCR_GAME_STATE_LOADING_REP2) + { + _LCR_gamePrepareGhost(); + } + LCR_gameResetRun( LCR_game.state == LCR_GAME_STATE_LOADING_REP1); } diff --git a/racing.h b/racing.h index 05e97ce..c8d16a0 100644 --- a/racing.h +++ b/racing.h @@ -295,7 +295,10 @@ for (int i = 0; i < 8; ++i) // hash uint8_t LCR_replayGetNextInput(void) { if (LCR_replay.currentEvent >= LCR_replay.eventCount) + { + LCR_replay.currentFrame++; // has to be here return 0; + } if (LCR_replay.currentFrame == (LCR_replay.events[LCR_replay.currentEvent] >> 4)) @@ -312,7 +315,19 @@ uint8_t LCR_replayGetNextInput(void) int LCR_replayHasFinished(void) { - return LCR_replay.currentEvent >= LCR_replay.eventCount; + if (LCR_replay.currentEvent == LCR_replay.eventCount) + { + uint32_t totalTime = LCR_replay.currentFrame; + + for (int i = 0; i < LCR_replay.eventCount; ++i) + totalTime += LCR_replay.events[i] >> 4; + + return totalTime >= LCR_replay.achievedTime; + } + + return LCR_replay.currentEvent > LCR_replay.eventCount; + +// return LCR_replay.currentEvent >= LCR_replay.eventCount; } /** @@ -1084,8 +1099,13 @@ uint32_t LCR_racingStep(unsigned int input) if (LCR_racing.tick == 0) LCR_replayInitPlaying(); - input = LCR_replayGetNextInput(); + if (LCR_replayHasFinished()) + { + LCR_LOG1("replay finished"); + return LCR_RACING_EVENT_FINISHED; + } + input = LCR_replayGetNextInput(); } else { diff --git a/settings.h b/settings.h index daf2272..aefb036 100644 --- a/settings.h +++ b/settings.h @@ -205,4 +205,19 @@ #define LCR_SETTING_TIME_MULTIPLIER 100 #endif +#ifndef LCR_SETTING_GHOST_STEP + /** Step (in physics engine ticks) by which the samples for ghost car will be + spaced (positions inbetween will be interpolated). Lower step along with more + ghost samples should result in more accurate ghost animation, but will eat up + more memory. This should ideally be kept a power of two. */ + #define LCR_SETTING_GHOST_STEP 16 +#endif + +#ifndef LCR_SETTING_GHOST_MAX_SAMPLES + /** Maximum number of samples the ghost car will be able to use. Higher value + should generally result in more accurate ghost animation, but will eat up more + memory. Value 0 disables ghosts. */ + #define LCR_SETTING_GHOST_MAX_SAMPLES 128 +#endif + #endif // guard