2025-06-26 15:39:44 +02:00
|
|
|
Licar mod for making TAS (tool assisted speedruns). This is a simple mod that
|
2025-06-29 15:16:52 +02:00
|
|
|
mainly adds one feature: instead of completely restarting a map, the restart key
|
2025-06-26 15:39:44 +02:00
|
|
|
rather rewinds time a little back. This allows to retry any part of a map as
|
2025-06-29 15:16:52 +02:00
|
|
|
many times as one desires. Loading a replay for viewing also loads the inputs
|
|
|
|
from the replay so that it's possible to reuse a start portion of another run.
|
|
|
|
It helps to slow down time in the settings and drive in slow motion.
|
2025-06-26 15:39:44 +02:00
|
|
|
|
2025-06-29 15:16:52 +02:00
|
|
|
diff --git a/assets.h b/assets.h
|
|
|
|
index 4629aaa..c9449f5 100644
|
|
|
|
--- a/assets.h
|
|
|
|
+++ b/assets.h
|
|
|
|
@@ -36,7 +36,7 @@
|
|
|
|
static const char *LCR_texts[] =
|
|
|
|
{
|
|
|
|
#define LCR_TEXTS_VERSION 0
|
|
|
|
- LCR_VERSION, // version string
|
|
|
|
+ LCR_VERSION " TAS mod", // version string
|
|
|
|
#define LCR_TEXTS_TABS 1
|
|
|
|
"main menu",
|
|
|
|
"play map",
|
2025-06-26 15:39:44 +02:00
|
|
|
diff --git a/game.h b/game.h
|
2025-06-29 15:16:52 +02:00
|
|
|
index b1db32c..1743707 100644
|
2025-06-26 15:39:44 +02:00
|
|
|
--- a/game.h
|
|
|
|
+++ b/game.h
|
|
|
|
@@ -344,6 +344,9 @@ struct
|
|
|
|
uint32_t runTime; ///< Current time of the run, in ticks.
|
|
|
|
uint8_t mapBeaten;
|
|
|
|
|
|
|
|
+ uint8_t tasInputs[LCR_SETTING_TAS_MAX_INPUTS / 2 + 1];
|
|
|
|
+ unsigned int tasInputCount;
|
|
|
|
+
|
|
|
|
char popupStr[LCR_POPUP_STR_SIZE];
|
|
|
|
uint8_t popupCountdown;
|
|
|
|
|
2025-06-29 15:16:52 +02:00
|
|
|
@@ -504,8 +507,36 @@ void LCR_gameResetRun(uint8_t replay, uint8_t ghost)
|
2025-06-26 15:39:44 +02:00
|
|
|
LCR_game.ghost.active = ghost;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
- LCR_gameSetState(LCR_GAME_STATE_RUN_STARTING);
|
|
|
|
LCR_game.runTime = 0;
|
|
|
|
+
|
|
|
|
+ if (!LCR_racing.replay.on)
|
|
|
|
+ {
|
2025-06-29 15:16:52 +02:00
|
|
|
+ LCR_game.tasInputCount = LCR_game.tasInputCount >= LCR_SETTING_TAS_REWIND ?
|
|
|
|
+ LCR_game.tasInputCount - LCR_SETTING_TAS_REWIND : 0;
|
2025-06-26 15:39:44 +02:00
|
|
|
+
|
|
|
|
+ for (unsigned int i = 0; i < LCR_game.tasInputCount; ++i)
|
|
|
|
+ {
|
|
|
|
+ if (LCR_racingStep((LCR_game.tasInputs[i / 2] >> ((i % 2) * 4)) & 0x0f)
|
|
|
|
+ == LCR_RACING_EVENT_CP_TAKEN)
|
|
|
|
+ {
|
|
|
|
+ int carBlock[3];
|
|
|
|
+ LCR_racingGetCarBlockCoords(carBlock);
|
|
|
|
+ LCR_rendererMarkTakenCP(carBlock[0],carBlock[1],carBlock[2]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ LCR_game.runTime = LCR_racing.tick;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ LCR_gameSetState(LCR_GAME_STATE_RUN);
|
|
|
|
+ LCR_racingGetCarTransform(carTransform,carTransform + 3,0);
|
|
|
|
+ LCR_rendererSetCarTransform(carTransform,carTransform + 3);
|
|
|
|
+ LCR_rendererCameraReset();
|
|
|
|
+ LCR_rendererLoadMapChunks();
|
|
|
|
+ LCR_game.nextRenderFrameTime = LCR_game.time + 1000 / LCR_SETTING_FPS;
|
2025-06-29 15:16:52 +02:00
|
|
|
+ LCR_game.nextRacingTickTime = LCR_game.time + LCR_RACING_TICK_MS_RT;
|
2025-06-26 15:39:44 +02:00
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ LCR_gameSetState(LCR_GAME_STATE_RUN_STARTING);
|
|
|
|
}
|
|
|
|
|
|
|
|
void LCR_gameGetNthGhostSample(unsigned int n,
|
2025-06-29 15:16:52 +02:00
|
|
|
@@ -867,6 +898,7 @@ uint8_t LCR_gameLoadMap(unsigned int mapIndex)
|
2025-06-26 15:39:44 +02:00
|
|
|
|
|
|
|
result = LCR_mapLoadFromStr(LCR_gameGetNextDataStrChar,name);
|
|
|
|
LCR_game.mapBeaten = LCR_mapIsBeaten(LCR_currentMap.name);
|
|
|
|
+ LCR_game.tasInputCount = 0;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2025-06-29 15:16:52 +02:00
|
|
|
@@ -1708,6 +1740,23 @@ void LCR_gameHandleInput(void)
|
|
|
|
}
|
|
|
|
}
|
2025-06-26 15:39:44 +02:00
|
|
|
|
2025-06-29 15:16:52 +02:00
|
|
|
+void LCR_gameAddTASInput(uint8_t input)
|
|
|
|
+{
|
|
|
|
+ if (LCR_game.tasInputCount >= LCR_SETTING_TAS_MAX_INPUTS)
|
|
|
|
+ {
|
|
|
|
+ LCR_LOG1("maximum TAS inputs reached!");
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ uint8_t *inputRec = LCR_game.tasInputs + LCR_game.tasInputCount / 2;
|
2025-06-26 15:39:44 +02:00
|
|
|
+
|
2025-06-29 15:16:52 +02:00
|
|
|
+ *inputRec = (LCR_game.tasInputCount % 2) ?
|
|
|
|
+ *inputRec | ((input & 0x0f) << 4) : (input & 0x0f);
|
2025-06-26 15:39:44 +02:00
|
|
|
+
|
2025-06-29 15:16:52 +02:00
|
|
|
+ LCR_game.tasInputCount++;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
uint8_t LCR_gameStep(uint32_t time)
|
|
|
|
{
|
|
|
|
LCR_LOG2("game step (start)");
|
|
|
|
@@ -1742,9 +1791,21 @@ uint8_t LCR_gameStep(uint32_t time)
|
|
|
|
LCR_gameSetState(LCR_GAME_STATE_LOADED);
|
|
|
|
}
|
|
|
|
else if (LCR_game.state == LCR_GAME_STATE_LOADED)
|
|
|
|
- LCR_gameResetRun( // start countdown now, loading the map lost many frames
|
|
|
|
- LCR_game.menu.selectedTab == 2,
|
|
|
|
- LCR_game.menu.selectedTab == 3);
|
|
|
|
+ {
|
|
|
|
+ if (LCR_game.menu.selectedTab == 2) // view replay? => load TAS inputs
|
|
|
|
+ {
|
|
|
|
+ LCR_replayInitPlaying();
|
|
|
|
+ LCR_game.tasInputCount = 0;
|
|
|
|
+
|
|
|
|
+ while (!LCR_replayHasFinished())
|
|
|
|
+ LCR_gameAddTASInput(LCR_replayGetNextInput());
|
|
|
|
+
|
|
|
|
+ LCR_game.menu.selectedTab = 3;
|
|
|
|
+ _LCR_gamePrepareGhost();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ LCR_gameResetRun(0,LCR_game.menu.selectedTab == 3);
|
|
|
|
+ }
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LCR_gameHandleInput();
|
|
|
|
@@ -1765,6 +1826,9 @@ uint8_t LCR_gameStep(uint32_t time)
|
|
|
|
(LCR_game.keyStates[LCR_KEY_DOWN] ? LCR_RACING_INPUT_BACK : 0) |
|
|
|
|
(LCR_game.keyStates[LCR_KEY_LEFT] ? LCR_RACING_INPUT_LEFT : 0));
|
|
|
|
|
|
|
|
+ if (!paused && !LCR_racing.replay.on)
|
|
|
|
+ LCR_gameAddTASInput(input);
|
2025-06-26 15:39:44 +02:00
|
|
|
+
|
|
|
|
#ifdef LCR_FPS_GET_MS
|
|
|
|
frameTime = LCR_FPS_GET_MS;
|
|
|
|
#endif
|
|
|
|
diff --git a/settings.h b/settings.h
|
2025-06-29 15:16:52 +02:00
|
|
|
index 332df45..b9b3aba 100644
|
2025-06-26 15:39:44 +02:00
|
|
|
--- a/settings.h
|
|
|
|
+++ b/settings.h
|
|
|
|
@@ -296,4 +296,14 @@
|
|
|
|
#define LCR_SETTING_ONLY_SMALL_MAPS 0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
+#ifndef LCR_SETTING_TAS_MAX_INPUTS
|
|
|
|
+ /** Maximum possible length of a run in the TAS mod, in physics ticks. */
|
|
|
|
+ #define LCR_SETTING_TAS_MAX_INPUTS 4096
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+#ifndef LCR_SETTING_TAS_REWIND
|
|
|
|
+ /** Length of TAS rewind, in physics ticks. */
|
2025-06-29 15:16:52 +02:00
|
|
|
+ #define LCR_SETTING_TAS_REWIND 20
|
2025-06-26 15:39:44 +02:00
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
#endif // guard
|