Continue replay saving

This commit is contained in:
Miloslav Ciz 2025-02-23 15:58:31 +01:00
parent e43ed793a3
commit 27da348b24
6 changed files with 218 additions and 185 deletions

View file

@ -42,6 +42,7 @@ Some of the **gameplay features** include:
- **No codes of censorship, flags, furry mascots or similar political bullshit**. This is just a game. - **No codes of censorship, flags, furry mascots or similar political bullshit**. This is just a game.
- **No "modern" bullshit such as OOP, meme design patterns, scripting and similar nonsense**. - **No "modern" bullshit such as OOP, meme design patterns, scripting and similar nonsense**.
- It is **absolutely and completely public domain free software with ZERO conditions on use** under CC0, it is legally more free that most "FOSS" software, there is no copyleft or credit requirement, you can do ABSOLUTELY anything you want with the project. This is a **selfless project aiming for no benefit of the creator** (include any non-finacial benefit as well), this is made purely to bring more good to the world without gaining any advantage for self. - It is **absolutely and completely public domain free software with ZERO conditions on use** under CC0, it is legally more free that most "FOSS" software, there is no copyleft or credit requirement, you can do ABSOLUTELY anything you want with the project. This is a **selfless project aiming for no benefit of the creator** (include any non-finacial benefit as well), this is made purely to bring more good to the world without gaining any advantage for self.
- This game has **PURE SOUL**. No Unity or Unreal, no "retro" imitation shaders, no assets from a store. This isn't an immitation of a 90s game, this IS a 90s game.
## Manifesto ## Manifesto

View file

@ -1,5 +1,10 @@
=========== GENERAL ============== =========== GENERAL ==============
- make the U-ramp map taller due to new physics
- replay format should probably record game version
- also there should probably be some version system that says version of
physics vs version of everything else; replay could only record physics
version (maybe just two or three chars?)
- on 1st map the camera is obscured by the wall at the start, fix it somehow - on 1st map the camera is obscured by the wall at the start, fix it somehow
(not the best first impression) (not the best first impression)
- culling is very slow now, it showed that distance bailout can accelerate it - culling is very slow now, it showed that distance bailout can accelerate it

View file

@ -42,7 +42,11 @@ static const char *LCR_texts[] =
"exit", "exit",
#define LCR_TEXTS_LOADING 10 #define LCR_TEXTS_LOADING 10
"loading" "loading",
#define LCR_TEXTS_SAVED 11
"saved"
}; };
// TODO: define string for CLI arguments for frontends? // TODO: define string for CLI arguments for frontends?

383
game.h
View file

@ -264,6 +264,7 @@ static inline void LCR_gameDrawPixelXYSafe(unsigned int x, unsigned int y,
struct struct
{ {
uint8_t state; uint8_t state;
uint32_t statePrev;
uint32_t stateStartTime; uint32_t stateStartTime;
uint32_t time; ///< Current frame's time. uint32_t time; ///< Current frame's time.
uint32_t frame; ///< Current frame number. uint32_t frame; ///< Current frame number.
@ -401,6 +402,7 @@ static inline void LCR_gameDrawPixelXYSafe(unsigned int x, unsigned int y,
void LCR_gameSetState(uint8_t state) void LCR_gameSetState(uint8_t state)
{ {
LCR_LOG1("changing state"); LCR_LOG1("changing state");
LCR_game.statePrev = LCR_game.state;
LCR_game.state = state; LCR_game.state = state;
LCR_game.stateStartTime = LCR_game.time; LCR_game.stateStartTime = LCR_game.time;
} }
@ -1049,7 +1051,7 @@ void LCR_gameTimeToStr(uint32_t timeMS, char *str)
str[2] = '\''; str[2] = '\'';
} }
LCR_gameDrawPopupMessage(void) void LCR_gameDrawPopupMessage(void)
{ {
int textH = LCR_rendererComputeTextHeight(5); int textH = LCR_rendererComputeTextHeight(5);
int textW = LCR_rendererComputeTextWidth(LCR_game.popupStr,5); int textW = LCR_rendererComputeTextWidth(LCR_game.popupStr,5);
@ -1151,6 +1153,12 @@ void _LCR_gameDataCharWrite(char c)
printf("%c",c); printf("%c",c);
} }
void LCR_gameSaveReplay(void)
{
LCR_LOG0("saving replay");
LCR_replayOutputStr(_LCR_gameDataCharWrite);
}
/** /**
Helper subroutine, handles user input during main loop frame, EXCEPT for the Helper subroutine, handles user input during main loop frame, EXCEPT for the
driving input (that is handled in the loop itself). driving input (that is handled in the loop itself).
@ -1160,153 +1168,24 @@ void LCR_gameHandleInput(void)
int tabSwitchedTo = -1; int tabSwitchedTo = -1;
int scrolled = 0; int scrolled = 0;
switch (LCR_game.state) if (LCR_game.state != LCR_GAME_STATE_MENU)
{ {
case LCR_GAME_STATE_MENU: if (LCR_game.state == LCR_GAME_STATE_RUN_FINISHED)
if (LCR_game.keyStates[LCR_KEY_RIGHT] == 1) {
{
LCR_LOG1("menu tab right");
LCR_game.menu.selectedTab =
(LCR_game.menu.selectedTab + 1) % LCR_MENU_TABS;
tabSwitchedTo = LCR_game.menu.selectedTab;
LCR_game.menu.selectedItem = 0;
LCR_audioPlaySound(LCR_SOUND_CLICK);
}
else if (LCR_game.keyStates[LCR_KEY_LEFT] == 1)
{
LCR_LOG1("menu tab left");
LCR_game.menu.selectedTab =
(LCR_game.menu.selectedTab + LCR_MENU_TABS - 1) % LCR_MENU_TABS;
tabSwitchedTo = LCR_game.menu.selectedTab;
LCR_game.menu.selectedItem = 0;
LCR_audioPlaySound(LCR_SOUND_CLICK);
}
else if (LCR_game.keyStates[LCR_KEY_UP] == 1)
{
LCR_LOG1("menu item up");
if (LCR_game.menu.selectedItem != 0)
{
LCR_game.menu.selectedItem--;
LCR_audioPlaySound(LCR_SOUND_CLICK);
}
else if (LCR_game.menu.selectedTab != 0 &&
LCR_game.dataFile.firstItemIndex != 0)
{
LCR_game.menu.selectedItem = LCR_RESOURCE_ITEM_CHUNK - 1;
LCR_audioPlaySound(LCR_SOUND_CLICK);
scrolled = -1;
}
}
else if (LCR_game.keyStates[LCR_KEY_DOWN] == 1)
{
LCR_LOG1("menu item down");
if (LCR_game.menu.selectedTab == 0)
{
if (LCR_game.menu.selectedItem < 4)
{
LCR_game.menu.selectedItem++;
LCR_audioPlaySound(LCR_SOUND_CLICK);
}
}
else if (LCR_game.menu.selectedItem < LCR_game.menu.itemCount - 1)
{
LCR_game.menu.selectedItem++;
LCR_audioPlaySound(LCR_SOUND_CLICK);
}
else if (LCR_game.dataFile.firstItemIndex +
LCR_RESOURCE_ITEM_CHUNK < LCR_game.dataFile.itemsTotal)
{
LCR_game.menu.selectedItem = 0;
LCR_audioPlaySound(LCR_SOUND_CLICK);
scrolled = 1;
}
}
else if (LCR_game.keyStates[LCR_KEY_B] == 1 && LCR_currentMap.blockCount)
{
LCR_LOG1("menu quit");
LCR_gameSetState(LCR_GAME_STATE_RUN);
}
else if (LCR_game.keyStates[LCR_KEY_A] == 1)
{
LCR_LOG1("menu confirm");
LCR_audioPlaySound(LCR_SOUND_CLICK);
switch (LCR_game.menu.selectedTab)
{
case 0:
switch (LCR_game.menu.selectedItem)
{
case 0:
LCR_game.cameraMode = (LCR_game.cameraMode + 1) % 4;
LCR_rendererSetCarVisibility(
LCR_game.cameraMode != LCR_CAMERA_MODE_INSIDE);
LCR_rendererCameraReset();
LCR_gamePopupNumber(LCR_game.cameraMode);
break;
#if LCR_SETTING_MUSIC
case 1:
LCR_game.musicOn = !LCR_game.musicOn;
LCR_gamePopupNumber(LCR_game.musicOn);
break;
#endif
case 2:
LCR_audio.on = !LCR_audio.on;
LCR_gamePopupNumber(LCR_audio.on);
break;
case 4:
LCR_gameSetState(LCR_GAME_STATE_END);
break;
default: break;
}
LCR_gameLoadMainMenuItems();
break;
case 1: // maps
LCR_gameLoadMap(LCR_game.dataFile.firstItemIndex +
LCR_game.menu.selectedItem);
LCR_gameSetState(LCR_GAME_STATE_LOADING);
break;
case 2: // view replay
case 3: // play against replay
{
int mapIndex = LCR_gameLoadReplay(LCR_game.dataFile.firstItemIndex +
LCR_game.menu.selectedItem);
if (mapIndex < -1)
{
LCR_LOG1("couldn't load replay");
}
else if (mapIndex == -1)
{
LCR_LOG1("couldn't load replay map");
}
else
LCR_gameSetState(LCR_GAME_STATE_LOADING);
break;
}
default: break;
}
}
break;
case LCR_GAME_STATE_RUN_FINISHED:
if (LCR_game.keyStates[LCR_KEY_A] == 1) if (LCR_game.keyStates[LCR_KEY_A] == 1)
{
if (LCR_game.runTimeMS <= LCR_currentMap.targetTime * LCR_RACING_TICK_MS
&& !LCR_game.ghost.active)
{
LCR_LOG1("setting new target time");
LCR_currentMap.targetTime = LCR_game.runTimeMS / LCR_RACING_TICK_MS;
}
LCR_gameResetRun(LCR_racing.playingReplay,LCR_game.ghost.active); LCR_gameResetRun(LCR_racing.playingReplay,LCR_game.ghost.active);
}
break; }
else if (LCR_game.state == LCR_GAME_STATE_RUN_STARTING)
case LCR_GAME_STATE_RUN_STARTING: {
if (LCR_game.time - LCR_game.stateStartTime if (LCR_game.time - LCR_game.stateStartTime
>= 1000 * LCR_SETTING_COUNTDOWN_SECONDS) >= 1000 * LCR_SETTING_COUNTDOWN_SECONDS)
{ {
@ -1316,53 +1195,195 @@ void LCR_gameHandleInput(void)
else else
LCR_gamePopupNumber(LCR_SETTING_COUNTDOWN_SECONDS - LCR_gamePopupNumber(LCR_SETTING_COUNTDOWN_SECONDS -
(LCR_game.time - LCR_game.stateStartTime) / 1000); (LCR_game.time - LCR_game.stateStartTime) / 1000);
}
// fall through if (LCR_game.keyStates[LCR_KEY_B] == 1)
default: {
if (LCR_game.keyStates[LCR_KEY_B] == 1) LCR_LOG1("menu open");
LCR_gameSetState(LCR_GAME_STATE_MENU);
LCR_game.menu.selectedItem = 0;
}
else if (LCR_game.cameraMode == LCR_CAMERA_MODE_FREE)
{
LCR_GameUnit offsets[5];
for (int i = 0; i < 5; ++i)
offsets[i] = 0;
if (LCR_game.keyStates[LCR_KEY_A])
{
if (LCR_game.keyStates[LCR_KEY_UP])
offsets[4] = LCR_FREE_CAMERA_TURN_STEP;
else if (LCR_game.keyStates[LCR_KEY_DOWN])
offsets[4] -= LCR_FREE_CAMERA_TURN_STEP;
if (LCR_game.keyStates[LCR_KEY_RIGHT])
offsets[3] -= LCR_FREE_CAMERA_TURN_STEP;
else if (LCR_game.keyStates[LCR_KEY_LEFT])
offsets[3] = LCR_FREE_CAMERA_TURN_STEP;
}
else
{
if (LCR_game.keyStates[LCR_KEY_UP])
offsets[0] = LCR_FREE_CAMERA_STEP;
else if (LCR_game.keyStates[LCR_KEY_DOWN])
offsets[0] -= LCR_FREE_CAMERA_STEP;
if (LCR_game.keyStates[LCR_KEY_RIGHT])
offsets[1] = LCR_FREE_CAMERA_STEP;
else if (LCR_game.keyStates[LCR_KEY_LEFT])
offsets[1] -= LCR_FREE_CAMERA_STEP;
}
LCR_rendererMoveCamera(offsets,offsets + 3);
}
else if (LCR_game.keyStates[LCR_KEY_A] == 1)
LCR_gameResetRun(LCR_racing.playingReplay,LCR_game.ghost.active);
}
else // LCR_GAME_STATE_MENU
{
if (LCR_game.keyStates[LCR_KEY_RIGHT] == 1)
{
LCR_LOG1("menu tab right");
LCR_game.menu.selectedTab =
(LCR_game.menu.selectedTab + 1) % LCR_MENU_TABS;
tabSwitchedTo = LCR_game.menu.selectedTab;
LCR_game.menu.selectedItem = 0;
LCR_audioPlaySound(LCR_SOUND_CLICK);
}
else if (LCR_game.keyStates[LCR_KEY_LEFT] == 1)
{
LCR_LOG1("menu tab left");
LCR_game.menu.selectedTab =
(LCR_game.menu.selectedTab + LCR_MENU_TABS - 1) % LCR_MENU_TABS;
tabSwitchedTo = LCR_game.menu.selectedTab;
LCR_game.menu.selectedItem = 0;
LCR_audioPlaySound(LCR_SOUND_CLICK);
}
else if (LCR_game.keyStates[LCR_KEY_UP] == 1)
{
LCR_LOG1("menu item up");
if (LCR_game.menu.selectedItem != 0)
{
LCR_game.menu.selectedItem--;
LCR_audioPlaySound(LCR_SOUND_CLICK);
}
else if (LCR_game.menu.selectedTab != 0 &&
LCR_game.dataFile.firstItemIndex != 0)
{
LCR_game.menu.selectedItem = LCR_RESOURCE_ITEM_CHUNK - 1;
LCR_audioPlaySound(LCR_SOUND_CLICK);
scrolled = -1;
}
}
else if (LCR_game.keyStates[LCR_KEY_DOWN] == 1)
{
LCR_LOG1("menu item down");
if (LCR_game.menu.selectedTab == 0)
{
if (LCR_game.menu.selectedItem < 4)
{
LCR_game.menu.selectedItem++;
LCR_audioPlaySound(LCR_SOUND_CLICK);
}
}
else if (LCR_game.menu.selectedItem < LCR_game.menu.itemCount - 1)
{
LCR_game.menu.selectedItem++;
LCR_audioPlaySound(LCR_SOUND_CLICK);
}
else if (LCR_game.dataFile.firstItemIndex +
LCR_RESOURCE_ITEM_CHUNK < LCR_game.dataFile.itemsTotal)
{ {
LCR_LOG1("menu open");
LCR_gameSetState(LCR_GAME_STATE_MENU);
LCR_game.menu.selectedItem = 0; LCR_game.menu.selectedItem = 0;
LCR_audioPlaySound(LCR_SOUND_CLICK);
scrolled = 1;
} }
else if (LCR_game.cameraMode == LCR_CAMERA_MODE_FREE) }
else if (LCR_game.keyStates[LCR_KEY_B] == 1 && LCR_currentMap.blockCount)
{
LCR_LOG1("menu quit");
LCR_gameSetState(LCR_game.statePrev);
}
else if (LCR_game.keyStates[LCR_KEY_A] == 1)
{
LCR_LOG1("menu confirm");
LCR_audioPlaySound(LCR_SOUND_CLICK);
switch (LCR_game.menu.selectedTab)
{ {
LCR_GameUnit offsets[5]; case 0:
switch (LCR_game.menu.selectedItem)
{
case 0:
LCR_game.cameraMode = (LCR_game.cameraMode + 1) % 4;
LCR_rendererSetCarVisibility(
LCR_game.cameraMode != LCR_CAMERA_MODE_INSIDE);
LCR_rendererCameraReset();
LCR_gamePopupNumber(LCR_game.cameraMode);
break;
for (int i = 0; i < 5; ++i) #if LCR_SETTING_MUSIC
offsets[i] = 0; case 1:
LCR_game.musicOn = !LCR_game.musicOn;
LCR_gamePopupNumber(LCR_game.musicOn);
break;
#endif
case 2:
LCR_audio.on = !LCR_audio.on;
LCR_gamePopupNumber(LCR_audio.on);
break;
if (LCR_game.keyStates[LCR_KEY_A]) case 3:
if (LCR_game.statePrev == LCR_GAME_STATE_RUN_FINISHED)
{
LCR_gameSaveReplay();
LCR_gamePopupMessage(LCR_texts[LCR_TEXTS_SAVED]);
}
break;
case 4:
LCR_gameSetState(LCR_GAME_STATE_END);
break;
default: break;
}
LCR_gameLoadMainMenuItems();
break;
case 1: // maps
LCR_gameLoadMap(LCR_game.dataFile.firstItemIndex +
LCR_game.menu.selectedItem);
LCR_gameSetState(LCR_GAME_STATE_LOADING);
break;
case 2: // view replay
case 3: // play against replay
{ {
if (LCR_game.keyStates[LCR_KEY_UP]) int mapIndex = LCR_gameLoadReplay(LCR_game.dataFile.firstItemIndex +
offsets[4] = LCR_FREE_CAMERA_TURN_STEP; LCR_game.menu.selectedItem);
else if (LCR_game.keyStates[LCR_KEY_DOWN])
offsets[4] -= LCR_FREE_CAMERA_TURN_STEP;
if (LCR_game.keyStates[LCR_KEY_RIGHT]) if (mapIndex < -1)
offsets[3] -= LCR_FREE_CAMERA_TURN_STEP; {
else if (LCR_game.keyStates[LCR_KEY_LEFT]) LCR_LOG1("couldn't load replay");
offsets[3] = LCR_FREE_CAMERA_TURN_STEP; }
} else if (mapIndex == -1)
else {
{ LCR_LOG1("couldn't load replay map");
if (LCR_game.keyStates[LCR_KEY_UP]) }
offsets[0] = LCR_FREE_CAMERA_STEP; else
else if (LCR_game.keyStates[LCR_KEY_DOWN]) LCR_gameSetState(LCR_GAME_STATE_LOADING);
offsets[0] -= LCR_FREE_CAMERA_STEP;
if (LCR_game.keyStates[LCR_KEY_RIGHT]) break;
offsets[1] = LCR_FREE_CAMERA_STEP;
else if (LCR_game.keyStates[LCR_KEY_LEFT])
offsets[1] -= LCR_FREE_CAMERA_STEP;
} }
LCR_rendererMoveCamera(offsets,offsets + 3); default: break;
} }
else if (LCR_game.keyStates[LCR_KEY_A] == 1) }
LCR_gameResetRun(LCR_racing.playingReplay,LCR_game.ghost.active);
break;
} }
if (tabSwitchedTo == 0) if (tabSwitchedTo == 0)
@ -1441,7 +1462,9 @@ uint8_t LCR_gameStep(uint32_t time)
LCR_game.state != LCR_GAME_STATE_RUN_FINISHED) LCR_game.state != LCR_GAME_STATE_RUN_FINISHED)
{ {
LCR_LOG1("finished"); LCR_LOG1("finished");
LCR_replayOutputStr(_LCR_gameDataCharWrite);
if (LCR_game.runTimeMS <= LCR_currentMap.targetTime * LCR_RACING_TICK_MS)
LCR_gameSaveReplay();
LCR_audioPlaySound(LCR_SOUND_CLICK); LCR_audioPlaySound(LCR_SOUND_CLICK);
LCR_gameSetState(LCR_GAME_STATE_RUN_FINISHED); LCR_gameSetState(LCR_GAME_STATE_RUN_FINISHED);

2
map.h
View file

@ -164,7 +164,7 @@ struct
uint8_t checkpointCount; uint8_t checkpointCount;
uint32_t hash; ///< Hash of the processed binary map. uint32_t hash; ///< Hash of the processed binary map.
uint32_t targetTime; uint32_t targetTime; ///< Target time in physics ticks.
char name[LCR_MAP_NAME_MAX_LEN + 1]; char name[LCR_MAP_NAME_MAX_LEN + 1];
} LCR_currentMap; } LCR_currentMap;

View file

@ -30,7 +30,7 @@
typedef int32_t LCR_GameUnit; ///< abstract game unit typedef int32_t LCR_GameUnit; ///< abstract game unit
#define LCR_GAME_UNIT 2048 //1024 ///< length of map square in LCR_GameUnits #define LCR_GAME_UNIT 2048 ///< length of map square in LCR_GameUnits
#define LCR_RACING_INPUT_FORW 0x01 #define LCR_RACING_INPUT_FORW 0x01
#define LCR_RACING_INPUT_RIGHT 0x02 #define LCR_RACING_INPUT_RIGHT 0x02