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 "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.
- 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

View file

@ -1,5 +1,10 @@
=========== 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
(not the best first impression)
- 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",
#define LCR_TEXTS_LOADING 10
"loading"
"loading",
#define LCR_TEXTS_SAVED 11
"saved"
};
// 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
{
uint8_t state;
uint32_t statePrev;
uint32_t stateStartTime;
uint32_t time; ///< Current frame's time.
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)
{
LCR_LOG1("changing state");
LCR_game.statePrev = LCR_game.state;
LCR_game.state = state;
LCR_game.stateStartTime = LCR_game.time;
}
@ -1049,7 +1051,7 @@ void LCR_gameTimeToStr(uint32_t timeMS, char *str)
str[2] = '\'';
}
LCR_gameDrawPopupMessage(void)
void LCR_gameDrawPopupMessage(void)
{
int textH = LCR_rendererComputeTextHeight(5);
int textW = LCR_rendererComputeTextWidth(LCR_game.popupStr,5);
@ -1151,6 +1153,12 @@ void _LCR_gameDataCharWrite(char 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
driving input (that is handled in the loop itself).
@ -1160,153 +1168,24 @@ void LCR_gameHandleInput(void)
int tabSwitchedTo = -1;
int scrolled = 0;
switch (LCR_game.state)
if (LCR_game.state != LCR_GAME_STATE_MENU)
{
case 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_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.state == LCR_GAME_STATE_RUN_FINISHED)
{
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);
break;
case LCR_GAME_STATE_RUN_STARTING:
}
}
else if (LCR_game.state == LCR_GAME_STATE_RUN_STARTING)
{
if (LCR_game.time - LCR_game.stateStartTime
>= 1000 * LCR_SETTING_COUNTDOWN_SECONDS)
{
@ -1316,53 +1195,195 @@ void LCR_gameHandleInput(void)
else
LCR_gamePopupNumber(LCR_SETTING_COUNTDOWN_SECONDS -
(LCR_game.time - LCR_game.stateStartTime) / 1000);
}
// fall through
default:
if (LCR_game.keyStates[LCR_KEY_B] == 1)
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_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)
offsets[i] = 0;
#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;
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])
offsets[4] = LCR_FREE_CAMERA_TURN_STEP;
else if (LCR_game.keyStates[LCR_KEY_DOWN])
offsets[4] -= LCR_FREE_CAMERA_TURN_STEP;
int mapIndex = LCR_gameLoadReplay(LCR_game.dataFile.firstItemIndex +
LCR_game.menu.selectedItem);
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 (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);
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;
break;
}
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)
@ -1441,7 +1462,9 @@ uint8_t LCR_gameStep(uint32_t time)
LCR_game.state != LCR_GAME_STATE_RUN_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_gameSetState(LCR_GAME_STATE_RUN_FINISHED);

2
map.h
View file

@ -164,7 +164,7 @@ struct
uint8_t checkpointCount;
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];
} LCR_currentMap;

View file

@ -30,7 +30,7 @@
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_RIGHT 0x02