From 0231cd25c56d1cd091c66e653d6ee51f2f2718cd Mon Sep 17 00:00:00 2001 From: Miloslav Ciz Date: Thu, 3 Jul 2025 00:35:59 +0200 Subject: [PATCH] Improve TAS mod --- mods/tas.diff | 373 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 362 insertions(+), 11 deletions(-) diff --git a/mods/tas.diff b/mods/tas.diff index 59382f5..6422f57 100644 --- a/mods/tas.diff +++ b/mods/tas.diff @@ -3,13 +3,14 @@ mainly adds one feature: instead of completely restarting a map, the restart key rather rewinds time a little back. This allows to retry any part of a map as 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. +Additionally there is more information shown in HUD and the possibility to set +slow motion levels in main menu. diff --git a/assets.h b/assets.h -index 4629aaa..c9449f5 100644 +index 06b853c..a8edf18 100644 --- a/assets.h +++ b/assets.h -@@ -36,7 +36,7 @@ +@@ -36,23 +36,24 @@ static const char *LCR_texts[] = { #define LCR_TEXTS_VERSION 0 @@ -18,11 +19,32 @@ index 4629aaa..c9449f5 100644 #define LCR_TEXTS_TABS 1 "main menu", "play map", +- "view repl", ++ "load repl", + "race repl", + #define LCR_TEXTS_MAIN_MENU 5 + "camera", + "music", + "sound", + "save repl", ++ "slowmo", + "exit", +-#define LCR_TEXTS_LOADING 10 ++#define LCR_TEXTS_LOADING 11 + "loading", +-#define LCR_TEXTS_SAVED 11 ++#define LCR_TEXTS_SAVED 12 + "saved", +-#define LCR_TEXTS_FAIL 12 ++#define LCR_TEXTS_FAIL 13 + "failed" + }; + diff --git a/game.h b/game.h -index b1db32c..1743707 100644 +index 1ad5131..f407ef4 100644 --- a/game.h +++ b/game.h -@@ -344,6 +344,9 @@ struct +@@ -344,9 +344,16 @@ struct uint32_t runTime; ///< Current time of the run, in ticks. uint8_t mapBeaten; @@ -32,7 +54,37 @@ index b1db32c..1743707 100644 char popupStr[LCR_POPUP_STR_SIZE]; uint8_t popupCountdown; -@@ -504,8 +507,36 @@ void LCR_gameResetRun(uint8_t replay, uint8_t ghost) ++ uint16_t racingTickRT; ++ ++ int carSpeeds[2]; ///< Current and previous tick speed. ++ + struct + { + uint8_t selectedTab; +@@ -441,8 +448,10 @@ void LCR_gamePopupMessage(const char *str) + + void LCR_gamePopupNumber(uint8_t num) + { +- LCR_game.popupStr[0] = '0' + num; +- LCR_game.popupStr[1] = 0; ++ LCR_game.popupStr[3] = 0; ++ LCR_game.popupStr[2] = '0' + num % 10; ++ LCR_game.popupStr[1] = num > 9 ? '0' + (num / 10) % 10 : ' '; ++ LCR_game.popupStr[0] = num > 99 ? '0' + num / 100 : ' '; + LCR_gamePopupMessage(LCR_game.popupStr); + } + +@@ -489,6 +498,9 @@ void LCR_gameResetRun(uint8_t replay, uint8_t ghost) + LCR_LOG0("resetting run"); + LCR_mapReset(); + ++ LCR_game.carSpeeds[0] = 0; ++ LCR_game.carSpeeds[1] = 0; ++ + LCR_racingRestart(replay); + LCR_rendererUnmarkCPs(); + LCR_racingGetCarTransform(carTransform,carTransform + 3,0); +@@ -504,8 +516,36 @@ void LCR_gameResetRun(uint8_t replay, uint8_t ghost) LCR_game.ghost.active = ghost; #endif @@ -63,14 +115,14 @@ index b1db32c..1743707 100644 + LCR_rendererCameraReset(); + LCR_rendererLoadMapChunks(); + LCR_game.nextRenderFrameTime = LCR_game.time + 1000 / LCR_SETTING_FPS; -+ LCR_game.nextRacingTickTime = LCR_game.time + LCR_RACING_TICK_MS_RT; ++ LCR_game.nextRacingTickTime = LCR_game.time + LCR_game.racingTickRT; + } + else + LCR_gameSetState(LCR_GAME_STATE_RUN_STARTING); } void LCR_gameGetNthGhostSample(unsigned int n, -@@ -867,6 +898,7 @@ uint8_t LCR_gameLoadMap(unsigned int mapIndex) +@@ -872,6 +912,7 @@ uint8_t LCR_gameLoadMap(unsigned int mapIndex) result = LCR_mapLoadFromStr(LCR_gameGetNextDataStrChar,name); LCR_game.mapBeaten = LCR_mapIsBeaten(LCR_currentMap.name); @@ -78,7 +130,212 @@ index b1db32c..1743707 100644 return result; } -@@ -1708,6 +1740,23 @@ void LCR_gameHandleInput(void) +@@ -967,14 +1008,14 @@ void LCR_gameEraseMenuItemNames(void) + + void LCR_gameLoadMainMenuItems(void) + { +- for (int j = 0; j < 5; ++j) ++ for (int j = 0; j < 6; ++j) + for (int i = 0; i < LCR_MENU_STRING_SIZE - 1; ++i) + { + LCR_game.menu.itemNames[j][i] = LCR_texts[LCR_TEXTS_MAIN_MENU + j][i]; + LCR_game.menu.itemNames[j][i + 1] = 0; + } + +- LCR_game.menu.itemCount = 5; ++ LCR_game.menu.itemCount = 6; + } + + #define LCR_GAME_DATA_FILE_BUFFER_SIZE 32 +@@ -1094,6 +1135,8 @@ void LCR_gameInit(int argc, const char **argv) + + LCR_game.dataFile.state = 0; + ++ LCR_game.racingTickRT = LCR_RACING_TICK_MS_RT; ++ + for (int i = 0; i < LCR_MENU_MAX_ITEMS; ++i) + LCR_game.menu.itemNamePtrs[i] = LCR_game.menu.itemNames[i]; + +@@ -1231,22 +1274,30 @@ void LCR_gameEnd(void) + + void LCR_gameTimeToStr(uint32_t timeMS, char *str) + { +- str[9] = 0; +- str[8] = '0' + timeMS % 10; // milliseconds ++ uint32_t ticks = timeMS / LCR_RACING_TICK_MS; ++ ++ str[12] = 0; ++ str[11] = '0' + timeMS % 10; // milliseconds + timeMS /= 10; +- str[7] = '0' + timeMS % 10; ++ str[10] = '0' + timeMS % 10; + timeMS /= 10; +- str[6] = '0' + timeMS % 10; ++ str[9] = '0' + timeMS % 10; + timeMS /= 10; +- str[5] = '\''; +- str[4] = '0' + timeMS % 10; // seconds ++ str[8] = '0' + timeMS % 10; // seconds + timeMS /= 10; +- str[3] = '0' + timeMS % 6; +- str[2] = '\''; +- timeMS /= 6; +- str[1] = '0' + timeMS % 10; // minutes ++ str[7] = '0' + timeMS % 10; + timeMS /= 10; +- str[0] = '0' + timeMS % 10; ++ str[6] = '0' + timeMS % 10; ++ str[5] = '\''; ++ str[4] = '0' + ticks % 10; ++ ticks /= 10; ++ str[3] = '0' + ticks % 10; ++ ticks /= 10; ++ str[2] = '0' + ticks % 10; ++ ticks /= 10; ++ str[1] = '0' + ticks % 10; ++ ticks /= 10; ++ str[0] = '0' + ticks % 10; + } + + void LCR_gameDrawPopupMessage(void) +@@ -1280,7 +1331,7 @@ void LCR_gameDraw3DView(void) + #endif + ? LCR_GAME_UNIT - + (((int) (LCR_game.nextRacingTickTime - LCR_game.time)) * LCR_GAME_UNIT) +- / LCR_RACING_TICK_MS_RT // 32: magic constant ++ / LCR_game.racingTickRT // 32: magic constant + : _LCR_min(LCR_GAME_UNIT,32 * ((int) (LCR_game.time - + LCR_game.stateStartTime))); + +@@ -1331,25 +1382,67 @@ void LCR_gameDraw3DView(void) + + #if LCR_SETTING_DISPLAY_HUD + // GUI/HUD: ++ ++ char str[16]; + +- char str[10]; +- int val = LCR_carSpeedKMH(); ++ for (int i = 0; i < 3; ++i) ++ { ++ int val = i == 2 ? (100 * LCR_racing.driftFriction) / ++ LCR_CAR_DRIFT_THRESHOLD_1 : LCR_game.carSpeeds[0]; + +- if (val < 5) // don't show tiny oscillations when still +- val = 0; ++ if (i == 1) ++ val -= LCR_game.carSpeeds[1]; ++ else if (i == 0 && val < 5) // don't show tiny speed oscillations when still ++ val = 0; + +- str[0] = val >= 100 ? '0' + (val / 100) % 10 : ' '; +- str[1] = val >= 10 ? '0' + (val / 10) % 10 : ' '; +- str[2] = '0' + val % 10; +- str[3] = 0; ++ str[0] = ' '; + +- #define _FONT_SIZE (1 + (LCR_EFFECTIVE_RESOLUTION_Y > 96)) ++ if (val < 0) ++ { ++ str[0] = '-'; ++ val *= -1; ++ } + +- LCR_rendererDrawText(str,LCR_EFFECTIVE_RESOLUTION_X - // speed (bot., right) +- LCR_rendererComputeTextWidth(str,_FONT_SIZE) - LCR_GUI_GAP, +- LCR_EFFECTIVE_RESOLUTION_Y - LCR_rendererComputeTextHeight(_FONT_SIZE) - +- LCR_GUI_GAP,0,_FONT_SIZE); ++ str[1] = val >= 100 ? '0' + (val / 100) % 10 : ' '; ++ str[2] = val >= 10 ? '0' + (val / 10) % 10 : ' '; ++ str[3] = '0' + val % 10; ++ str[4] = 0; ++ ++ #define _FONT_SIZE (1 + (LCR_EFFECTIVE_RESOLUTION_Y > 96)) ++ ++ LCR_rendererDrawText(str,LCR_EFFECTIVE_RESOLUTION_X - ++ LCR_rendererComputeTextWidth(str,_FONT_SIZE) - LCR_GUI_GAP, ++ LCR_EFFECTIVE_RESOLUTION_Y - (i + 1) * ++ (LCR_rendererComputeTextHeight(_FONT_SIZE) + LCR_GUI_GAP), ++ (i == 2 && LCR_racing.carDrifting) ? 0xf800 : 0,_FONT_SIZE); + ++ if (i == 0) ++ { ++ str[0] = (LCR_racing.currentInputs & LCR_RACING_INPUT_LEFT) ? 'L' : '.'; ++ str[1] = (LCR_racing.currentInputs & LCR_RACING_INPUT_BACK) ? 'D' : '.'; ++ str[2] = (LCR_racing.currentInputs & LCR_RACING_INPUT_FORW) ? 'U' : '.'; ++ str[3] = (LCR_racing.currentInputs & LCR_RACING_INPUT_RIGHT) ? 'R' : '.'; ++ str[4] = 0; ++ } ++ else if (i == 1) ++ { ++ str[0] = '0' + LCR_racingCurrentGroundMaterial(); ++ str[1] = (LCR_racing.wheelCollisions & 0x02) ? 'f' : '.'; ++ str[2] = (LCR_racing.wheelCollisions & 0x01) ? 'f' : '.'; ++ str[3] = (LCR_racing.wheelCollisions & 0x08) ? 'r' : '.'; ++ str[4] = (LCR_racing.wheelCollisions & 0x04) ? 'r' : '.'; ++ str[5] = 0; ++ } ++ ++ if (i < 2) ++ LCR_rendererDrawText(str,LCR_EFFECTIVE_RESOLUTION_X - ++ LCR_rendererComputeTextWidth(str,_FONT_SIZE) - LCR_GUI_GAP, ++ LCR_EFFECTIVE_RESOLUTION_Y - (4 + i) * ++ (LCR_rendererComputeTextHeight(_FONT_SIZE) + LCR_GUI_GAP),0,_FONT_SIZE); ++ } ++ ++ ++/* + str[0] = (LCR_racing.currentInputs & LCR_RACING_INPUT_LEFT) ? 'L' : '.'; + str[1] = (LCR_racing.currentInputs & LCR_RACING_INPUT_BACK) ? 'D' : '.'; + str[2] = (LCR_racing.currentInputs & LCR_RACING_INPUT_FORW) ? 'U' : '.'; +@@ -1358,8 +1451,9 @@ void LCR_gameDraw3DView(void) + + LCR_rendererDrawText(str,LCR_EFFECTIVE_RESOLUTION_X - + LCR_rendererComputeTextWidth(str,_FONT_SIZE) - LCR_GUI_GAP, +- LCR_EFFECTIVE_RESOLUTION_Y - 2 * ++ LCR_EFFECTIVE_RESOLUTION_Y - 3 * + (LCR_rendererComputeTextHeight(_FONT_SIZE) + LCR_GUI_GAP),0,_FONT_SIZE); ++*/ + + LCR_gameTimeToStr(LCR_game.runTime * LCR_RACING_TICK_MS,str); + +@@ -1384,7 +1478,7 @@ void LCR_gameDraw3DView(void) + + void LCR_gameSaveReplay(void) + { +- char str[10]; ++ char str[16]; + + LCR_LOG0("saving replay"); + +@@ -1581,7 +1675,7 @@ void LCR_gameHandleInput(void) + + if (LCR_game.menu.selectedTab == 0) + { +- if (LCR_game.menu.selectedItem < 4) ++ if (LCR_game.menu.selectedItem < 5) + { + LCR_game.menu.selectedItem++; + LCR_audioPlaySound(LCR_SOUND_CLICK); +@@ -1647,6 +1741,17 @@ void LCR_gameHandleInput(void) + break; + + case 4: ++ LCR_game.racingTickRT += LCR_RACING_TICK_MS / 2; ++ ++ if (LCR_game.racingTickRT > LCR_RACING_TICK_MS * 10) ++ LCR_game.racingTickRT = LCR_RACING_TICK_MS; ++ ++ LCR_gamePopupNumber( ++ (100 * LCR_RACING_TICK_MS ) / LCR_game.racingTickRT); ++ ++ break; ++ ++ case 5: + LCR_gameSetState(LCR_GAME_STATE_END); + break; + +@@ -1713,6 +1818,23 @@ void LCR_gameHandleInput(void) } } @@ -102,7 +359,7 @@ index b1db32c..1743707 100644 uint8_t LCR_gameStep(uint32_t time) { LCR_LOG2("game step (start)"); -@@ -1742,9 +1791,21 @@ uint8_t LCR_gameStep(uint32_t time) +@@ -1747,9 +1869,21 @@ uint8_t LCR_gameStep(uint32_t time) LCR_gameSetState(LCR_GAME_STATE_LOADED); } else if (LCR_game.state == LCR_GAME_STATE_LOADED) @@ -127,7 +384,7 @@ index b1db32c..1743707 100644 else { LCR_gameHandleInput(); -@@ -1765,6 +1826,9 @@ uint8_t LCR_gameStep(uint32_t time) +@@ -1770,12 +1904,19 @@ 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)); @@ -137,6 +394,100 @@ index b1db32c..1743707 100644 #ifdef LCR_FPS_GET_MS frameTime = LCR_FPS_GET_MS; #endif + ++ LCR_game.carSpeeds[1] = LCR_game.carSpeeds[0]; ++ + uint32_t events = paused ? 0 : LCR_racingStep(input); + ++ LCR_game.carSpeeds[0] = LCR_carSpeedKMH(); ++ + #if LCR_SETTING_PARTICLES + LCR_rendererSetParticles(0); + +@@ -1801,7 +1942,7 @@ uint8_t LCR_gameStep(uint32_t time) + if (events & LCR_RACING_EVENT_CP_TAKEN) + { + int carBlock[3]; +- char str[10]; ++ char str[16]; + + LCR_LOG1("CP taken"); + LCR_racingGetCarBlockCoords(carBlock); +@@ -1890,7 +2031,7 @@ uint8_t LCR_gameStep(uint32_t time) + LCR_game.state == LCR_GAME_STATE_RUN_STARTING) + LCR_game.runTime = LCR_racing.tick; + +- LCR_game.nextRacingTickTime += LCR_RACING_TICK_MS_RT; ++ LCR_game.nextRacingTickTime += LCR_game.racingTickRT; + } + + // handle rendering: +diff --git a/racing.h b/racing.h +index 0824354..cee1c23 100644 +--- a/racing.h ++++ b/racing.h +@@ -174,6 +174,8 @@ struct + + uint16_t crashState; + ++ TPE_Unit driftFriction; ++ + #if LCR_SETTING_REPLAY_MAX_SIZE != 0 + struct + { +@@ -1022,6 +1024,7 @@ void LCR_racingRestart(uint8_t replay) + + LCR_racing.tick = 0; + LCR_racing.fanForce = 0; ++ LCR_racing.driftFriction = 0; + + #if LCR_SETTING_REPLAY_MAX_SIZE > 0 + LCR_racing.replay.on = replay; +@@ -1258,7 +1261,7 @@ uint32_t LCR_racingStep(unsigned int input) + TPE_Vec3 carForw, carRight, carUp, carVel; + uint8_t onAccel = 0; // standing on accelerator? + int groundBlockIndex = -1; +- TPE_Unit driftFriction = 0; // average wheel friction (absolute value) ++ LCR_racing.driftFriction = 0; // average wheel friction (absolute value) + + LCR_racing.groundMaterial = LCR_BLOCK_MATERIAL_CONCRETE; + +@@ -1484,7 +1487,7 @@ uint32_t LCR_racingStep(unsigned int input) + (LCR_CAR_STEER_FRICTION * LCR_CAR_DRIFT_FACTOR) / 8 : + LCR_CAR_STEER_FRICTION,LCR_racing.groundMaterial)) / TPE_F); + +- driftFriction += TPE_vec3Len(fric); ++ LCR_racing.driftFriction += TPE_vec3Len(fric); + + jv = TPE_vec3Minus(jv,fric); // subtract the friction + +@@ -1493,7 +1496,7 @@ uint32_t LCR_racingStep(unsigned int input) + LCR_racing.carBody.joints[i].velocity[2] = jv.z; + } + +- driftFriction /= 4; // divide by 4 wheels ++ LCR_racing.driftFriction /= 4; // divide by 4 wheels + + if (steering && + (input & (LCR_RACING_INPUT_FORW | LCR_RACING_INPUT_BACK)) && +@@ -1510,7 +1513,7 @@ uint32_t LCR_racingStep(unsigned int input) + } + + if ((!LCR_racing.carDrifting) && +- driftFriction > ++ LCR_racing.driftFriction > + (LCR_CAR_DRIFT_THRESHOLD_1 >> (( // back key initiates drift easily + ((input & (LCR_RACING_INPUT_FORW | LCR_RACING_INPUT_BACK)) + == (LCR_RACING_INPUT_FORW | LCR_RACING_INPUT_BACK))) << 2))) +@@ -1519,7 +1522,7 @@ uint32_t LCR_racingStep(unsigned int input) + LCR_racing.carDrifting = 1; + } + else if (LCR_racing.carDrifting && +- (driftFriction < LCR_CAR_DRIFT_THRESHOLD_0 || ++ (LCR_racing.driftFriction < LCR_CAR_DRIFT_THRESHOLD_0 || + LCR_racingGetCarSpeedUnsigned() < 5)) + { + LCR_LOG1("drift end"); diff --git a/settings.h b/settings.h index 332df45..b9b3aba 100644 --- a/settings.h