This commit is contained in:
Miloslav Ciz 2025-01-28 22:27:19 +01:00
parent e695921c5c
commit 6986028e33
4 changed files with 171 additions and 155 deletions

22
audio.h
View file

@ -1,5 +1,10 @@
/** /*
audio: this file implements the audio system. Licar audio
This file implements the audio system. The module only computes audio samples
(playing them must be done by the frontend). The audio format is 8bit, 8 KHz
mono. This module does NOT deal with music (music is stored in a separate
file, left to be optionally loaded and played by the frontend).
*/ */
#ifndef _LCR_AUDIO #ifndef _LCR_AUDIO
@ -26,6 +31,9 @@ struct
int engineInc; int engineInc;
} LCR_audio; } LCR_audio;
/**
Initializes the audio system, only call once.
*/
void LCR_audioInit(void) void LCR_audioInit(void)
{ {
LCR_LOG0("initializing audio"); LCR_LOG0("initializing audio");
@ -40,11 +48,18 @@ void LCR_audioInit(void)
LCR_audio.engineIntensity = 0; LCR_audio.engineIntensity = 0;
} }
/**
Sets the intensity of car engine sound that's constantly played (unless when
intensitiy has been set to 0). Maximum value is 255.
*/
void LCR_audioSetEngineIntensity(uint8_t value) void LCR_audioSetEngineIntensity(uint8_t value)
{ {
LCR_audio.engineIntensity = value; LCR_audio.engineIntensity = value;
} }
/**
Tells the audio system to play certain sound (see the sound constants).
*/
void LCR_audioPlaySound(uint8_t sound) void LCR_audioPlaySound(uint8_t sound)
{ {
LCR_LOG2("playing sound"); LCR_LOG2("playing sound");
@ -58,6 +73,9 @@ uint8_t _LCR_audioNoise(void)
return LCR_audio.noise >> 16; return LCR_audio.noise >> 16;
} }
/**
Gets the next audio sample.
*/
uint8_t LCR_audioGetNextSample(void) uint8_t LCR_audioGetNextSample(void)
{ {
unsigned char result = 128; unsigned char result = 128;

98
game.h
View file

@ -8,6 +8,9 @@
graphics, sound etc., and is meant to be included and used by specific graphics, sound etc., and is meant to be included and used by specific
frontends (which will handle each platform's hardware details and I/O). See frontends (which will handle each platform's hardware details and I/O). See
the frontend info below for help with porting the game to a new platform. the frontend info below for help with porting the game to a new platform.
This module (with the help of other modules) will perform all the computations
(physics, graphics, audio, ...) and only use the frontend's quite primitive
functions to actually present the results to the user.
The code uses LCR_ (or _LCR) prefix as a kind of namespace preventing The code uses LCR_ (or _LCR) prefix as a kind of namespace preventing
collision with 3rd party identifiers. collision with 3rd party identifiers.
@ -59,19 +62,21 @@
/* /*
FOR FRONTENDS (porting to other platforms): FOR FRONTENDS (porting to other platforms):
- Implement the below described functions according to their description. - Implement the frontend functions given below according to their description.
- Implement the main program and game loop. - Implement the main program and game loop.
- Call the below described functions as described. - Call some of the frontend funtions given below in your main program in
- If you want to support music, make your frontend play music from the "music" places as described in the description of the function.
file in assets. It is in raw format, storing 8bit unsigned samples at 8000 - If you want to support music, make your code load and play the "music"
Hz. Use the LCR_gameMusicOn to check what the music volume is. If you file in the asset directory. It is in raw format, storing 8bit unsigned
don't support music, set LCR_SETTING_MUSIC to 0 in your frontend code so samples at 8 KHz mono. Use the LCR_gameMusicOn function to check whether the
that the game knows music is disabled. music is currently enabled (if not, stop playing it). If you don't want to
support music, set LCR_SETTING_MUSIC to 0 in your frontend code (before
including this module) so that the game knows music is disabled.
*/ */
/** /**
Implement this in your frontend. Returns 1 if given key is pressed or 0 Implement this in your frontend. Returns 1 if given key (see LCR_KEY_*
otherwise. constants) is pressed or 0 otherwise.
*/ */
uint8_t LCR_keyPressed(uint8_t key); uint8_t LCR_keyPressed(uint8_t key);
@ -87,35 +92,40 @@ void LCR_sleep(uint16_t timeMs);
to the screen back buffer (i.e. NOT directly to screen, back buffer shall to the screen back buffer (i.e. NOT directly to screen, back buffer shall
only be copied to front buffer once the LCR_gameStep function finishes all only be copied to front buffer once the LCR_gameStep function finishes all
rendering). This function should NOT check for out-of-screen coordinates, this rendering). This function should NOT check for out-of-screen coordinates, this
is handled by the game internals and out-of-screen pixels will never be drawn. is handled by the game internals and out-of-screen pixels will never be drawn
The color value depends on game settings but is normally an RGB565 value. (your code unnecessarily checking it again would likely slow down rendering a
lot, as drawing pixels is a bottleneck). The color value depends on game
settings but is normally an RGB565 value. The index parameter says the
coordinate at which to write the pixel, assuming the pixels are stored by
rows, from top to bottom.
*/ */
void LCR_drawPixel(unsigned long index, uint16_t color); void LCR_drawPixel(unsigned long index, uint16_t color);
/** /**
Implement this in your frontend. This function will be called to log what the Implement this in your frontend. This function will be called to log what the
program is doing. If you want to ignore logging, simply make the function do program is doing. Typically this function should print the string to console.
nothing. If you want to ignore logging, simply make the function do nothing.
*/ */
void LCR_log(const char *str); void LCR_log(const char *str);
/** /**
Implement this in your frontend. This function serves for loading optional Implement this in your frontend. This function serves for loading optional
data file that allows to add more maps, replays etc. If your frontend data file that allows adding more maps, replays etc. If your frontend won't
won't support this, just make the function return 0. Otherwise it must return support this, just make the function return 0. Otherwise it must return
characters from the data file one by one; after reaching the end of file characters from the data file one by one; after reaching the end of file 0
0 must be returned and the reading position will be reset to start again. must be returned and the reading position will be reset to start from
beginning again.
*/ */
char LCR_getNextDataFileChar(void); char LCR_getNextDataFileChar(void);
/** /**
Implement this in your frontend. This serves to store data in the optional Implement this in your frontend. This serves to store data in the optional
data file, e.g. replays. If your frontend doesn't support this (e.g. data file (e.g. replays and info about beaten maps). If your frontend doesn't
because the file is read only), the function may ignore the append, but if support this (e.g. because the file is read only), the function may ignore the
the file is otherwise supported, a rewind of the read position must still be append, but if the file is otherwise supported, a rewind of the read position
done. If appending is supported, the function must append the provided string must still be performed. If appending is supported, the function must append
to the data file AND then reset the data file reading position back to the provided string to the data file AND then reset the data file reading
the start. position back to the start.
*/ */
void LCR_appendDataStr(const char *str); void LCR_appendDataStr(const char *str);
@ -134,18 +144,22 @@ void LCR_gameEnd(void);
Call this function in your frontend repeatedly inside the main loop, pass the Call this function in your frontend repeatedly inside the main loop, pass the
current time as the number of milliseconds since program start. This function current time as the number of milliseconds since program start. This function
will perform the game step AND other things such as checking the input states, will perform the game step AND other things such as checking the input states,
rendering or sleeping (all using the above functions you should implement). rendering or sleeping (all using the above described functions you should
Returns 0 if program should end, otherwise 1. implement). Returns 0 if program should end, otherwise 1.
*/ */
uint8_t LCR_gameStep(uint32_t timeMs); uint8_t LCR_gameStep(uint32_t timeMs);
/** /**
Gets the current music volume; Returns 1 if music is currently on, else 0. If your frontend supports music,
use this function at each frame to know whether you should start/stop playing
music. Otherwise ignore this.
*/ */
uint8_t LCR_gameMusicOn(void); uint8_t LCR_gameMusicOn(void);
/** /**
Gets next audio sample (unsigned 8bit samples, 8 KHz). Gets the next audio sample (unsigned 8bit samples, 8 KHz mono). If your
frontend supports sound, call this continuously and play the stream of
samples, otherwise ignore this.
*/ */
uint8_t LCR_gameGetNextAudioSample(void); uint8_t LCR_gameGetNextAudioSample(void);
@ -187,7 +201,7 @@ uint8_t LCR_gameGetNextAudioSample(void);
// forward decls of pixel drawing functions for the renderer // forward decls of pixel drawing functions for the renderer
/** /**
Internal pixel drawing function that draws pixel at specified screen coords Internal pixel drawing function that puts a pixel at specified screen coords
without checking for safety (it's faster but can only be done if we know for without checking for safety (it's faster but can only be done if we know for
sure we're not drawing outside the screen). sure we're not drawing outside the screen).
*/ */
@ -213,7 +227,7 @@ static inline void LCR_drawPixelXYSafe(unsigned int x, unsigned int y,
#if LCR_SETTING_GHOST_MAX_SAMPLES == 0 #if LCR_SETTING_GHOST_MAX_SAMPLES == 0
#undef LCR_MENU_TABS #undef LCR_MENU_TABS
#define LCR_MENU_TABS 3 #define LCR_MENU_TABS 3 // no ghosts => remove the tab for ghosts
#endif #endif
#define LCR_MENU_STRING_SIZE 16 #define LCR_MENU_STRING_SIZE 16
@ -241,18 +255,17 @@ struct
{ {
uint8_t state; uint8_t state;
uint32_t stateStartTime; uint32_t stateStartTime;
uint32_t time; uint32_t time; ///< Current frame's time.
uint32_t frame; uint32_t frame; ///< Current frame number.
uint32_t nextRenderFrameTime; uint32_t nextRenderFrameTime; ///< At which frame to render next frame.
uint32_t nextRacingTickTime; uint32_t nextRacingTickTime; ///< When to simulate next physics tick.
uint8_t cameraMode; uint8_t cameraMode;
uint8_t debugDraw;
uint8_t musicOn; uint8_t musicOn;
uint8_t keyStates[LCR_KEYS_TOTAL]; /**< Assures unchanging key states uint8_t keyStates[LCR_KEYS_TOTAL]; /**< Assures unchanging key states
during a single frame, hold number of during a single frame, hold number of
frames for which the key has been frames for which the key has been
continuously held. */ continuously held. */
uint32_t runTimeMS; /**< Current time of the run */ uint32_t runTimeMS; ///< Current time of the run
struct struct
{ {
@ -260,14 +273,14 @@ struct
uint8_t selectedItem; uint8_t selectedItem;
uint8_t itemCount; uint8_t itemCount;
char itemNames[LCR_MENU_MAX_ITEMS][LCR_MENU_STRING_SIZE]; char itemNames[LCR_MENU_MAX_ITEMS][LCR_MENU_STRING_SIZE];
const char *itemNamePtrs[LCR_MENU_MAX_ITEMS]; ///< helper array const char *itemNamePtrs[LCR_MENU_MAX_ITEMS]; ///< Helper array.
} menu; } menu;
struct struct
{ {
int state; ///< -1 if reading external res. f., else pos. int state; ///< -1 if reading external data f., else pos.
// indices and counts are among the data of the same type // Indices and counts are among the data of the same type.
unsigned int firstItemIndex; unsigned int firstItemIndex;
unsigned int itemsTotal; unsigned int itemsTotal;
} dataFile; } dataFile;
@ -286,7 +299,6 @@ struct
is to allow ghosts for even long replays. */ is to allow ghosts for even long replays. */
} ghost; } ghost;
#endif #endif
} LCR_game; } LCR_game;
uint8_t LCR_gameMusicOn(void) uint8_t LCR_gameMusicOn(void)
@ -448,7 +460,7 @@ void _LCR_gamePrepareGhost(void)
LCR_game.ghost.stretch = 0; LCR_game.ghost.stretch = 0;
while (((int) LCR_replay.achievedTime) > while (((int) LCR_racing.replay.achievedTime) >
(LCR_SETTING_GHOST_STEP << LCR_game.ghost.stretch) * (LCR_SETTING_GHOST_STEP << LCR_game.ghost.stretch) *
LCR_SETTING_GHOST_MAX_SAMPLES) LCR_SETTING_GHOST_MAX_SAMPLES)
{ {
@ -780,7 +792,7 @@ void LCR_gameInit(int argc, const char **argv)
LCR_game.menu.selectedItem = 0; LCR_game.menu.selectedItem = 0;
LCR_game.frame = 0; LCR_game.frame = 0;
LCR_game.musicOn = 1; LCR_game.musicOn = LCR_SETTING_MUSIC;
LCR_game.nextRenderFrameTime = 0; LCR_game.nextRenderFrameTime = 0;
LCR_game.nextRacingTickTime = 0; LCR_game.nextRacingTickTime = 0;
LCR_game.cameraMode = LCR_CAMERA_MODE_DRIVE; LCR_game.cameraMode = LCR_CAMERA_MODE_DRIVE;
@ -1176,9 +1188,11 @@ void LCR_gameHandleInput(void)
LCR_rendererCameraReset(); LCR_rendererCameraReset();
break; break;
#if LCR_SETTING_MUSIC
case 1: case 1:
LCR_game.musicOn = !LCR_game.musicOn; LCR_game.musicOn = !LCR_game.musicOn;
break; break;
#endif
case 2: case 2:
LCR_audio.on = !LCR_audio.on; LCR_audio.on = !LCR_audio.on;

20
map.h
View file

@ -94,7 +94,7 @@
#define LCR_BLOCK_RAMP_34 '/' ///< plain ramp, 3/4 size #define LCR_BLOCK_RAMP_34 '/' ///< plain ramp, 3/4 size
#define LCR_BLOCK_RAMP_12 '<' ///< plain ramp, 1/2 size #define LCR_BLOCK_RAMP_12 '<' ///< plain ramp, 1/2 size
#define LCR_BLOCK_RAMP_14 '_' ///< plain ramp, 1/4 size #define LCR_BLOCK_RAMP_14 '_' ///< plain ramp, 1/4 size
#define LCR_BLOCK_RAMP_CORNER 'v' ///< corner of ramp #define LCR_BLOCK_RAMP_CORNER 'v' ///< corner of a ramp
#define LCR_BLOCK_RAMP_CURVED_PLAT ']' ///< curved ramp with top platgform #define LCR_BLOCK_RAMP_CURVED_PLAT ']' ///< curved ramp with top platgform
#define LCR_BLOCK_RAMP_CURVED ')' ///< curv. ramp without top platf. #define LCR_BLOCK_RAMP_CURVED ')' ///< curv. ramp without top platf.
#define LCR_BLOCK_RAMP_CURVED_WALL '}' ///< curved ramp plus small wall #define LCR_BLOCK_RAMP_CURVED_WALL '}' ///< curved ramp plus small wall
@ -103,6 +103,8 @@
#define LCR_BLOCK_CORNER_12 '\\' ///< diagonal corner (1/2 wide) #define LCR_BLOCK_CORNER_12 '\\' ///< diagonal corner (1/2 wide)
#define LCR_BLOCK_HILL '(' ///< curved "hill" #define LCR_BLOCK_HILL '(' ///< curved "hill"
#define LCR_BLOCK_BUMP '~' ///< small bump on the road #define LCR_BLOCK_BUMP '~' ///< small bump on the road
#define LCR_BLOCK_CORNER_CONVEX 'n' ///< curved corner (convex)
#define LCR_BLOCK_CORNER_CONCAVE 'l' ///< curved corner (concave)
#define LCR_BLOCK_FULL_ACCEL '>' #define LCR_BLOCK_FULL_ACCEL '>'
#define LCR_BLOCK_BOTTOM_ACCEL 'z' #define LCR_BLOCK_BOTTOM_ACCEL 'z'
@ -111,9 +113,6 @@
#define LCR_BLOCK_FULL_FAN 'o' #define LCR_BLOCK_FULL_FAN 'o'
#define LCR_BLOCK_RAMP_FAN 'V' #define LCR_BLOCK_RAMP_FAN 'V'
#define LCR_BLOCK_CORNER_CONVEX 'n'
#define LCR_BLOCK_CORNER_CONCAVE 'l'
#define LCR_BLOCK_CHECKPOINT_0 '+' ///< checkpoint, not taken #define LCR_BLOCK_CHECKPOINT_0 '+' ///< checkpoint, not taken
#define LCR_BLOCK_CHECKPOINT_1 '\'' ///< checkpoint, taken #define LCR_BLOCK_CHECKPOINT_1 '\'' ///< checkpoint, taken
@ -131,15 +130,10 @@
/* /*
TODO: TODO:
- ramp corner???
- curved corner?
- curved out corner?
- curved "hill"
- bumpy road
- bigger structures like a loop, sloped road etc? - bigger structures like a loop, sloped road etc?
*/ */
#define LCR_MAP_BLOCK_CACHE_SIZE (8 * 2) /// do not change #define LCR_MAP_BLOCK_CACHE_SIZE (8 * 2) ///< Do not change.
/** /**
Cache for accelerating LCR_mapGetBlockAtFast, consists of 8 2-item records, Cache for accelerating LCR_mapGetBlockAtFast, consists of 8 2-item records,
@ -154,12 +148,12 @@ struct
{ {
uint16_t blockCount; uint16_t blockCount;
uint8_t blocks[LCR_SETTING_MAP_MAX_BLOCKS * LCR_BLOCK_SIZE]; uint8_t blocks[LCR_SETTING_MAP_MAX_BLOCKS * LCR_BLOCK_SIZE];
uint8_t startPos[4]; ///< Initial position and rotation. uint8_t startPos[4]; ///< Initial position and rotation.
uint8_t environment; uint8_t environment;
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;
char name[LCR_MAP_NAME_MAX_LEN + 1]; char name[LCR_MAP_NAME_MAX_LEN + 1];
@ -249,7 +243,7 @@ void LCR_rampGetDimensions(uint8_t rampType, uint8_t *height4ths,
uint8_t *LCR_getMapBlockAtCoordNumber(uint32_t coord) uint8_t *LCR_getMapBlockAtCoordNumber(uint32_t coord)
{ {
// binary search the block: // Binary search the block:
uint16_t a = 0, b = LCR_currentMap.blockCount - 1; uint16_t a = 0, b = LCR_currentMap.blockCount - 1;

186
racing.h
View file

@ -43,8 +43,8 @@ typedef int32_t LCR_GameUnit; ///< abstract game unit
#define LCR_PHYSICS_UNIT 4096 ///< len. of square for phys. engine #define LCR_PHYSICS_UNIT 4096 ///< len. of square for phys. engine
#define TPE_RESHAPE_TENSION_LIMIT 3 #define TPE_RESHAPE_TENSION_LIMIT 3
#define TPE_RESHAPE_ITERATIONS 8 #define TPE_RESHAPE_ITERATIONS 8
#include "general.h" #include "general.h"
#include "map.h" #include "map.h"
@ -69,10 +69,10 @@ typedef int32_t LCR_GameUnit; ///< abstract game unit
#define LCR_CAR_CRASH_SPEED_SMALL 400 #define LCR_CAR_CRASH_SPEED_SMALL 400
#define LCR_CAR_CRASH_SPEED_BIG 800 #define LCR_CAR_CRASH_SPEED_BIG 800
// multipliers (in 8ths) of friction and acceleration on concrete: // Multipliers (in 8ths) of friction and acceleration on concrete:
#define LCR_CAR_GRASS_FACTOR 5 #define LCR_CAR_GRASS_FACTOR 5
#define LCR_CAR_DIRT_FACTOR 3 #define LCR_CAR_DIRT_FACTOR 3
#define LCR_CAR_ICE_FACTOR 1 #define LCR_CAR_ICE_FACTOR 1
#define LCR_CAR_DRIFT_FACTOR 2 ///< only affects steering friction #define LCR_CAR_DRIFT_FACTOR 2 ///< only affects steering friction
#define LCR_CAR_JOINTS 5 #define LCR_CAR_JOINTS 5
@ -87,7 +87,7 @@ struct
TPE_Joint carJoints[LCR_CAR_JOINTS]; TPE_Joint carJoints[LCR_CAR_JOINTS];
TPE_Connection carConnections[LCR_CAR_CONNECTIONS]; TPE_Connection carConnections[LCR_CAR_CONNECTIONS];
uint32_t tick; uint32_t tick; ///< Physics tick (frame) number.
uint8_t wheelCollisions; /**< In individual bits records for each car wheel uint8_t wheelCollisions; /**< In individual bits records for each car wheel
whether it's currently touching the ground. whether it's currently touching the ground.
Lower bits record current collisions, higher Lower bits record current collisions, higher
@ -111,19 +111,19 @@ struct
uint16_t crashState; uint16_t crashState;
uint8_t playingReplay; uint8_t playingReplay;
struct
{
uint16_t eventCount;
uint16_t events[LCR_SETTING_REPLAY_MAX_SIZE];
// for playing
uint16_t currentEvent;
uint16_t currentFrame;
uint32_t achievedTime;
} replay;
} LCR_racing; } LCR_racing;
struct
{
uint16_t eventCount;
uint16_t events[LCR_SETTING_REPLAY_MAX_SIZE];
// for playing
uint16_t currentEvent;
uint16_t currentFrame;
uint32_t achievedTime;
} LCR_replay; // TODO: move inside LCR_racing?
/** /**
Gets times of the run in milliseconds. Gets times of the run in milliseconds.
*/ */
@ -144,15 +144,15 @@ TPE_Vec3 _LCR_TPE_vec3DividePlain(TPE_Vec3 v, TPE_Unit d)
void LCR_replayInitRecording(void) void LCR_replayInitRecording(void)
{ {
LCR_LOG1("initializing replay recording"); LCR_LOG1("initializing replay recording");
LCR_replay.eventCount = 0; LCR_racing.replay.eventCount = 0;
LCR_replay.achievedTime = 0; LCR_racing.replay.achievedTime = 0;
} }
void LCR_replayInitPlaying(void) void LCR_replayInitPlaying(void)
{ {
LCR_LOG1("initializing replay playing"); LCR_LOG1("initializing replay playing");
LCR_replay.currentEvent = 0; LCR_racing.replay.currentEvent = 0;
LCR_replay.currentFrame = 0; LCR_racing.replay.currentFrame = 0;
} }
/** /**
@ -185,14 +185,16 @@ void LCR_replayOutputStr(void (*printChar)(char))
// 8 decimal digits are enough to record 24 hours // 8 decimal digits are enough to record 24 hours
#define PUTD(order) printChar('0' + (LCR_replay.achievedTime / order) % 10); #define PUTD(order) \
printChar('0' + (LCR_racing.replay.achievedTime / order) % 10);
PUTD(10000000) PUTD(1000000) PUTD(100000) PUTD(10000) PUTD(10000000) PUTD(1000000) PUTD(100000) PUTD(10000)
PUTD(1000) PUTD(100) PUTD(10) PUTD(1) PUTD(1000) PUTD(100) PUTD(10) PUTD(1)
#undef PUTD #undef PUTD
for (int i = 0; i < LCR_replay.eventCount; ++i) for (int i = 0; i < LCR_racing.replay.eventCount; ++i)
{ {
uint16_t e = LCR_replay.events[i]; uint16_t e = LCR_racing.replay.events[i];
printChar(':'); printChar(':');
for (int j = 0; j < 4; ++j) for (int j = 0; j < 4; ++j)
@ -208,51 +210,35 @@ void LCR_replayOutputStr(void (*printChar)(char))
/** /**
Reads replay from string using provided function that returns next character Reads replay from string using provided function that returns next character
in the string. The mapHash and nameHash pointers are optional: if non-zero, in the string. The mapHash and nameHash pointers are optional: if non-zero,
they will be filled with the map hash and name hash. Returns 1 on success, the memory they point to will be filled with the map hash and name hash.
else 0. Returns 1 on success, else 0.
*/ */
int LCR_replayLoadFromStr(char (*nextChar)(void), int LCR_replayLoadFromStr(char (*nextChar)(void),
uint32_t *mapHash, uint16_t *nameHash) uint32_t *mapHash, uint16_t *nameHash)
{ {
char c = ' '; char c = ' ';
LCR_replay.eventCount = 0; LCR_racing.replay.eventCount = 0;
LCR_replay.achievedTime = 0; LCR_racing.replay.achievedTime = 0;
if (nameHash) if (nameHash)
*nameHash = _LCR_simpleStrHash(nextChar,';'); *nameHash = _LCR_simpleStrHash(nextChar,';');
else else
_LCR_simpleStrHash(nextChar,';'); _LCR_simpleStrHash(nextChar,';');
/* if (mapHash)
do // map name *mapHash = 0;
for (int i = 0; i < 8; ++i) // hash
{ {
c = nextChar(); c = nextChar();
if (c == 0) if (_LCR_hexDigitVal(c) < 0)
return 0; return 0;
} while (c != ';');
*/
if (mapHash) if (mapHash)
*mapHash = 0; *mapHash = ((*mapHash) << 4) | _LCR_hexDigitVal(c);
}
for (int i = 0; i < 8; ++i) // hash
{
c = nextChar();
if (_LCR_hexDigitVal(c) < 0)
return 0;
if (mapHash)
*mapHash = ((*mapHash) << 4) | _LCR_hexDigitVal(c);
}
/*
for (int i = 0; i < 8; ++i) // hash
if (_LCR_hexDigitVal(nextChar()) < 0)
return 0;
*/
nextChar(); nextChar();
@ -263,7 +249,8 @@ for (int i = 0; i < 8; ++i) // hash
if (c < '0' || c > '9') if (c < '0' || c > '9')
break; break;
LCR_replay.achievedTime = LCR_replay.achievedTime * 10 + c - '0'; LCR_racing.replay.achievedTime =
LCR_racing.replay.achievedTime * 10 + c - '0';
} }
while (c != 0) // events while (c != 0) // events
@ -278,11 +265,11 @@ for (int i = 0; i < 8; ++i) // hash
if (e == 0) if (e == 0)
break; break;
if (LCR_replay.eventCount >= LCR_SETTING_REPLAY_MAX_SIZE) if (LCR_racing.replay.eventCount >= LCR_SETTING_REPLAY_MAX_SIZE)
return 0; return 0;
LCR_replay.events[LCR_replay.eventCount] = e; LCR_racing.replay.events[LCR_racing.replay.eventCount] = e;
LCR_replay.eventCount++; LCR_racing.replay.eventCount++;
} }
c = nextChar(); c = nextChar();
@ -292,60 +279,61 @@ for (int i = 0; i < 8; ++i) // hash
} }
/** /**
During playing of a replay returns the next input and shifts to next frame. When playing back a replay this function returns the next recorded input and
shifts to the next frame.
*/ */
uint8_t LCR_replayGetNextInput(void) uint8_t LCR_replayGetNextInput(void)
{ {
if (LCR_replay.currentEvent >= LCR_replay.eventCount) if (LCR_racing.replay.currentEvent >= LCR_racing.replay.eventCount)
{ {
LCR_replay.currentFrame++; // has to be here LCR_racing.replay.currentFrame++; // has to be here
return 0; return 0;
} }
if (LCR_replay.currentFrame == if (LCR_racing.replay.currentFrame ==
(LCR_replay.events[LCR_replay.currentEvent] >> 4)) (LCR_racing.replay.events[LCR_racing.replay.currentEvent] >> 4))
{ {
LCR_replay.currentEvent++; LCR_racing.replay.currentEvent++;
LCR_replay.currentFrame = 0; LCR_racing.replay.currentFrame = 0;
} }
LCR_replay.currentFrame++; LCR_racing.replay.currentFrame++;
return LCR_replay.currentEvent ? return LCR_racing.replay.currentEvent ?
(LCR_replay.events[LCR_replay.currentEvent - 1] & 0x0f) : 0; (LCR_racing.replay.events[LCR_racing.replay.currentEvent - 1] & 0x0f) : 0;
} }
int LCR_replayHasFinished(void) int LCR_replayHasFinished(void)
{ {
if (LCR_replay.currentEvent == LCR_replay.eventCount) if (LCR_racing.replay.currentEvent == LCR_racing.replay.eventCount)
{ {
uint32_t totalTime = LCR_replay.currentFrame; uint32_t totalTime = LCR_racing.replay.currentFrame;
for (int i = 0; i < LCR_replay.eventCount; ++i) for (int i = 0; i < LCR_racing.replay.eventCount; ++i)
totalTime += LCR_replay.events[i] >> 4; totalTime += LCR_racing.replay.events[i] >> 4;
return totalTime >= LCR_replay.achievedTime; return totalTime >= LCR_racing.replay.achievedTime;
} }
return LCR_replay.currentEvent > LCR_replay.eventCount; return LCR_racing.replay.currentEvent > LCR_racing.replay.eventCount;
} }
/** /**
Records another input event. Returns 1 on success, or 0 if the event couldn't Records another input event into a replay. Returns 1 on success, or 0 if the
be recorded. The event is only added if necessary, i.e. this function can event couldn't be recorded. The event is only added if necessary, i.e. this
(and must) be called every frame without worrying about inflating its size. function can, and MUST, be called every frame without worrying about inflating
When the run ends, the LCR_REPLAY_EVENT_END has to be fed! its size. When the run ends, the LCR_REPLAY_EVENT_END has to be fed!
*/ */
int LCR_replayRecordEvent(uint32_t frame, uint8_t input) int LCR_replayRecordEvent(uint32_t frame, uint8_t input)
{ {
LCR_LOG2("recording replay event"); LCR_LOG2("recording replay event");
if (LCR_replay.achievedTime) if (LCR_racing.replay.achievedTime)
return 1; // already finished return 1; // already finished
if (input == LCR_REPLAY_EVENT_END) if (input == LCR_REPLAY_EVENT_END)
{ {
LCR_replay.achievedTime = frame; LCR_racing.replay.achievedTime = frame;
LCR_LOG1("replay recording finished"); LCR_LOG1("replay recording finished");
return 1; return 1;
} }
@ -354,10 +342,10 @@ int LCR_replayRecordEvent(uint32_t frame, uint8_t input)
uint8_t previousInput = 0; uint8_t previousInput = 0;
uint32_t previousFrame = 0; uint32_t previousFrame = 0;
for (int i = 0; i < LCR_replay.eventCount; ++i) for (int i = 0; i < LCR_racing.replay.eventCount; ++i)
{ {
previousInput = LCR_replay.events[i] & 0x0f; previousInput = LCR_racing.replay.events[i] & 0x0f;
previousFrame += LCR_replay.events[i] >> 4; previousFrame += LCR_racing.replay.events[i] >> 4;
} }
if (input == previousInput) if (input == previousInput)
@ -368,30 +356,30 @@ int LCR_replayRecordEvent(uint32_t frame, uint8_t input)
frame -= previousFrame; // convert to offset frame -= previousFrame; // convert to offset
while (frame > 4095 && LCR_replay.eventCount < LCR_SETTING_REPLAY_MAX_SIZE) while (frame > 4095 && LCR_racing.replay.eventCount < LCR_SETTING_REPLAY_MAX_SIZE)
{ {
// add intermediate events // add intermediate events
frame -= 4095; frame -= 4095;
previousFrame += 4095; previousFrame += 4095;
LCR_replay.events[LCR_replay.eventCount] = LCR_racing.replay.events[LCR_racing.replay.eventCount] =
(previousFrame << 4) | previousInput; (previousFrame << 4) | previousInput;
LCR_replay.eventCount++; LCR_racing.replay.eventCount++;
} }
if (LCR_replay.eventCount >= LCR_SETTING_REPLAY_MAX_SIZE) if (LCR_racing.replay.eventCount >= LCR_SETTING_REPLAY_MAX_SIZE)
return 0; return 0;
LCR_replay.events[LCR_replay.eventCount] = (frame << 4) | (input & 0x0f); LCR_racing.replay.events[LCR_racing.replay.eventCount] = (frame << 4) | (input & 0x0f);
LCR_replay.eventCount++; LCR_racing.replay.eventCount++;
#endif #endif
return 1; return 1;
} }
/** /**
Helper function for _LCR_racingEnvironmentFunction, returns closest point Helper function for _LCR_racingEnvironmentFunction, returns closest point on
on a map block placed at coordinate origin. a map block placed at coordinate origin.
*/ */
TPE_Vec3 _LCR_racingBlockEnvFunc(TPE_Vec3 point, const uint8_t *block) TPE_Vec3 _LCR_racingBlockEnvFunc(TPE_Vec3 point, const uint8_t *block)
{ {
@ -691,8 +679,8 @@ TPE_Vec3 _LCR_racingBlockEnvFunc(TPE_Vec3 point, const uint8_t *block)
} }
/** /**
For tinyphysicsengine, function that defines the shape of physics world, For tinyphysicsengine, function that defines the shape of the static physics
returns closest point to any given point in space. world, returns closest point to any given point in space.
*/ */
TPE_Vec3 _LCR_racingEnvironmentFunction(TPE_Vec3 point, TPE_Unit maxDist) TPE_Vec3 _LCR_racingEnvironmentFunction(TPE_Vec3 point, TPE_Unit maxDist)
{ {
@ -795,10 +783,10 @@ LCR_GameUnit LCR_racingGetCarSpeedSigned(void)
uint8_t _LCR_racingCollisionHandler(uint16_t b1, uint16_t j1, uint16_t b2, uint8_t _LCR_racingCollisionHandler(uint16_t b1, uint16_t j1, uint16_t b2,
uint16_t j2, TPE_Vec3 p) uint16_t j2, TPE_Vec3 p)
{ {
// check which wheels are touching the ground.
#if LCR_SETTING_CRASH_SOUNDS #if LCR_SETTING_CRASH_SOUNDS
TPE_Unit speed = TPE_vec3Len( // detect crashes // Detect crashes:
TPE_Unit speed = TPE_vec3Len(
TPE_vec3Project( TPE_vec3Project(
TPE_vec3( TPE_vec3(
LCR_racing.carBody.joints[j1].velocity[0], LCR_racing.carBody.joints[j1].velocity[0],
@ -811,6 +799,8 @@ uint8_t _LCR_racingCollisionHandler(uint16_t b1, uint16_t j1, uint16_t b2,
(speed >= LCR_CAR_CRASH_SPEED_SMALL); (speed >= LCR_CAR_CRASH_SPEED_SMALL);
#endif #endif
// Check which wheels are touching the ground:
if (j1 < 4) // wheel joint? if (j1 < 4) // wheel joint?
LCR_racing.wheelCollisions |= 0x01 << j1; LCR_racing.wheelCollisions |= 0x01 << j1;