diff --git a/TODO.txt b/TODO.txt index 4e8ab61..fb588a7 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,7 +1,9 @@ +fuck issue trackers :D + =========== GENERAL ============== +- make some kinda repo for world record runs? - fix the ramp map again due to new physics -- should drifting make a sound? - keyboard ghosting is an issue, particularly when initiating drift with brake (arrow keys must be used with S for braking for left drift to work) -- think about what to do about this? or leave as is? @@ -11,46 +13,11 @@ its velocity by a proportion of car's velocity change (this minus prev. frame), then offset car body by this. However we'll also have to transform inbetween world space and model space. -- Consider better input handling in SDL? Currently it just detects presses on - the exact frame, so a press can be missed. But how tho? - try to add distance fog? -- use 332 in SDL with potato? -- option to turn on simple 332 colors? - c99 may impose limit 4095 chars on str literal, gives warning on internal data file, try to somehow hack around it (maybe just convert it to an array in the end?) Maybe this: make a standalone C file with the string in it that when compiled and run outputs the array. -- culling is very slow now, it showed that distance bailout can accelerate it - a lot, try to make a more accurate bailaout and see if it's faster (watch - out for bugs due to false positives) -- maps to make: - - there should be these maps: - - compiled in: - - standard maps, let's say 5? - - additionally a few (5?) extremely simple maps, compile time option to - only include these, for very weak devices with limited block/triangle - count - - bonus maps in the external file (also 5?) - - map types: - - purely falling map DONE - - traveling salesman kind of maze, with fans n shit KINDA DONE - - city map? DONE - - some kinda buggy bumpy downhill DONE - - map where car starts upside down DONE - - dirt map DONE - - ice map - - boss map DONE - - text or picture from blocks? DONE - - chessboard from different materials KINDA DONE - - falling tube map? DONE - - grass hills as decoration? DONE - - something with multiple roads DONE - - RPG kinda map? could be in bonus maps KINDA DONE - - some speed map (mostly flat to prevent high speed bugs) DONE - - some (at least partially) interior map KINDA DONE - - something with multiple finishes DONE - - U-ramp to build speed and jump up to catch a CP (done) DONE - - jump through air ring with CP DONE - replay validation? - final tests: - very long replay @@ -66,6 +33,8 @@ - correct saving or replays etc - empty and large data file - FPS on each platform + - try to use the racing module by itself + - doxygen documentation =========== BUGS ================= @@ -78,7 +47,16 @@ =========== HANDLED ============== +- should drifting make a sound? NO NEED +- Consider better input handling in SDL? Currently it just detects presses on + the exact frame, so a press can be missed. But how tho? LOOKS FINE, it's + only an issue with extremely low FPS, at which point it's unplayable anyway +- use 332 in SDL with potato? +- option to turn on simple 332 colors? - MAP4: one triangle in top section is missing! +- culling is very slow now, it showed that distance bailout can accelerate it + a lot, try to make a more accurate bailaout and see if it's faster (watch + out for bugs due to false positives) PROLLY FINE - when non-rotating, the car is very fast, allowing uberbugs: find out why and fix - allow slowing down in air like in TM? @@ -253,6 +231,34 @@ - camera behavior? what if car is riding upside down (on magnet) etc? <- looks cool now - allow car to be flipped upside down on start? with start block transform - track size: 64x64x64 +- maps to make: + - there should be these maps: + - compiled in: + - standard maps, let's say 5? + - additionally a few (5?) extremely simple maps, compile time option to + only include these, for very weak devices with limited block/triangle + count + - bonus maps in the external file (also 5?) + - map types: + - purely falling map DONE + - traveling salesman kind of maze, with fans n shit KINDA DONE + - city map? DONE + - some kinda buggy bumpy downhill DONE + - map where car starts upside down DONE + - dirt map DONE + - ice map + - boss map DONE + - text or picture from blocks? DONE + - chessboard from different materials KINDA DONE + - falling tube map? DONE + - grass hills as decoration? DONE + - something with multiple roads DONE + - RPG kinda map? could be in bonus maps KINDA DONE + - some speed map (mostly flat to prevent high speed bugs) DONE + - some (at least partially) interior map KINDA DONE + - something with multiple finishes DONE + - U-ramp to build speed and jump up to catch a CP (done) DONE + - jump through air ring with CP DONE - sky images could be just composed of 4x4 normal images? then we only need one type of image TOTAL SIZE OF TEXTURES: diff --git a/assets.h b/assets.h index a54c429..07b4963 100644 --- a/assets.h +++ b/assets.h @@ -448,7 +448,7 @@ static const char *LCR_internalDataFile = ":^D1bJ:f11i:^J1bL:f11i" ":^D0bJ-:f11i:^J0bL-:f11i" ":^E1s:f511" - ":>E0k:f513" + ":>E0k:f514" // slope: ":^C2vI-:f611" ":^p3v:fj11" @@ -7066,9 +7066,9 @@ void LCR_imageChangeBrightness(int up) Samples currently loaded image at given pixels coordinates (with wrapping). This is slower than reading the image pixel by pixel. */ -uint16_t LCR_sampleImage(int x, int y) +uint16_t LCR_sampleImage(int_fast32_t x, int_fast32_t y) { - // TODO: bottleneck, later on optimize here + // bottleneck here, optimization will increase rendering performance x = (y % LCR_IMAGE_SIZE) * LCR_IMAGE_SIZE + (x % LCR_IMAGE_SIZE); x += (x < 0) * (LCR_IMAGE_SIZE * LCR_IMAGE_SIZE); return LCR_currentImage.palette[LCR_currentImage.image[x]]; diff --git a/data b/data index 9b55766..329b012 100644 --- a/data +++ b/data @@ -151,3 +151,8 @@ details #BLCbonus2; #RLCtiny2;00LCtiny2;833ee4b2 0000262:0014:0a10:0031:00d0:0074 #RLCtiny4;00LCtiny4;1787f12a 0000233:0121:0083:0091:0073:0061:0043:0031:0033:0041:0033:0061:00a3:0142:01b1:0129:0061:0159:0031:00f9:0051 +#RLCtiny1;00LCtiny1;ae1ab677 0000327:0011:0159:0031:00d3:0041:0039:0051:0113:0021:0099:0041:0129:00d1:0153:0041:0073:0051:0043:0041:0023:0071:0023:0021:0055:0031:0013:00c1:0033:01a1:00c9:0031:01a5:0031:01b3:0041:00e9:0031:0043:0041:0049 +#RLC1;00LC1;8bd6e314 0000566:0011:0169:0031:0109:0081:0039:0051:0039:0051:0039:0051:00b9:0011:0069:0031:0119:0091:0025:0041:0083:0132:0033:0361:00d9:0101:00d9:0081:0269:0021:00a9:0021:01a5:0031:01f3:0031:0103:0031:0023:00b1:0033:00c1:0023:0071:0063:0021:0053:0071:0189:0021 +#BLC1; +#RLC1;00LC1;8bd6e314 0000527:0011:0149:0041:0039:0031:00b9:0031:0079:00a1:02d9:0081:0035:0031:0049:0271:0073:00a1:0043:0041:0099:0061:00a9:0091:0119:0031:0063:0041:0079:0031:0145:0041:0053:0171:00d3:0041:0083:0041:0043:00e1:0023:00a1:01c9:0081:0083:0051:0099:0011:0399:0031:00f3:0031:0069:0031 +#RLC1;00LC1;8bd6e314 0000523:0011:0139:0031:0159:00a1:0049:0031:00a9:0061:0089:0041:0049:0021:0119:0071:0025:0031:0039:01e1:0043:00a1:0059:0031:0073:0021:0049:0061:0059:0041:0043:01e1:0089:0061:00b9:0021:00d9:0021:0453:0141:00b3:0091:0119:0031:00c9:0041:03b9:0021:0093:0031:00f9:0021:0039:0021 diff --git a/frontend_sdl.c b/frontend_sdl.c index 1a396c7..beaf2ce 100644 --- a/frontend_sdl.c +++ b/frontend_sdl.c @@ -9,7 +9,7 @@ #define DATA_FILE_NAME "data" #define LCR_LOADING_COMMAND SDL_PumpEvents(); -// #define LCR_FPS_GET_MS SDL_GetTicks() // uncomment for FPS measuring +#define LCR_FPS_GET_MS SDL_GetTicks() // uncomment for FPS measuring #include "game.h" diff --git a/game.h b/game.h index 223fd61..4e4b71b 100644 --- a/game.h +++ b/game.h @@ -1076,10 +1076,8 @@ void LCR_gameInit(int argc, const char **argv) case 'R': quickLoad = 2; break; case 'P': quickLoad = 3; break; - // TODO - default: - LCR_LOG2("unknown argument"); + LCR_LOG1("unknown argument"); break; } } @@ -1199,7 +1197,7 @@ void LCR_gameTimeToStr(uint32_t timeMS, char *str) void LCR_gameDrawPopupMessage(void) { #define _TEXT_SIZE 5 -#define _OFFSET_V (LCR_EFFECTIVE_RESOLUTION_Y / 6) +#define _OFFSET_V (LCR_EFFECTIVE_RESOLUTION_Y / 8) int textH = LCR_rendererComputeTextHeight(_TEXT_SIZE); int textW = LCR_rendererComputeTextWidth(LCR_game.popupStr,_TEXT_SIZE); diff --git a/media/manual.txt b/media/manual.txt index c92383d..a3f7e7f 100644 --- a/media/manual.txt +++ b/media/manual.txt @@ -2,29 +2,30 @@ WORK IN PROGRESS '-._-'"'-_.-'"'-._.-'"'-._.-'"'- LICAR MANUAL -'"'-._.-'"'-._.-'"'-._.-'"'-._.-' - libre racing video game by drummyfish + libre racing video game by drummyfish (drummyfish@disroot.org) released under CC0 1.0, public domain ~~~~~ GENERAL ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Licar is a relatively simple 3D stunt racing game inspired by other popular -games of the genre, such as Trackmania and Stunts. Unlike mainstream video -games, Licar is completely libre, i.e. free as in freedom (meaning its source -code and art assets are available for any use whatsoever), gratis (free of cost) -and its focus lies in being well programmed by adhering to minimalism and -rejecting harmful "modern" programming practices. The game aims to seflessly -bring joy and entertainment to all the people that might enjoy it, even those -who aren't able or willing to pay and/or watch ads, those owning very old and -weak computers and so on. It was made in whole by a single man as a completely -non-commercial product, the development was driven purely by love of the craft -and other living beings. Licar is also more than a game, for example it may -serve educational purposes or become a basis for new projects. +Licar is a relatively simple 3D stunt racing video game inspired by other +popular games of the genre, such as Trackmania and Stunts. Unlike mainstream +video games (even idie ones), Licar is completely libre, i.e. free as in freedom +(meaning its source code and art assets are available for any use whatsoever), +gratis (free of cost) and its focus lies in being well programmed by adhering to +minimalism and rejecting harmful "modern" programming practices. The game aims +to seflessly bring joy and entertainment to all the people that might enjoy it, +even those who aren't able or willing to pay and/or watch ads, those owning very +old and weak computers and so on. It was made in whole by a single man as a +completely non-commercial program, the development was driven purely by love of +the craft and other living beings to whom it might serve. Licar is also more +than a game, for example it may serve educational purposes or become a basis for +new projects. The game runs on many platforms and comes in different versions depending on what the platforms allow. Some versions may have more features or visual "richness" than others. If anything mentioned in this manual is missing in your -game, it's probably because of limitations of your platform. On PCs and laptops -however everything should be supported. +game, it's probably because of limitations of your platform. On PCs and laptops, +however, everything should be supported. ~~~~~ RUNNING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -304,13 +305,18 @@ frustration but unless you want to fix this yourself, you'll have to just accept it, the game is meant to be a simple entertainment. In other words this is a feature :) +Q: I found a bug or have some other important comment. +A: Send me an email (found on top of this file). + Q: I have some other question (such as "Why is this not written in a modern language?" or "What inspired you to make the game?" etc.) -A: Many questions I often get asked about my life philosophy and opinions can't -now be answered shortly. I have a website at http://www.tastyfish.cz. If you -don't find the answer there, my email is currently drummyfish@disroot.org, feel -free to ask me anything, but please remember I am not very social and don't -enjoy too much engaging in smalltalk or lengthy discussions about worldviews -etcetc. +A: Many questions I often get asked about my life and/or programming philosophy +can't now be answered in a tl;dr way without doing them injustice. I have a +website at http://www.tastyfish.cz, where I attempt to explain things, but +please note you will probably not like it. If you still decide to follow the +link and don't find your answer, feel to ask me anything over email, but please +remember I am not very social and don't enjoy too much engaging in smalltalk or +lengthy discussions about worldviews etcetc. I am certainly not interested in +having my opinions changed or changing someone else's mind by force. diff --git a/racing.h b/racing.h index b3ba6ba..0e38edd 100644 --- a/racing.h +++ b/racing.h @@ -6,10 +6,15 @@ Licar: racing module This implements the racing physics and logic as well as replays and other - related things. It's possible to use this module alone if one wants to + related stuff. It's possible to use this module alone if one wants to implement a program that doesn't need graphics, I/O etc. Some comments: + - This module uses tinyphysicsengine, a small and simple physics engine that + uses "balls and springs" to model bodies (here the car) and kind of + "signed distance functions" to model environment (the map). The car is + strictly a soft body, but it's very "stiff" so that it behaves almost like + a rigid body. - Replays are internally stored as follows: the replay consists of 16 bit words representing changes in input at specific frame. In lowest 4 bits the new input state is recorded, the remaining 12 bits record physics frame @@ -91,8 +96,6 @@ typedef int32_t LCR_GameUnit; ///< abstract game unit #define LCR_CAR_GRASS_FACTOR 5 #define LCR_CAR_DIRT_FACTOR 3 #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_DRIFT_THRESHOLD_1 (LCR_GAME_UNIT / 10) @@ -218,7 +221,6 @@ void LCR_replayOutputStr(void (*printChar)(char)) #define PUTD(order) \ printChar('0' + (LCR_racing.replay.achievedTime / order) % 10); - PUTD(1000000) PUTD(100000) PUTD(10000) PUTD(1000) PUTD(100) PUTD(10) PUTD(1) #undef PUTD @@ -252,7 +254,7 @@ int LCR_replayLoadFromStr(char (*nextChar)(void), LCR_racing.replay.eventCount = 0; LCR_racing.replay.achievedTime = 0; - // has to be like this to force correct evaluation order: + // Has to be like this to force correct evaluation order: c = nextChar() == LCR_RACING_VERSION1; c |= (nextChar() == LCR_RACING_VERSION2) << 1; @@ -431,8 +433,10 @@ int LCR_replayRecordEvent(uint32_t frame, uint8_t input) } /** - Helper function for _LCR_racingEnvironmentFunction, returns closest point on - a map block placed at coordinate origin. + Helper function for _LCR_racingEnvironmentFunction, for given arbitrary point + returns the closest point on map block (of given type) placed at coordinate + origin (and in default orientation). This function defines shapes of map + blocks. */ TPE_Vec3 _LCR_racingBlockEnvFunc(TPE_Vec3 point, const uint8_t *block) { @@ -758,7 +762,7 @@ TPE_Vec3 _LCR_racingBlockEnvFunc(TPE_Vec3 point, const uint8_t *block) /** For tinyphysicsengine, function that defines the shape of the static physics - world, returns closest point to any given point in space. + world. For any given point in space returns the environment's closest point. */ TPE_Vec3 _LCR_racingEnvironmentFunction(TPE_Vec3 point, TPE_Unit maxDist) { @@ -821,7 +825,7 @@ TPE_Vec3 _LCR_racingEnvironmentFunction(TPE_Vec3 point, TPE_Unit maxDist) TPE_ENV_NEXT(_LCR_racingBlockEnvFunc(point, // check it LCR_currentMap.blocks + blockNum * LCR_BLOCK_SIZE),point) - // narrow the search range: + // Narrow the search range: if (i % 2 == 0 && blockNum > start) start = blockNum; @@ -869,8 +873,7 @@ uint8_t _LCR_racingCollisionHandler(uint16_t b1, uint16_t j1, uint16_t b2, LCR_racing.carBody.joints[j1].velocity[0], LCR_racing.carBody.joints[j1].velocity[1], LCR_racing.carBody.joints[j1].velocity[2]), - TPE_vec3Minus(p, - LCR_racing.carBody.joints[j1].position))); + TPE_vec3Minus(p,LCR_racing.carBody.joints[j1].position))); LCR_racing.crashState |= ((speed >= LCR_CAR_CRASH_SPEED_BIG) << 1) | (speed >= LCR_CAR_CRASH_SPEED_SMALL); @@ -1527,10 +1530,10 @@ uint32_t LCR_racingStep(unsigned int input) { LCR_LOG2("roof squeezed, applying anti force") - TPE_Vec3 tmpVec = TPE_vec3Times(carUp,LCR_PHYSICS_UNIT / 16); // TODO: 16 magic con. + TPE_Vec3 tmpVec = + TPE_vec3Times(carUp,LCR_PHYSICS_UNIT / 16); // 16: magic const. angle = TPE_F - 4 * angle; // 4 comes from above TPE_F / 4 - tmpVec = TPE_vec3Times(tmpVec,angle); if (angle <= 0) @@ -1540,7 +1543,7 @@ uint32_t LCR_racingStep(unsigned int input) angle = 0; } - // accelerate roof and wheels away from each other + // Accelerate roof and wheels away from each other: for (int i = 0; i < LCR_CAR_JOINTS; ++i) { LCR_racing.carBody.joints[i].velocity[0] += (i == 4 ? 1 : -1) * tmpVec.x; diff --git a/renderer.h b/renderer.h index ea90ab2..109b0f2 100644 --- a/renderer.h +++ b/renderer.h @@ -10,6 +10,9 @@ Some comments: + - The module uses small3dlib, a tiny software rasterization library. This + module knows nothing about I/O (windows, canvases, ...), it just say where + to draw pixels and what colors they should have. - The map 3D model is divided into 4x4x4 chunks, i.e. 64 in total, out of which only 8 are loaded at any time, depending on where the camera is and where it is looking. This is to save resources, we don't draw the far away @@ -17,6 +20,10 @@ - Extremely simple LOD of far away chunks is implemented: we keep an 8x8x8 bit array of where there is empty space and where there is "something", then for far away areas with "something" we just draw some 2D rectangles. + - RENDERING IS THE BOTTLENECK OF PERFORMANCE, it takes even much more time + than physics simulation, i.e. care should be taken to make code here very + optimized, namely the _LCR_pixelFunc3D function, as it is called for every + rasterized pixel. */ #define S3L_RESOLUTION_X LCR_EFFECTIVE_RESOLUTION_X @@ -108,12 +115,12 @@ struct // pixel function precomputed values: uint32_t previousTriID; int triUVs[6]; - int texSubsampleCount; - unsigned int flatAndTransparent; /**< If non-zero, transparent (dithered) - polygons will be drawn without texture, - with color stored in this variable. */ + uint_fast16_t texSubsampleCount; + uint_fast16_t flatAndTransparent; /**< If non-zero, transparent (dithered) + polygons will be drawn without texture, + with color stored in this variable. */ #if LCR_SETTING_PARTICLES - uint16_t particleColor; /**< 0x0000 means no particles active. */ + uint_fast16_t particleColor; /**< 0x0000 means no particles active. */ #endif } LCR_renderer; @@ -338,7 +345,7 @@ void _LCR_pixelFunc3D(S3L_PixelInfo *pixel) LCR_gameDrawPixelXYUnsafe(pixel->x,pixel->y,LCR_renderer.flatAndTransparent); #else // LCR_SETTING_POTATO_GRAPHICS - // once we get a new triangle, we precompute things for it: + // New triangle? Precompute stuff for it: if (pixel->triangleID != LCR_renderer.previousTriID) { LCR_renderer.previousTriID = pixel->triangleID; @@ -483,10 +490,14 @@ void _LCR_pixelFunc3D(S3L_PixelInfo *pixel) } } + /* Bottleneck: code from here below will be ran for every rasterized pixel, + optimizing it may significantly improve rendering performance. */ + if (LCR_renderer.flatAndTransparent) { - if (pixel->x % 2 == pixel->y % 2) - LCR_gameDrawPixelXYUnsafe(pixel->x,pixel->y,LCR_renderer.flatAndTransparent); + if ((pixel->x % 2) == (pixel->y % 2)) + LCR_gameDrawPixelXYUnsafe( + pixel->x,pixel->y,LCR_renderer.flatAndTransparent); else S3L_zBufferWrite(pixel->x,pixel->y,S3L_MAX_DEPTH); /* ^ Clear z-buffer if we didn't draw the pixel. Without this further @@ -1034,8 +1045,7 @@ uint8_t _LCR_buildMapModel(void) LCR_IMAGE_WALL_CONCRETE : LCR_IMAGE_WALL_WOOD; } else - { // TODO: tidy this mess? - + { if (LCR_mapBlockIsAccelerator(blockType)) triData |= LCR_IMAGE_GROUND_ACCEL; else if (LCR_mapBlockIsFan(blockType))