diff --git a/CMakeLists.txt b/CMakeLists.txt index 06f1426..c125193 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,8 +26,8 @@ set(SOURCES src/MathF16.cpp src/GameCanvas.cpp src/GamePhysics.cpp - src/TimerOrMotoPartOrMenuElem.cpp - src/class_10.cpp + src/PhysicsElemOrMenuItem.cpp + src/MotoComponent.cpp src/GameLevel.cpp src/LevelLoader.cpp src/Micro.cpp diff --git a/src/GameCanvas.cpp b/src/GameCanvas.cpp index 46aec65..ff3686e 100644 --- a/src/GameCanvas.cpp +++ b/src/GameCanvas.cpp @@ -42,10 +42,10 @@ void GameCanvas::drawSprite(Graphics* g, int spriteNo, int x, int y) } } -void GameCanvas::requestRepaint(int var1) +void GameCanvas::requestRepaint(int state) { - field_184 = var1; - if (var1 == 0) { + loadingScreenState = state; + if (state == 0) { splashImage = nullptr; logoImage = nullptr; } else { @@ -54,9 +54,9 @@ void GameCanvas::requestRepaint(int var1) } } -void GameCanvas::method_124(bool var1) +void GameCanvas::setInputConfigEnabled(bool enabled) { - field_205 = var1; + unknown_bool = enabled; // Set but never read - possibly dead code updateSizeAndRepaint(); } @@ -85,7 +85,7 @@ int GameCanvas::loadSprites(int flags) fenderImage = nullptr; engineImage = nullptr; } - + if (flags & 2) { if (!bodyPartsImages[1]) { bodyPartsImages[1] = std::make_unique("blueleg.png"); @@ -112,9 +112,9 @@ int GameCanvas::loadSprites(int flags) return flags; } -void GameCanvas::method_129() +void GameCanvas::reset() { - method_164(); + resetActiveKeys(); } void GameCanvas::setViewPosition(int dx, int dy) @@ -175,17 +175,19 @@ void GameCanvas::renderBodyPart(int x1F16, int y1F16, int x2F16, int y2F16, int } } -void GameCanvas::method_142(int var1, int var2, int var3, int var4) +void GameCanvas::drawWheelHub(int centerX, int centerY, int radius, int angleF16) { - ++var3; - int var5 = addDx(var1 - var3); - int var6 = addDy(var2 + var3); - int var7 = var3 << 1; - if ((var4 = -((int)(((int64_t)((int)((int64_t)var4 * 11796480L >> 16)) << 32) / 205887L >> 16))) < 0) { - var4 += 360; + // Draw arc representing wheel hub/spokes at specified position and angle + ++radius; // Slight radius adjustment + int screenX = addDx(centerX - radius); + int screenY = addDy(centerY + radius); + int diameter = radius << 1; + // Convert angle from fixed-point to degrees and draw 90-degree arc + if ((angleF16 = -((int)(((int64_t)((int)((int64_t)angleF16 * 11796480L >> 16)) << 32) / 205887L >> 16))) < 0) { + angleF16 += 360; } - graphics->drawArc(var5, var6, var7, var7, (var4 >> 16) + 170, 90); + graphics->drawArc(screenX, screenY, diameter, diameter, (angleF16 >> 16) + 170, 90); } void GameCanvas::drawCircle(int x, int y, int size) @@ -198,26 +200,26 @@ void GameCanvas::drawCircle(int x, int y, int size) void GameCanvas::fillRect(int x, int y, int w, int h) { - int var5 = addDx(x); - int var6 = addDy(y); - graphics->fillRect(var5, var6, w, h); + int screenX = addDx(x); + int screenY = addDy(y); + graphics->fillRect(screenX, screenY, w, h); } -void GameCanvas::drawForthSpriteByCenter(int centerX, int centerY) +void GameCanvas::drawAttachmentPointSprite(int x, int y) { int halfSizeX = spriteSizeX[4] / 2; int halfSizeY = spriteSizeY[4] / 2; - drawSprite(graphics, 4, addDx(centerX - halfSizeX), addDy(centerY + halfSizeY)); + drawSprite(graphics, 4, addDx(x - halfSizeX), addDy(y + halfSizeY)); } void GameCanvas::drawHelmet(int x, int y, int angleF16) { - int var4 = calcSpriteNo(angleF16, -102943, 411774, 32, true); + int spriteNo = calcSpriteNo(angleF16, -102943, 411774, 32, true); if (helmetImage != nullptr) { - int var5 = addDx(x) - helmetSpriteWidth / 2; - int var6 = addDy(y) - helmetSpriteHeight / 2; - graphics->setClip(var5, var6, helmetSpriteWidth, helmetSpriteHeight); - graphics->drawImage(helmetImage.get(), var5 - helmetSpriteWidth * (var4 % 6), var6 - helmetSpriteHeight * (var4 / 6), 20); + int screenX = addDx(x) - helmetSpriteWidth / 2; + int screenY = addDy(y) - helmetSpriteHeight / 2; + graphics->setClip(screenX, screenY, helmetSpriteWidth, helmetSpriteHeight); + graphics->drawImage(helmetImage.get(), screenX - helmetSpriteWidth * (spriteNo % 6), screenY - helmetSpriteHeight * (spriteNo / 6), 20); graphics->setClip(0, 0, width, getHeight()); } } @@ -260,18 +262,19 @@ void GameCanvas::drawTime(int64_t time10Ms) } } -void GameCanvas::method_150(int var1) +void GameCanvas::triggerTimerIfMatching(int timerId) { - if (timerId == var1) { + if (this->timerId == timerId) { timerTriggered = true; } } -void GameCanvas::method_151() +void GameCanvas::flagAnimation() { - field_226 += 655; - int var0 = 32768 + ((MathF16::sinF16(field_226) < 0 ? -MathF16::sinF16(field_226) : MathF16::sinF16(field_226)) >> 1); - flagAnimationTime += (int)(6553L * (int64_t)var0 >> 16); + // Update flag animation phase (fixed-point, incremented by 655 each frame) + flagAnimationPhase += 655; + int amplitude = 32768 + ((MathF16::sinF16(flagAnimationPhase) < 0 ? -MathF16::sinF16(flagAnimationPhase) : MathF16::sinF16(flagAnimationPhase)) >> 1); + flagAnimationTime += (int)(6553L * (int64_t)amplitude >> 16); } void GameCanvas::renderStartFlag(int x, int y) @@ -312,21 +315,22 @@ void GameCanvas::drawWheelTires(int x, int y, int wheelIsThin) drawSprite(graphics, spriteNo, centerX, centerY); } -int GameCanvas::calcSpriteNo(int angleF16, int var2, int var3, int var4, bool var5) +int GameCanvas::calcSpriteNo(int angleF16, int angleOffset, int angleRange, int spriteCount, bool flipDirection) { - for (angleF16 += var2; angleF16 < 0; angleF16 += var3) { + // Calculate sprite number from angle for animated sprites + for (angleF16 += angleOffset; angleF16 < 0; angleF16 += angleRange) { } - while (angleF16 >= var3) { - angleF16 -= var3; + while (angleF16 >= angleRange) { + angleF16 -= angleRange; } - if (var5) { - angleF16 = var3 - angleF16; + if (flipDirection) { + angleF16 = angleRange - angleF16; } - int var6; - return (var6 = (int)((int64_t)((int)(((int64_t)angleF16 << 32) / (int64_t)var3 >> 16)) * (int64_t)(var4 << 16) >> 16)) >> 16 < var4 - 1 ? var6 >> 16 : var4 - 1; + int spriteIndex; + return (spriteIndex = (int)((int64_t)((int)(((int64_t)angleF16 << 32) / (int64_t)angleRange >> 16)) * (int64_t)(spriteCount << 16) >> 16)) >> 16 < spriteCount - 1 ? spriteIndex >> 16 : spriteCount - 1; } void GameCanvas::renderEngine(int x, int y, int angleF16) @@ -384,12 +388,13 @@ void GameCanvas::setColor(int red, int green, int blue) void GameCanvas::drawGame(Graphics* g) { // synchronized (objectForSyncronization) { - if (Micro::field_249 && !micro->field_242) { + if (Micro::isGameLoopRunning && !micro->isAboutToExit) { graphics = g; - int var3; - if (field_184 != 0) { - if (field_184 == 1) { + int progress; + if (loadingScreenState != 0) { + if (loadingScreenState == 1) { + // Logo screen graphics->setColor(255, 255, 255); graphics->fillRect(0, 0, getWidth(), getHeight()); if (logoImage != nullptr) { @@ -398,6 +403,7 @@ void GameCanvas::drawGame(Graphics* g) drawSprite(graphics, 17, getWidth() - spriteSizeX[17] - 4, getHeight() - spriteSizeY[17] - spriteSizeY[16] - 9); } } else { + // Splash screen graphics->setColor(255, 255, 255); graphics->fillRect(0, 0, getWidth(), getHeight()); if (splashImage != nullptr) { @@ -405,15 +411,16 @@ void GameCanvas::drawGame(Graphics* g) } } - var3 = (int)(((int64_t)(Micro::gameLoadingStateStage << 16) << 32) / 655360L >> 16); - method_161(var3, true); + progress = (int)(((int64_t)(Micro::gameLoadingStateStage << 16) << 32) / 655360L >> 16); + drawProgressBar(progress, true); } else { if (height != getHeight()) { updateSizeAndRepaint(); } - gamePhysics->setMotoComponents(); - setViewPosition(-gamePhysics->getCamPosX() + field_178 + width / 2, gamePhysics->getCamPosY() + field_179 + height2 / 2); + gamePhysics->prepareRenderCache(); + // Apply camera offsets for view adjustment + setViewPosition(-gamePhysics->getCamPosX() + cameraOffsetX + width / 2, gamePhysics->getCamPosY() + cameraOffsetY + height2 / 2); gamePhysics->renderGame(this); if (isDrawingTime) { drawTime(micro->gameTimeMs / 10L); @@ -439,8 +446,8 @@ void GameCanvas::drawGame(Graphics* g) // graphics->setFont(font); // graphics->drawString("FPS: " + std::to_string(fps), defaultFontWidth00, height2 - 5, 36); - var3 = gamePhysics->method_52(); - method_161(var3, false); + progress = gamePhysics->getRawXDistance(); + drawProgressBar(progress, false); } graphics = nullptr; @@ -448,18 +455,21 @@ void GameCanvas::drawGame(Graphics* g) // } } -void GameCanvas::method_161(int var1, bool mode) +void GameCanvas::drawProgressBar(int progress, bool mode) { - int h = mode ? height : height2; + // Draw progress bar (loading or distance) + // progress: 0-65536 fixed-point value + int canvasHeight = mode ? height : height2; setColor(0, 0, 0); - graphics->fillRect(1, h - 4, width - 2, 3); + graphics->fillRect(1, canvasHeight - 4, width - 2, 3); setColor(255, 255, 255); - graphics->fillRect(2, h - 3, (int)((int64_t)((width - 4) << 16) * (int64_t)var1 >> 16) >> 16, 1); + graphics->fillRect(2, canvasHeight - 3, (int)((int64_t)((width - 4) << 16) * (int64_t)progress >> 16) >> 16, 1); } -void GameCanvas::method_163(int var1) +void GameCanvas::setInputConfigIndex(int configIndex) { - field_232 = var1; + // Set input configuration index (0, 1, or 2) - selects key-to-direction mapping + inputConfigIndex = configIndex; } void GameCanvas::paint(Graphics* graphics) @@ -476,53 +486,53 @@ void GameCanvas::paint(Graphics* graphics) processTimers(); // We need to call this function as often as we can. It might be better to move this call somewhere. if (Micro::isInGameMenu && menuManager != nullptr) { - menuManager->method_202(graphics); + menuManager->renderMenuOverGame(graphics); } else { drawGame(graphics); } } -void GameCanvas::method_164() +void GameCanvas::resetActiveKeys() { - int var1; - for (var1 = 0; var1 < 10; ++var1) { - activeKeys[var1] = false; + int i; + for (i = 0; i < 10; ++i) { + activeKeys[i] = false; } - for (var1 = 0; var1 < 7; ++var1) { - activeActions[var1] = false; + for (i = 0; i < 7; ++i) { + activeActions[i] = false; } } void GameCanvas::handleUpdatedInput() { - int var1 = 0; - int var2 = 0; - int var3 = field_232; + int upDown = 0; + int leftRight = 0; + int configIndex = inputConfigIndex; - int var4; - for (var4 = 0; var4 < 10; ++var4) { - if (activeKeys[var4]) { - var1 += field_231[var3][var4][0]; - var2 += field_231[var3][var4][1]; + int i; + for (i = 0; i < 10; ++i) { + if (activeKeys[i]) { + upDown += keyDirectionMap[configIndex][i][0]; + leftRight += keyDirectionMap[configIndex][i][1]; } } - for (var4 = 0; var4 < 7; ++var4) { - if (activeActions[var4]) { - var1 += field_230[var4][0]; - var2 += field_230[var4][1]; + for (i = 0; i < 7; ++i) { + if (activeActions[i]) { + upDown += actionDirectionMap[i][0]; + leftRight += actionDirectionMap[i][1]; } } - gamePhysics->method_30(var1, var2); + gamePhysics->updateInputs(upDown, leftRight); } void GameCanvas::processTimers() { for (auto i = timers.begin(); i != timers.end();) { if (i->ready()) { - method_150(i->getId()); + triggerTimerIfMatching(i->getId()); i = timers.erase(i); } else { i++; @@ -563,11 +573,11 @@ void GameCanvas::init(GamePhysics* gamePhysics) gamePhysics->setMinimalScreenWH(width < height2 ? width : height2); } -void GameCanvas::scheduleGameTimerTask(std::string timerMessage, int delayMs) +void GameCanvas::scheduleGameTimerTask(const std::string& message, int delayMs) { timerTriggered = false; ++timerId; - this->timerMessage = timerMessage; + timerMessage = message; timers.push_back(Timer(timerId, delayMs)); } @@ -576,35 +586,35 @@ void GameCanvas::setMenuManager(MenuManager* menuManager) this->menuManager = menuManager; } -void GameCanvas::method_168(Command* var1, Displayable* var2) +void GameCanvas::handleMenuCommand(Command* command, Displayable* displayable) { - (void)var2; - if (var1 == commandMenu) { - menuManager->field_377 = true; - micro->gameToMenu(); + (void)displayable; + if (command == commandMenu) { + menuManager->isMenuRenderingBlocked = true; // Signal menu manager to show menu + micro->gameToMenu(); // Transition from game to menu state } } -void GameCanvas::keyPressed(int var1) +void GameCanvas::keyPressed(int keyCode) { if (Micro::isInGameMenu && menuManager != nullptr) { - menuManager->processKeyCode(var1); + menuManager->processKey(keyCode); } - processKeyPressed(var1); + processKeyPressed(keyCode); } -void GameCanvas::keyReleased(int var1) +void GameCanvas::keyReleased(int keyCode) { - processKeyReleased(var1); + processKeyReleased(keyCode); } -void GameCanvas::commandAction(Command* var1, Displayable* var2) +void GameCanvas::commandAction(Command* command, Displayable* displayable) { if (Micro::isInGameMenu && menuManager != nullptr) { - menuManager->method_206(var1, var2); + menuManager->handleCommand(command, displayable); } else { - method_168(var1, var2); + handleMenuCommand(command, displayable); } } @@ -616,4 +626,4 @@ void GameCanvas::removeMenuCommand() void GameCanvas::addMenuCommand() { addCommand(commandMenu); -} \ No newline at end of file +} diff --git a/src/GameCanvas.h b/src/GameCanvas.h index 9ab971c..cfb0eeb 100644 --- a/src/GameCanvas.h +++ b/src/GameCanvas.h @@ -17,7 +17,7 @@ class MenuManager; class GameCanvas : public Canvas, public CommandListener { private: - void method_164(); + void resetActiveKeys(); void handleUpdatedInput(); void processTimers(); @@ -30,25 +30,27 @@ class GameCanvas : public Canvas, public CommandListener { int fenderSpriteHeight; GamePhysics* gamePhysics = nullptr; MenuManager* menuManager = nullptr; - int field_178 = 0; - int field_179 = 0; + // Additional offsets for camera view adjustment + int cameraOffsetX = 0; + int cameraOffsetY = 0; Micro* micro = nullptr; std::shared_ptr font; bool timerTriggered = false; - int field_184 = 1; + // 0=gameplay, 1=logo screen, 2=splash screen + int loadingScreenState = 1; std::unique_ptr splashImage; std::unique_ptr logoImage; std::unique_ptr bodyPartsImages[3]; std::unique_ptr engineImage; std::unique_ptr fenderImage; - std::unique_ptr onePixImage; + std::unique_ptr onePixImage; // Unused std::unique_ptr spritesImage; int bodyPartsSpriteWidth[3] = { 0, 0, 0 }; int bodyPartsSpriteHeight[3] = { 0, 0, 0 }; inline static int defaultFontWidth00 = 25; - bool field_205 = true; - int field_206; - std::unique_ptr screenBuffer; + bool unknown_bool = true; // Set by setInputConfigEnabled() but never read + int unused_field; // Dead code - never used + std::unique_ptr screenBuffer; // Unused std::string timerMessage = ""; int timerId = 0; std::vector timers; @@ -57,12 +59,15 @@ class GameCanvas : public Canvas, public CommandListener { std::vector time10MsToStringCache = std::vector(100); int timeInSeconds = -1; inline static int flagAnimationTime = 0; - inline static int field_226 = 0; + // Fixed-point animation phase accumulator + inline static int flagAnimationPhase = 0; const int startFlagAnimationTimeToSpriteNo[4] = { 12, 10, 11, 10 }; const int finishFlagAnumationTimeToSpriteNo[4] = { 14, 13, 15, 13 }; - int field_230[7][2] = { { 0, 0 }, { 1, 0 }, { 0, -1 }, { 0, 0 }, { 0, 0 }, { 0, 1 }, { -1, 0 } }; - int field_231[3][10][2] = { { { 0, 0 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, -1 }, { -1, 0 }, { 0, 1 }, { -1, -1 }, { -1, 0 }, { -1, 1 } }, { { 0, 0 }, { 1, 0 }, { 0, 0 }, { 0, 0 }, { -1, 0 }, { 0, -1 }, { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }, { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 }, { -1, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } }; - int field_232 = 2; + // Maps game actions to direction vectors + int actionDirectionMap[7][2] = { { 0, 0 }, { 1, 0 }, { 0, -1 }, { 0, 0 }, { 0, 0 }, { 0, 1 }, { -1, 0 } }; + // Current input configuration (0, 1, or 2) + int keyDirectionMap[3][10][2] = { { { 0, 0 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, -1 }, { -1, 0 }, { 0, 1 }, { -1, -1 }, { -1, 0 }, { -1, 1 } }, { { 0, 0 }, { 1, 0 }, { 0, 0 }, { 0, 0 }, { -1, 0 }, { 0, -1 }, { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }, { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 }, { -1, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } }; // Maps numeric keys to directions per input config + int inputConfigIndex = 2; std::vector activeActions = std::vector(7); std::vector activeKeys = std::vector(10); @@ -71,11 +76,11 @@ class GameCanvas : public Canvas, public CommandListener { public: GameCanvas(Micro* micro); void drawSprite(Graphics* g, int spriteNo, int x, int y); - void requestRepaint(int var1); - void method_124(bool var1); + void requestRepaint(int state); + void setInputConfigEnabled(bool enabled); // Sets unknown_bool (never read) void updateSizeAndRepaint(); int loadSprites(int flags); - void method_129(); + void reset(); void setViewPosition(int dx, int dy); int getDx(); int addDx(int x); @@ -84,35 +89,35 @@ class GameCanvas : public Canvas, public CommandListener { void drawLineF16(int x, int y, int x2, int y2); void renderBodyPart(int x1F16, int y1F16, int x2F16, int y2F16, int bodyPartNo); void renderBodyPart(int x1F16, int y1F16, int x2F16, int y2F16, int bodyPartNo, int tF16); - void method_142(int var1, int var2, int var3, int var4); + void drawWheelHub(int centerX, int centerY, int radius, int angleF16); void drawCircle(int x, int y, int size); void fillRect(int x, int y, int w, int h); - void drawForthSpriteByCenter(int centerX, int centerY); - void drawHelmet(int var1, int var2, int var3); + void drawAttachmentPointSprite(int x, int y); + void drawHelmet(int x, int y, int angleF16); void drawTime(int64_t time10Ms); - void method_150(int var1); - static void method_151(); + void triggerTimerIfMatching(int timerId); + static void flagAnimation(); void renderStartFlag(int x, int y); void renderFinishFlag(int x, int y); void drawWheelTires(int x, int y, int wheelIsThin); - int calcSpriteNo(int angleF16, int var2, int var3, int var4, bool var5); + int calcSpriteNo(int angleF16, int angleOffset, int angleRange, int spriteCount, bool flipDirection); void renderEngine(int x, int y, int angleF16); void renderFender(int x, int y, int angleF16); void clearScreenWithWhite(); void setColor(int red, int green, int blue); void drawGame(Graphics* g); - void method_161(int var1, bool mode); - void method_163(int var1); + void drawProgressBar(int progress, bool mode); + void setInputConfigIndex(int configIndex); void paint(Graphics* g); void init(GamePhysics* gamePhysics); void processKeyPressed(int keyCode); void processKeyReleased(int keyCode); - void scheduleGameTimerTask(std::string var1, int delayMs); + void scheduleGameTimerTask(const std::string& message, int delayMs); void setMenuManager(MenuManager* menuManager); - void method_168(Command* var1, Displayable* var2); - void keyPressed(int var1); - void keyReleased(int var1); - void commandAction(Command* var1, Displayable* var2); + void handleMenuCommand(Command* command, Displayable* displayable); + void keyPressed(int keyCode); + void keyReleased(int keyCode); + void commandAction(Command* command, Displayable* displayable); void removeMenuCommand(); void addMenuCommand(); @@ -127,4 +132,4 @@ class GameCanvas : public Canvas, public CommandListener { inline static const int spriteOffsetY[18] = { 10, 25, 16, 20, 10, 0, 0, 0, 8, 0, 0, 6, 12, 0, 6, 12, 29, 18 }; inline static const int spriteSizeX[18] = { 15, 15, 8, 8, 3, 6, 6, 6, 7, 7, 12, 12, 12, 12, 12, 12, 16, 17 }; inline static const int spriteSizeY[18] = { 15, 15, 4, 4, 3, 10, 10, 10, 8, 8, 6, 6, 6, 6, 6, 6, 11, 22 }; -}; \ No newline at end of file +}; diff --git a/src/GameLevel.cpp b/src/GameLevel.cpp index 5b9bcbc..ec2d861 100644 --- a/src/GameLevel.cpp +++ b/src/GameLevel.cpp @@ -18,12 +18,12 @@ void GameLevel::init() field_274 = 0; } -void GameLevel::method_174(int var1, int var2, int var3, int var4) +void GameLevel::setStartFinishPositions(int startX, int startY, int finishX, int finishY) { - startPosX = var1 << 16 >> 3; - startPosY = var2 << 16 >> 3; - finishPosX = var3 << 16 >> 3; - finishPosY = var4 << 16 >> 3; + startPosX = startX << 16 >> 3; + startPosY = startY << 16 >> 3; + finishPosX = finishX << 16 >> 3; + finishPosY = finishY << 16 >> 3; } int GameLevel::getStartPosX() @@ -56,11 +56,11 @@ int GameLevel::getPointY(int pointNo) return pointPositions[pointNo][1] << 3 >> 16; } -int GameLevel::method_181(int var1) +int GameLevel::calculateProgressPercent(int currentX) { - int var2 = var1 - pointPositions[startFlagPoint][0]; - int var3; - return ((var3 = pointPositions[finishFlagPoint][0] - pointPositions[startFlagPoint][0]) < 0 ? -var3 : var3) >= 3 && var2 <= var3 ? (int)(((int64_t)var2 << 32) / (int64_t)var3 >> 16) : 65536; + int distanceFromStart = currentX - pointPositions[startFlagPoint][0]; + int totalDistance; + return ((totalDistance = pointPositions[finishFlagPoint][0] - pointPositions[startFlagPoint][0]) < 0 ? -totalDistance : totalDistance) >= 3 && distanceFromStart <= totalDistance ? (int)(((int64_t)distanceFromStart << 32) / (int64_t)totalDistance >> 16) : 65536; } void GameLevel::setMinMaxX(int minX, int maxX) @@ -69,99 +69,99 @@ void GameLevel::setMinMaxX(int minX, int maxX) this->maxX = maxX << 16 >> 3; } -void GameLevel::method_183(int var1, int var2) +void GameLevel::setShadowBoundariesHalf(int startX, int endX) { - field_264 = var1 >> 1; - field_265 = var2 >> 1; + shadowStartX = startX >> 1; + shadowEndX = endX >> 1; } -void GameLevel::method_184(int var1, int var2, int var3) +void GameLevel::setShadowBoundaries(int startX, int endX, int heightThreshold) { - field_264 = var1; - field_265 = var2; - field_266 = var3; + shadowStartX = startX; + shadowEndX = endX; + shadowHeightThreshold = heightThreshold; } -void GameLevel::renderShadow(GameCanvas* gameCanvas, int var2, int var3) +void GameLevel::renderShadow(GameCanvas* gameCanvas, int startLineIdx, int endLineIdx) { - if (var3 <= pointsCount - 1) { - int var4 = field_266 - ((pointPositions[var2][1] + pointPositions[var3 + 1][1]) >> 1) < 0 ? 0 : field_266 - ((pointPositions[var2][1] + pointPositions[var3 + 1][1]) >> 1); - if (field_266 <= pointPositions[var2][1] || field_266 <= pointPositions[var3 + 1][1]) { - var4 = var4 < 327680 ? var4 : 327680; + if (endLineIdx <= pointsCount - 1) { + int shadowHeight = shadowHeightThreshold - ((pointPositions[startLineIdx][1] + pointPositions[endLineIdx + 1][1]) >> 1) < 0 ? 0 : shadowHeightThreshold - ((pointPositions[startLineIdx][1] + pointPositions[endLineIdx + 1][1]) >> 1); + if (shadowHeightThreshold <= pointPositions[startLineIdx][1] || shadowHeightThreshold <= pointPositions[endLineIdx + 1][1]) { + shadowHeight = shadowHeight < 327680 ? shadowHeight : 327680; } - field_277 = (int)((int64_t)field_277 * 49152L >> 16) + (int)((int64_t)var4 * 16384L >> 16); - if (field_277 <= 557056) { - int var5 = (int)(1638400L * (int64_t)field_277 >> 16) >> 16; - gameCanvas->setColor(var5, var5, var5); - int var6 = pointPositions[var2][0] - pointPositions[var2 + 1][0]; - int var8 = (int)(((int64_t)(pointPositions[var2][1] - pointPositions[var2 + 1][1]) << 32) / (int64_t)var6 >> 16); - int var9 = pointPositions[var2][1] - (int)((int64_t)pointPositions[var2][0] * (int64_t)var8 >> 16); - int var10 = (int)((int64_t)field_264 * (int64_t)var8 >> 16) + var9; - var6 = pointPositions[var3][0] - pointPositions[var3 + 1][0]; - var8 = (int)(((int64_t)(pointPositions[var3][1] - pointPositions[var3 + 1][1]) << 32) / (int64_t)var6 >> 16); - var9 = pointPositions[var3][1] - (int)((int64_t)pointPositions[var3][0] * (int64_t)var8 >> 16); - int var11 = (int)((int64_t)field_265 * (int64_t)var8 >> 16) + var9; - if (var2 == var3) { - gameCanvas->drawLine(field_264 << 3 >> 16, (var10 + 65536) << 3 >> 16, field_265 << 3 >> 16, (var11 + 65536) << 3 >> 16); + shadowIntensity = (int)((int64_t)shadowIntensity * 49152L >> 16) + (int)((int64_t)shadowHeight * 16384L >> 16); + if (shadowIntensity <= 557056) { + int shadowColor = (int)(1638400L * (int64_t)shadowIntensity >> 16) >> 16; + gameCanvas->setColor(shadowColor, shadowColor, shadowColor); + int lineDx = pointPositions[startLineIdx][0] - pointPositions[startLineIdx + 1][0]; + int lineSlope = (int)(((int64_t)(pointPositions[startLineIdx][1] - pointPositions[startLineIdx + 1][1]) << 32) / (int64_t)lineDx >> 16); + int lineIntercept = pointPositions[startLineIdx][1] - (int)((int64_t)pointPositions[startLineIdx][0] * (int64_t)lineSlope >> 16); + int startYProjected = (int)((int64_t)shadowStartX * (int64_t)lineSlope >> 16) + lineIntercept; + lineDx = pointPositions[endLineIdx][0] - pointPositions[endLineIdx + 1][0]; + lineSlope = (int)(((int64_t)(pointPositions[endLineIdx][1] - pointPositions[endLineIdx + 1][1]) << 32) / (int64_t)lineDx >> 16); + lineIntercept = pointPositions[endLineIdx][1] - (int)((int64_t)pointPositions[endLineIdx][0] * (int64_t)lineSlope >> 16); + int endYProjected = (int)((int64_t)shadowEndX * (int64_t)lineSlope >> 16) + lineIntercept; + if (startLineIdx == endLineIdx) { + gameCanvas->drawLine(shadowStartX << 3 >> 16, (startYProjected + 65536) << 3 >> 16, shadowEndX << 3 >> 16, (endYProjected + 65536) << 3 >> 16); return; } - gameCanvas->drawLine(field_264 << 3 >> 16, (var10 + 65536) << 3 >> 16, pointPositions[var2 + 1][0] << 3 >> 16, (pointPositions[var2 + 1][1] + 65536) << 3 >> 16); + gameCanvas->drawLine(shadowStartX << 3 >> 16, (startYProjected + 65536) << 3 >> 16, pointPositions[startLineIdx + 1][0] << 3 >> 16, (pointPositions[startLineIdx + 1][1] + 65536) << 3 >> 16); - for (int i = var2 + 1; i < var3; ++i) { + for (int i = startLineIdx + 1; i < endLineIdx; ++i) { gameCanvas->drawLine(pointPositions[i][0] << 3 >> 16, (pointPositions[i][1] + 65536) << 3 >> 16, pointPositions[i + 1][0] << 3 >> 16, (pointPositions[i + 1][1] + 65536) << 3 >> 16); } - gameCanvas->drawLine(pointPositions[var3][0] << 3 >> 16, (pointPositions[var3][1] + 65536) << 3 >> 16, field_265 << 3 >> 16, (var11 + 65536) << 3 >> 16); + gameCanvas->drawLine(pointPositions[endLineIdx][0] << 3 >> 16, (pointPositions[endLineIdx][1] + 65536) << 3 >> 16, shadowEndX << 3 >> 16, (endYProjected + 65536) << 3 >> 16); } } } void GameLevel::renderLevel3D(GameCanvas* gameCanvas, int xF16, int yF16) { - int var7 = 0, var8 = 0; + int shadowStartLineIdx = 0, shadowEndLineIdx = 0; int lineNo; for (lineNo = 0; lineNo < pointsCount - 1 && pointPositions[lineNo][0] <= minX; ++lineNo) { } if (lineNo > 0) { --lineNo; } - int var9 = xF16 - pointPositions[lineNo][0]; - int var10 = yF16 + 3276800 - pointPositions[lineNo][1]; - int var11 = GamePhysics::getSmthLikeMaxAbs(var9, var10); - var9 = (int)(((int64_t)var9 << 32) / (int64_t)(var11 >> 1 >> 1) >> 16); - var10 = (int)(((int64_t)var10 << 32) / (int64_t)(var11 >> 1 >> 1) >> 16); + int deltaX = xF16 - pointPositions[lineNo][0]; + int deltaY = yF16 + 3276800 - pointPositions[lineNo][1]; + int vectorLength = GamePhysics::fastVectorLengthF16(deltaX, deltaY); + deltaX = (int)(((int64_t)deltaX << 32) / (int64_t)(vectorLength >> 1 >> 1) >> 16); + deltaY = (int)(((int64_t)deltaY << 32) / (int64_t)(vectorLength >> 1 >> 1) >> 16); gameCanvas->setColor(0, 170, 0); while (lineNo < pointsCount - 1) { - int var4 = var9; - int var5 = var10; - var9 = xF16 - pointPositions[lineNo + 1][0]; - var10 = yF16 + 3276800 - pointPositions[lineNo + 1][1]; - var11 = GamePhysics::getSmthLikeMaxAbs(var9, var10); - var9 = (int)(((int64_t)var9 << 32) / (int64_t)(var11 >> 1 >> 1) >> 16); - var10 = (int)(((int64_t)var10 << 32) / (int64_t)(var11 >> 1 >> 1) >> 16); + int prevDeltaX = deltaX; + int prevDeltaY = deltaY; + deltaX = xF16 - pointPositions[lineNo + 1][0]; + deltaY = yF16 + 3276800 - pointPositions[lineNo + 1][1]; + vectorLength = GamePhysics::fastVectorLengthF16(deltaX, deltaY); + deltaX = (int)(((int64_t)deltaX << 32) / (int64_t)(vectorLength >> 1 >> 1) >> 16); + deltaY = (int)(((int64_t)deltaY << 32) / (int64_t)(vectorLength >> 1 >> 1) >> 16); // far line - gameCanvas->drawLine((pointPositions[lineNo][0] + var4) << 3 >> 16, (pointPositions[lineNo][1] + var5) << 3 >> 16, (pointPositions[lineNo + 1][0] + var9) << 3 >> 16, (pointPositions[lineNo + 1][1] + var10) << 3 >> 16); + gameCanvas->drawLine((pointPositions[lineNo][0] + prevDeltaX) << 3 >> 16, (pointPositions[lineNo][1] + prevDeltaY) << 3 >> 16, (pointPositions[lineNo + 1][0] + deltaX) << 3 >> 16, (pointPositions[lineNo + 1][1] + deltaY) << 3 >> 16); // from far to near - gameCanvas->drawLine(pointPositions[lineNo][0] << 3 >> 16, pointPositions[lineNo][1] << 3 >> 16, (pointPositions[lineNo][0] + var4) << 3 >> 16, (pointPositions[lineNo][1] + var5) << 3 >> 16); + gameCanvas->drawLine(pointPositions[lineNo][0] << 3 >> 16, pointPositions[lineNo][1] << 3 >> 16, (pointPositions[lineNo][0] + prevDeltaX) << 3 >> 16, (pointPositions[lineNo][1] + prevDeltaY) << 3 >> 16); if (lineNo > 1) { - if (pointPositions[lineNo][0] > field_264 && var7 == 0) { - var7 = lineNo - 1; + if (pointPositions[lineNo][0] > shadowStartX && shadowStartLineIdx == 0) { + shadowStartLineIdx = lineNo - 1; } - if (pointPositions[lineNo][0] > field_265 && var8 == 0) { - var8 = lineNo - 1; + if (pointPositions[lineNo][0] > shadowEndX && shadowEndLineIdx == 0) { + shadowEndLineIdx = lineNo - 1; } } if (startFlagPoint == lineNo) { // render far start flag - gameCanvas->renderStartFlag((pointPositions[startFlagPoint][0] + var4) << 3 >> 16, (pointPositions[startFlagPoint][1] + var5) << 3 >> 16); + gameCanvas->renderStartFlag((pointPositions[startFlagPoint][0] + prevDeltaX) << 3 >> 16, (pointPositions[startFlagPoint][1] + prevDeltaY) << 3 >> 16); gameCanvas->setColor(0, 170, 0); } if (finishFlagPoint == lineNo) { // render far finish flag - gameCanvas->renderFinishFlag((pointPositions[finishFlagPoint][0] + var4) << 3 >> 16, (pointPositions[finishFlagPoint][1] + var5) << 3 >> 16); + gameCanvas->renderFinishFlag((pointPositions[finishFlagPoint][0] + prevDeltaX) << 3 >> 16, (pointPositions[finishFlagPoint][1] + prevDeltaY) << 3 >> 16); gameCanvas->setColor(0, 170, 0); } if (pointPositions[lineNo][0] > maxX) { @@ -169,9 +169,9 @@ void GameLevel::renderLevel3D(GameCanvas* gameCanvas, int xF16, int yF16) } ++lineNo; } - gameCanvas->drawLine(pointPositions[pointsCount - 1][0] << 3 >> 16, pointPositions[pointsCount - 1][1] << 3 >> 16, (pointPositions[pointsCount - 1][0] + var9) << 3 >> 16, (pointPositions[pointsCount - 1][1] + var10) << 3 >> 16); + gameCanvas->drawLine(pointPositions[pointsCount - 1][0] << 3 >> 16, pointPositions[pointsCount - 1][1] << 3 >> 16, (pointPositions[pointsCount - 1][0] + deltaX) << 3 >> 16, (pointPositions[pointsCount - 1][1] + deltaY) << 3 >> 16); if (LevelLoader::isEnabledShadows) { - renderShadow(gameCanvas, var7, var8); + renderShadow(gameCanvas, shadowStartLineIdx, shadowEndLineIdx); } } @@ -206,8 +206,9 @@ void GameLevel::load(FileStream* inStream) int8_t c; inStream->readVariable(&c, true); if (c == 50) { - char var3[20]; - inStream->readVariable(var3, false, 20); + // DEAD CODE: version header buffer, read but never used + char versionHeader[20]; + inStream->readVariable(versionHeader, false, 20); } finishFlagPoint = 0; @@ -246,19 +247,19 @@ void GameLevel::load(FileStream* inStream) } } -void GameLevel::addPointSimple(int var1, int var2) +void GameLevel::addPointSimple(int x, int y) { - addPoint(var1 << 16 >> 3, var2 << 16 >> 3); + addPoint(x << 16 >> 3, y << 16 >> 3); } void GameLevel::addPoint(int x, int y) { if (pointPositions.empty() || static_cast(pointPositions.size()) <= pointsCount) { - int var3 = 100; + int newCapacity = 100; if (!pointPositions.empty()) { - var3 = var3 < static_cast(pointPositions.size()) + 30 ? pointPositions.size() + 30 : var3; + newCapacity = newCapacity < static_cast(pointPositions.size()) + 30 ? pointPositions.size() + 30 : newCapacity; } - pointPositions.resize(var3, std::vector(2)); // ABOBA + pointPositions.resize(newCapacity, std::vector(2)); } if (pointsCount == 0 || pointPositions[pointsCount - 1][0] < x) { diff --git a/src/GameLevel.h b/src/GameLevel.h index 42b9de8..65ac310 100644 --- a/src/GameLevel.h +++ b/src/GameLevel.h @@ -12,10 +12,14 @@ class GameLevel { private: int minX = 0; int maxX = 0; - int field_264 = 0; - int field_265 = 0; - int field_266 = 0; - int field_277 = 0; + // Shadow rendering start X coordinate + int shadowStartX = 0; + // Shadow rendering end X coordinate + int shadowEndX = 0; + // Y threshold for shadow height calculation + int shadowHeightThreshold = 0; + // Accumulated shadow intensity value + int shadowIntensity = 0; public: int startPosX; @@ -25,27 +29,28 @@ class GameLevel { int finishFlagPoint = 0; int finishPosY; int pointsCount; - int field_274; + int field_274; // UNUSED - dead code, initialized but never used std::vector> pointPositions; GameLevel(); ~GameLevel(); void init(); - void method_174(int var1, int var2, int var3, int var4); + void setStartFinishPositions(int startX, int startY, int finishX, int finishY); int getStartPosX(); int getStartPosY(); int getFinishPosX(); int getFinishPosY(); int getPointX(int pointNo); int getPointY(int pointNo); - int method_181(int var1); + int calculateProgressPercent(int currentX); void setMinMaxX(int minX, int maxX); - void method_183(int var1, int var2); - void method_184(int var1, int var2, int var3); - void renderShadow(GameCanvas* gameCanvas, int var2, int var3); + // Sets boundaries divided by 2 + void setShadowBoundariesHalf(int startX, int endX); + void setShadowBoundaries(int startX, int endX, int heightThreshold); + void renderShadow(GameCanvas* gameCanvas, int startLineIdx, int endLineIdx); /*synchronized*/ void renderLevel3D(GameCanvas* gameCanvas, int xF16, int yF16); /*synchronized*/ void renderTrackNearestGreenLine(GameCanvas* canvas); - void addPointSimple(int var1, int var2); + void addPointSimple(int x, int y); void addPoint(int x, int y); /*synchronized*/ void load(FileStream* inStream); }; diff --git a/src/GameMenu.cpp b/src/GameMenu.cpp index b3b4bd1..d45ef5c 100644 --- a/src/GameMenu.cpp +++ b/src/GameMenu.cpp @@ -9,15 +9,15 @@ #include "lcdui/FontStorage.h" #include "lcdui/Graphics.h" -GameMenu::GameMenu(std::string var1, Micro* micro, GameMenu* var3, char* inputString) +GameMenu::GameMenu(std::string title, Micro* micro, GameMenu* parentMenu, char* inputString) { - field_94 = var1; - field_95 = -1; + menuTitle = title; + selectedItemIndex = -1; this->micro = micro; - gameMenu = var3; + gameMenu = parentMenu; canvasWidth = micro->gameCanvas->getWidth(); canvasHeight = micro->gameCanvas->getHeight(); - + font = FontStorage::getFont(Font::STYLE_BOLD, Font::SIZE_LARGE); font3 = FontStorage::getFont(Font::STYLE_PLAIN, Font::SIZE_SMALL); @@ -29,7 +29,7 @@ GameMenu::GameMenu(std::string var1, Micro* micro, GameMenu* var3, char* inputSt TextRender::setDefaultFont(font3); TextRender::setMaxArea(canvasWidth, canvasHeight); - field_101 = 1; + marginPadding = 1; if (canvasWidth <= 100) { xPos = 6; } else { @@ -37,127 +37,127 @@ GameMenu::GameMenu(std::string var1, Micro* micro, GameMenu* var3, char* inputSt } if (canvasHeight <= 100) { - field_94 = ""; + menuTitle = ""; } - field_104 = xPos + 7; - field_103 = 2; - field_110 = 0; - if (field_94 != "") { - field_107 = (canvasHeight - (field_101 << 1) - 10 - font->getBaselinePosition()) / (font2->getBaselinePosition() + field_103); + renderXOffset = xPos + 7; + itemSpacing = 2; + helmetAnimFrame = 0; + if (menuTitle != "") { + maxVisibleItems = (canvasHeight - (marginPadding << 1) - 10 - font->getBaselinePosition()) / (font2->getBaselinePosition() + itemSpacing); } else { - field_107 = (canvasHeight - (field_101 << 1) - 10) / (font2->getBaselinePosition() + field_103); + maxVisibleItems = (canvasHeight - (marginPadding << 1) - 10) / (font2->getBaselinePosition() + itemSpacing); } if (inputString) { - field_111 = true; + isInputMode = true; nameCursorPos = 0; xPos = 8; strArr = inputString; } else { - field_111 = false; + isInputMode = false; } - if (field_107 > 13) { - field_107 = 13; + if (maxVisibleItems > 13) { + maxVisibleItems = 13; } } -void GameMenu::method_68(int var1) +void GameMenu::setItemSpacing(int spacing) { - field_103 = var1; + itemSpacing = spacing; } -void GameMenu::method_69(std::string var1) +void GameMenu::setMenuTitle(std::string title) { - field_94 = var1; + menuTitle = title; } -void GameMenu::method_70() +void GameMenu::resetToFirstItem() { - if (field_111) { + if (isInputMode) { nameCursorPos = 0; } else { if (!vector.empty()) { - field_95 = 0; + selectedItemIndex = 0; - for (int var1 = 0; var1 < static_cast(vector.size()) && var1 < field_107; ++var1) { - if (vector[var1]->isNotTextRender()) { - field_95 = var1; + for (int i = 0; i < static_cast(vector.size()) && i < maxVisibleItems; ++i) { + if (vector[i]->isNotTextRender()) { + selectedItemIndex = i; break; } } - field_105 = 0; - field_106 = vector.size() - 1; - if (field_106 > field_107 - 1) { - field_106 = field_107 - 1; + scrollOffsetFirst = 0; + scrollOffsetLast = vector.size() - 1; + if (scrollOffsetLast > maxVisibleItems - 1) { + scrollOffsetLast = maxVisibleItems - 1; } } } } -void GameMenu::method_71() +void GameMenu::resetToLastItem() { - field_95 = vector.size() - 1; + selectedItemIndex = vector.size() - 1; - for (int var1 = vector.size() - 1; var1 > 0; --var1) { - if (vector[var1]->isNotTextRender()) { - field_95 = var1; + for (int i = vector.size() - 1; i > 0; --i) { + if (vector[i]->isNotTextRender()) { + selectedItemIndex = i; break; } } - field_105 = vector.size() - field_107; - if (field_105 < 0) { - field_105 = 0; + scrollOffsetFirst = vector.size() - maxVisibleItems; + if (scrollOffsetFirst < 0) { + scrollOffsetFirst = 0; } - field_106 = vector.size() - 1; - if (field_106 > field_95 + field_107) { - field_106 = field_95 + field_107; + scrollOffsetLast = vector.size() - 1; + if (scrollOffsetLast > selectedItemIndex + maxVisibleItems) { + scrollOffsetLast = selectedItemIndex + maxVisibleItems; } } -void GameMenu::addMenuElement(IGameMenuElement* var1) +void GameMenu::addMenuElement(IGameMenuElement* element) { - int var2 = field_101; - field_107 = 1; - vector.push_back(var1); - if (field_94 != "") { - var2 = font->getBaselinePosition() + 2; + int yPos = marginPadding; + maxVisibleItems = 1; + vector.push_back(element); + if (menuTitle != "") { + yPos = font->getBaselinePosition() + 2; } if (canvasHeight < 100) { - ++var2; + ++yPos; } else { - var2 += 4; + yPos += 4; } - for (int var3 = 0; var3 < static_cast(vector.size()) - 1; ++var3) { - if (vector[var3]->isNotTextRender()) { - var2 += font2->getBaselinePosition() + field_103; + for (int i = 0; i < static_cast(vector.size()) - 1; ++i) { + if (vector[i]->isNotTextRender()) { + yPos += font2->getBaselinePosition() + itemSpacing; } else { - var2 += (TextRender::getBaselinePosition() < GameCanvas::spriteSizeY[5] ? GameCanvas::spriteSizeY[5] : TextRender::getBaselinePosition()) + field_103; + yPos += (TextRender::getBaselinePosition() < GameCanvas::spriteSizeY[5] ? GameCanvas::spriteSizeY[5] : TextRender::getBaselinePosition()) + itemSpacing; } - if (var2 > canvasHeight - (field_101 << 1) - 10) { + if (yPos > canvasHeight - (marginPadding << 1) - 10) { break; } - ++field_107; + ++maxVisibleItems; } - if (field_107 > 13) { - field_107 = 13; + if (maxVisibleItems > 13) { + maxVisibleItems = 13; } - method_70(); + resetToFirstItem(); } void GameMenu::processGameActionDown() { - if (field_111) { + if (isInputMode) { if (strArr[nameCursorPos] == 32) { strArr[nameCursorPos] = 90; return; @@ -169,53 +169,53 @@ void GameMenu::processGameActionDown() return; } } else if (vector.size() != 0) { - if (!(vector[field_95]->isNotTextRender())) { - ++field_106; - field_95 = field_106; - ++field_105; + if (!(vector[selectedItemIndex]->isNotTextRender())) { + ++scrollOffsetLast; + selectedItemIndex = scrollOffsetLast; + ++scrollOffsetFirst; return; } - ++field_95; - if (field_95 > static_cast(vector.size()) - 1) { - method_70(); + ++selectedItemIndex; + if (selectedItemIndex > static_cast(vector.size()) - 1) { + resetToFirstItem(); return; } - bool var3 = false; + bool foundNonText = false; - int var2; - for (var2 = field_95; var2 <= field_106 + 1; ++var2) { - if (vector[var2]->isNotTextRender()) { - var3 = true; + int i; + for (i = selectedItemIndex; i <= scrollOffsetLast + 1; ++i) { + if (vector[i]->isNotTextRender()) { + foundNonText = true; break; } } - if (var3) { - field_95 = var2; - } else if (field_106 < static_cast(vector.size()) - 1) { - ++field_106; - ++field_105; + if (foundNonText) { + selectedItemIndex = i; + } else if (scrollOffsetLast < static_cast(vector.size()) - 1) { + ++scrollOffsetLast; + ++scrollOffsetFirst; } else { - --field_95; + --selectedItemIndex; } - if (field_95 > field_106) { - ++field_105; - ++field_106; - if (field_106 > static_cast(vector.size()) - 1) { - field_106 = vector.size() - 1; + if (selectedItemIndex > scrollOffsetLast) { + ++scrollOffsetFirst; + ++scrollOffsetLast; + if (scrollOffsetLast > static_cast(vector.size()) - 1) { + scrollOffsetLast = vector.size() - 1; } - field_95 = field_106; + selectedItemIndex = scrollOffsetLast; } } } void GameMenu::processGameActionUp() { - if (field_111) { + if (isInputMode) { if (strArr[nameCursorPos] == 32) { strArr[nameCursorPos] = 65; return; @@ -227,58 +227,58 @@ void GameMenu::processGameActionUp() return; } } else if (vector.size() != 0) { - --field_95; - if (field_95 < 0) { - method_71(); + --selectedItemIndex; + if (selectedItemIndex < 0) { + resetToLastItem(); return; } - bool var3 = false; + bool foundNonText = false; - int var2; - for (var2 = field_95; var2 >= field_105; --var2) { - if (vector[var2]->isNotTextRender()) { - var3 = true; + int i; + for (i = selectedItemIndex; i >= scrollOffsetFirst; --i) { + if (vector[i]->isNotTextRender()) { + foundNonText = true; break; } } - if (!var3) { - if (field_105 > 0) { - --field_105; - if (static_cast(vector.size()) > field_107 - 1) { - --field_106; + if (!foundNonText) { + if (scrollOffsetFirst > 0) { + --scrollOffsetFirst; + if (static_cast(vector.size()) > maxVisibleItems - 1) { + --scrollOffsetLast; return; } } else { - method_71(); + resetToLastItem(); } return; } - field_95 = var2; - if (field_95 < field_105) { - --field_105; - if (field_105 < 0) { - field_95 = 0; - field_105 = 0; + selectedItemIndex = i; + if (selectedItemIndex < scrollOffsetFirst) { + --scrollOffsetFirst; + if (scrollOffsetFirst < 0) { + selectedItemIndex = 0; + scrollOffsetFirst = 0; } - if (static_cast(vector.size()) > field_107 - 1) { - --field_106; + if (static_cast(vector.size()) > maxVisibleItems - 1) { + --scrollOffsetLast; } } } } -void GameMenu::processGameActionUpd(int var1) +void GameMenu::processGameActionUpd(int action) { - if (field_111) { - switch (var1) { + if (isInputMode) { + switch (action) { case 1: if (nameCursorPos == 2) { - micro->menuManager->method_1(gameMenu, false); + micro->menuManager->switchToMenu(gameMenu, false); return; } @@ -299,11 +299,11 @@ void GameMenu::processGameActionUpd(int var1) } } else { - if (field_95 != -1) { - for (int var2 = field_95; var2 < static_cast(vector.size()); ++var2) { - IGameMenuElement* var3; - if ((var3 = vector[var2]) != nullptr && var3->isNotTextRender()) { - var3->menuElemMethod(var1); + if (selectedItemIndex != -1) { + for (int i = selectedItemIndex; i < static_cast(vector.size()); ++i) { + IGameMenuElement* element; + if ((element = vector[i]) != nullptr && element->isNotTextRender()) { + element->menuElemMethod(action); return; } } @@ -311,76 +311,76 @@ void GameMenu::processGameActionUpd(int var1) } } -void GameMenu::render_76(Graphics* graphics) +void GameMenu::render(Graphics* graphics) { - int var2; + int yPos; int i; - if (field_111) { + if (isInputMode) { graphics->setColor(0, 0, 20); graphics->setFont(font); - int8_t var7 = 1; - graphics->drawString("Enter Name", xPos, var7, 20); - var2 = var7 + font->getHeight() + (field_103 << 2); + int8_t yStart = 1; + graphics->drawString("Enter Name", xPos, yStart, 20); + yPos = yStart + font->getHeight() + (itemSpacing << 2); graphics->setFont(font2); for (i = 0; i < 3; ++i) { - graphics->drawChar((char)strArr[i], xPos + i * font2->charWidth('W') + 1, var2, 17); + graphics->drawChar((char)strArr[i], xPos + i * font2->charWidth('W') + 1, yPos, 17); if (i == nameCursorPos) { - graphics->drawChar('^', xPos + i * font2->charWidth('W') + 1, var2 + font2->getHeight(), 17); + graphics->drawChar('^', xPos + i * font2->charWidth('W') + 1, yPos + font2->getHeight(), 17); } } } else { graphics->setColor(0, 0, 0); - var2 = field_101; - if (field_94 != "") { + yPos = marginPadding; + if (menuTitle != "") { graphics->setFont(font); - graphics->drawString(field_94, xPos, var2, 20); - var2 += font->getBaselinePosition() + 2; + graphics->drawString(menuTitle, xPos, yPos, 20); + yPos += font->getBaselinePosition() + 2; } - if (field_105 > 0) { - micro->gameCanvas->drawSprite(graphics, 2, xPos - 3, var2); + if (scrollOffsetFirst > 0) { + micro->gameCanvas->drawSprite(graphics, 2, xPos - 3, yPos); } if (canvasHeight < 100) { - ++var2; + ++yPos; } else { - var2 += 4; + yPos += 4; } graphics->setFont(font2); - for (i = field_105; i < field_106 + 1; ++i) { - IGameMenuElement* var4 = vector[i]; + for (i = scrollOffsetFirst; i < scrollOffsetLast + 1; ++i) { + IGameMenuElement* element = vector[i]; graphics->setColor(0, 0, 0); - var4->render(graphics, var2, field_104); - if (i == field_95 && var4->isNotTextRender()) { - int var5 = xPos - micro->gameCanvas->helmetSpriteWidth / 2; - int var6 = var2 + font2->getBaselinePosition() / 2 - micro->gameCanvas->helmetSpriteHeight / 2; - graphics->setClip(var5, var6, micro->gameCanvas->helmetSpriteWidth, micro->gameCanvas->helmetSpriteHeight); - graphics->drawImage(micro->gameCanvas->helmetImage.get(), var5 - micro->gameCanvas->helmetSpriteWidth * (field_110 % 6), var6 - micro->gameCanvas->helmetSpriteHeight * (field_110 / 6), 20); + element->render(graphics, yPos, renderXOffset); + if (i == selectedItemIndex && element->isNotTextRender()) { + int helmetX = xPos - micro->gameCanvas->helmetSpriteWidth / 2; + int helmetY = yPos + font2->getBaselinePosition() / 2 - micro->gameCanvas->helmetSpriteHeight / 2; + graphics->setClip(helmetX, helmetY, micro->gameCanvas->helmetSpriteWidth, micro->gameCanvas->helmetSpriteHeight); + graphics->drawImage(micro->gameCanvas->helmetImage.get(), helmetX - micro->gameCanvas->helmetSpriteWidth * (helmetAnimFrame % 6), helmetY - micro->gameCanvas->helmetSpriteHeight * (helmetAnimFrame / 6), 20); graphics->setClip(0, 0, canvasWidth, canvasHeight); - ++field_110; - if (field_110 > 30) { - field_110 = 0; + ++helmetAnimFrame; + if (helmetAnimFrame > 30) { + helmetAnimFrame = 0; } } - if (var4->isNotTextRender()) { - var2 += font2->getBaselinePosition() + field_103; + if (element->isNotTextRender()) { + yPos += font2->getBaselinePosition() + itemSpacing; } else { - var2 += (TextRender::getBaselinePosition() < GameCanvas::spriteSizeY[5] ? GameCanvas::spriteSizeY[5] : TextRender::getBaselinePosition()) + field_103; + yPos += (TextRender::getBaselinePosition() < GameCanvas::spriteSizeY[5] ? GameCanvas::spriteSizeY[5] : TextRender::getBaselinePosition()) + itemSpacing; } } - if (static_cast(vector.size()) > field_106 && field_106 != static_cast(vector.size()) - 1) { - if (GameCanvas::spriteSizeY[3] + var2 > canvasHeight) { + if (static_cast(vector.size()) > scrollOffsetLast && scrollOffsetLast != static_cast(vector.size()) - 1) { + if (GameCanvas::spriteSizeY[3] + yPos > canvasHeight) { micro->gameCanvas->drawSprite(graphics, 3, xPos - 3, canvasHeight - GameCanvas::spriteSizeY[3]); return; } - micro->gameCanvas->drawSprite(graphics, 3, xPos - 3, var2 - 2); + micro->gameCanvas->drawSprite(graphics, 3, xPos - 3, yPos - 2); } } } @@ -395,17 +395,17 @@ GameMenu* GameMenu::getGameMenu() return gameMenu; } -int GameMenu::method_79() +int GameMenu::getSelectedItemIndex() { - return field_95; + return selectedItemIndex; } void GameMenu::clearVector() { vector.clear(); - field_105 = 0; - field_106 = 0; - field_95 = -1; + scrollOffsetFirst = 0; + scrollOffsetLast = 0; + selectedItemIndex = -1; } std::string GameMenu::makeString() @@ -418,15 +418,15 @@ char* GameMenu::getStrArr() const return strArr; } -void GameMenu::method_83(int var1) +void GameMenu::navigateToItem(int targetIndex) { - method_70(); + resetToFirstItem(); - while (field_95 < var1) { - ++field_95; - if (field_95 > field_106) { - ++field_105; - ++field_106; + while (selectedItemIndex < targetIndex) { + ++selectedItemIndex; + if (selectedItemIndex > scrollOffsetLast) { + ++scrollOffsetFirst; + ++scrollOffsetLast; } } } diff --git a/src/GameMenu.h b/src/GameMenu.h index fed448e..c96f244 100644 --- a/src/GameMenu.h +++ b/src/GameMenu.h @@ -13,44 +13,52 @@ class Graphics; class GameMenu { private: GameMenu* gameMenu; - std::string field_94; - int field_95; + std::string menuTitle; + int selectedItemIndex; std::vector vector; Micro* micro; std::shared_ptr font; std::shared_ptr font2; std::shared_ptr font3; - int field_101; - int field_103; - int field_104; - int field_105; - int field_106; - int field_107; + // Margin/padding value + int marginPadding; + // Spacing between menu items + int itemSpacing; + // X offset for rendering + int renderXOffset; + // First visible item index + int scrollOffsetFirst; + // Last visible item index + int scrollOffsetLast; + // Maximum number of visible items + int maxVisibleItems; int canvasWidth; int canvasHeight; - int field_110; - bool field_111; + // Animation frame counter for helmet sprite + int helmetAnimFrame; + // Is in name input mode + bool isInputMode; int nameCursorPos; char* strArr; public: int xPos; - GameMenu(std::string var1, Micro* micro, GameMenu* var3, char* inputString = nullptr); - void method_68(int var1); - void method_69(std::string var1); - void method_70(); - void method_71(); - void addMenuElement(IGameMenuElement* var1); + GameMenu(std::string title, Micro* micro, GameMenu* parentMenu, char* inputString = nullptr); + void setItemSpacing(int spacing); + void setMenuTitle(std::string title); + void resetToFirstItem(); + void resetToLastItem(); + void addMenuElement(IGameMenuElement* element); void processGameActionDown(); void processGameActionUp(); - void processGameActionUpd(int var1); - void render_76(Graphics* graphics); + void processGameActionUpd(int action); + void render(Graphics* graphics); void setGameMenu(GameMenu* gameMenu); GameMenu* getGameMenu(); - int method_79(); + int getSelectedItemIndex(); void clearVector(); std::string makeString(); char* getStrArr() const; - void method_83(int var1); + void navigateToItem(int targetIndex); }; diff --git a/src/GamePhysics.cpp b/src/GamePhysics.cpp index d776b18..03b5b29 100644 --- a/src/GamePhysics.cpp +++ b/src/GamePhysics.cpp @@ -1,19 +1,19 @@ #include "GamePhysics.h" #include "LevelLoader.h" -#include "class_10.h" +#include "MotoComponent.h" #include "MathF16.h" #include GamePhysics::GamePhysics(LevelLoader* levelLoader) { - for (int var2 = 0; var2 < 6; ++var2) { - motoComponents[var2] = std::make_unique(); + for (int i = 0; i < 6; ++i) { + renderCache[i] = std::make_unique(); } - field_44 = 0; - field_45 = 0; - field_46 = false; + physicsFrameCounter = 0; + renderMode = 0; + isRenderBodySprites = false; isRenderMotoWithSprites = false; isInputAcceleration = false; isInputBreak = false; @@ -23,275 +23,279 @@ GamePhysics::GamePhysics(LevelLoader* levelLoader) isInputDown = false; isInputLeft = false; isInputRight = false; - field_68 = false; - field_69 = false; + isTrackFinishedFlag = false; + frontWheelContactLatch = false; isEnableLookAhead = true; camShiftX = 0; camShiftY = 0; - field_73 = 655360; + cameraLookAheadLimit = 655360; - field_80 = { { 45875 }, { 32768 }, { 52428 } }; + torsoAnchorOffsets = { + { 45875 }, // Lean Back: Sprite center is 70% toward the shoulder + { 32768 }, // Neutral: Sprite center is 50% (middle) + { 52428 } // Lean Forward: Sprite center is 80% toward the shoulder + }; this->levelLoader = levelLoader; - resetSmth(true); + resetPhysicsState(true); isGenerateInputAI = false; - method_53(); - field_35 = false; + captureRenderSnapshot(); + isBikeDestroyed = false; } -int GamePhysics::method_21() +int GamePhysics::getRenderModeIndex() { - if (field_46 && isRenderMotoWithSprites) { + if (isRenderBodySprites && isRenderMotoWithSprites) { return 3; } else if (isRenderMotoWithSprites) { return 1; } else { - return field_46 ? 2 : 0; + return isRenderBodySprites ? 2 : 0; } } -void GamePhysics::method_22(int var1) +void GamePhysics::setRenderFlags(int flags) { - field_46 = false; + isRenderBodySprites = false; isRenderMotoWithSprites = false; - if ((var1 & 2) != 0) { - field_46 = true; + if ((flags & 2) != 0) { + isRenderBodySprites = true; } - if ((var1 & 1) != 0) { + if ((flags & 1) != 0) { isRenderMotoWithSprites = true; } } void GamePhysics::setMode(int mode) { - field_45 = mode; + renderMode = mode; switch (mode) { case 1: default: - field_7 = 1310; - field_8 = 1638400; + physicsSubstepsPerFrame = 1310; + gravityF16 = 1638400; setMotoLeague(1); - resetSmth(true); + resetPhysicsState(true); } } void GamePhysics::setMotoLeague(int league) { - curentMotoLeague = league; - field_9 = 45875; - field_10 = 13107; - field_11 = 39321; - field_14 = 1310720; - field_16 = 262144; - field_19 = 6553; + currentLeague = league; + normalFrictionF16 = 45875; + tangentialFrictionF16 = 13107; + restitutionF16 = 39321; + globalMassScalerF16 = 1310720; + defaultWheelAngleF16 = 262144; + engineMomentumDecayF16 = 6553; switch (league) { case 0: default: - motoParam1 = 19660; - motoParam2 = 19660; - motoParam3 = 1114112; - motoParam4 = 52428800; - motoParam5 = 3276800; - motoParam6 = 327; - motoParam7 = 0; - motoParam8 = 32768; - motoParam9 = 327680; - motoParam10 = 19660800; + leanForceCoefficientXF16 = 19660; + leanForceCoefficientYF16 = 19660; + maxAngularVelocityF16 = 1114112; + maxEngineMomentumF16 = 52428800; + engineAccelerationRateF16 = 3276800; + brakeAngularDampingF16 = 327; + brakeFrictionModifierF16 = 0; + leanInputSensitivityF16 = 32768; + maxLeanRateF16 = 327680; + defaultXOffsetF16 = 19660800; break; case 1: - motoParam1 = 32768; - motoParam2 = 32768; - motoParam3 = 1114112; - motoParam4 = 65536000; - motoParam5 = 3276800; - motoParam6 = 6553; - motoParam7 = 26214; - motoParam8 = 26214; - motoParam9 = 327680; - motoParam10 = 19660800; + leanForceCoefficientXF16 = 32768; + leanForceCoefficientYF16 = 32768; + maxAngularVelocityF16 = 1114112; + maxEngineMomentumF16 = 65536000; + engineAccelerationRateF16 = 3276800; + brakeAngularDampingF16 = 6553; + brakeFrictionModifierF16 = 26214; + leanInputSensitivityF16 = 26214; + maxLeanRateF16 = 327680; + defaultXOffsetF16 = 19660800; break; case 2: - motoParam1 = 32768; - motoParam2 = 32768; - motoParam3 = 1310720; - motoParam4 = 75366400; - motoParam5 = 3473408; - motoParam6 = 6553; - motoParam7 = 26214; - motoParam8 = 39321; - motoParam9 = 327680; - motoParam10 = 21626880; + leanForceCoefficientXF16 = 32768; + leanForceCoefficientYF16 = 32768; + maxAngularVelocityF16 = 1310720; + maxEngineMomentumF16 = 75366400; + engineAccelerationRateF16 = 3473408; + brakeAngularDampingF16 = 6553; + brakeFrictionModifierF16 = 26214; + leanInputSensitivityF16 = 39321; + maxLeanRateF16 = 327680; + defaultXOffsetF16 = 21626880; break; case 3: - motoParam1 = 32768; - motoParam2 = 32768; - motoParam3 = 1441792; - motoParam4 = 78643200; - motoParam5 = 3538944; - motoParam6 = 6553; - motoParam7 = 26214; - motoParam8 = 65536; - motoParam9 = 1310720; - motoParam10 = 21626880; + leanForceCoefficientXF16 = 32768; + leanForceCoefficientYF16 = 32768; + maxAngularVelocityF16 = 1441792; + maxEngineMomentumF16 = 78643200; + engineAccelerationRateF16 = 3538944; + brakeAngularDampingF16 = 6553; + brakeFrictionModifierF16 = 26214; + leanInputSensitivityF16 = 65536; + maxLeanRateF16 = 1310720; + defaultXOffsetF16 = 21626880; } - resetSmth(true); + resetPhysicsState(true); } -void GamePhysics::resetSmth(bool unused) +void GamePhysics::resetPhysicsState(bool unused) { (void)unused; - field_44 = 0; - method_27(levelLoader->method_93(), levelLoader->method_94()); - field_31 = 0; - field_39 = 0; - field_35 = false; - field_36 = false; - field_68 = false; - field_69 = false; + physicsFrameCounter = 0; + resetPhysics(levelLoader->getStartPosX(), levelLoader->getStartPosY()); + engineMomentumF16 = 0; + leanRateAccumulatorF16 = 0; + isBikeDestroyed = false; + isPlayerHeadCrashed = false; + isTrackFinishedFlag = false; + frontWheelContactLatch = false; isGenerateInputAI = false; - field_41 = false; - field_42 = false; - levelLoader->gameLevel->method_183(field_29[2]->motoComponents[5]->xF16 + 98304 - const175_1_half[0], field_29[1]->motoComponents[5]->xF16 - 98304 + const175_1_half[0]); + isTrackStartedFlag2 = false; + isTrackStartedFlag = false; + levelLoader->gameLevel->setShadowBoundariesHalf(motoComponents[2]->stateBuffers[5]->xF16 + 98304 - wheelRadiusValuesF16[0], motoComponents[1]->stateBuffers[5]->xF16 - 98304 + wheelRadiusValuesF16[0]); } -void GamePhysics::method_26(bool var1) +void GamePhysics::invertYPositions(bool isInverted) { - int var2 = (var1 ? 65536 : -65536) << 1; + int yOffset = (isInverted ? 65536 : -65536) << 1; - for (int var3 = 0; var3 < 6; ++var3) { - for (int var4 = 0; var4 < 6; ++var4) { - field_29[var3]->motoComponents[var4]->yF16 += var2; + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 6; ++j) { + motoComponents[i]->stateBuffers[j]->yF16 += yOffset; } } } -void GamePhysics::method_27(int var1, int var2) +void GamePhysics::resetPhysics(int startX, int startY) { - if (field_29.empty()) { - field_29 = std::vector>(6); + if (motoComponents.empty()) { + motoComponents = std::vector>(6); } - if (field_30.empty()) { - field_30 = std::vector>(10); + if (springConstraints.empty()) { + springConstraints = std::vector>(10); } - int var4 = 0; - int8_t var5 = 0; - int var6 = 0; - int var7 = 0; + int massValue = 0; + int8_t radiusIdx = 0; + int xOffset = 0; + int yOffset = 0; int i; for (i = 0; i < 6; ++i) { - short var8 = 0; + short leanInf = 0; switch (i) { - case 0: - var5 = 1; - var4 = 360448; - var6 = 0; - var7 = 0; + case 0: // Chassis (center) + radiusIdx = 1; + massValue = 360448; + xOffset = 0; + yOffset = 0; break; - case 1: - var5 = 0; - var4 = 98304; - var6 = 229376; - var7 = 0; + case 1: // Front wheel + radiusIdx = 0; + massValue = 98304; + xOffset = 229376; + yOffset = 0; break; - case 2: - var5 = 0; - var4 = 360448; - var6 = -229376; - var7 = 0; - var8 = 21626; + case 2: // Back wheel + radiusIdx = 0; + massValue = 360448; + xOffset = -229376; + yOffset = 0; + leanInf = 21626; break; - case 3: - var5 = 1; - var4 = 229376; - var6 = 131072; - var7 = 196608; + case 3: // Handlebar + radiusIdx = 1; + massValue = 229376; + xOffset = 131072; + yOffset = 196608; break; - case 4: - var5 = 1; - var4 = 229376; - var6 = -131072; - var7 = 196608; + case 4: // Seat + radiusIdx = 1; + massValue = 229376; + xOffset = -131072; + yOffset = 196608; break; - case 5: - var5 = 2; - var4 = 294912; - var6 = 0; - var7 = 327680; + case 5: // Rider head + radiusIdx = 2; + massValue = 294912; + xOffset = 0; + yOffset = 327680; } - if (field_29[i] == nullptr) { - field_29[i] = std::make_unique(); + if (motoComponents[i] == nullptr) { + motoComponents[i] = std::make_unique(); } - field_29[i]->reset(); - field_29[i]->field_257 = const175_1_half[var5]; - field_29[i]->field_258 = var5; - field_29[i]->field_259 = (int)((int64_t)((int)(281474976710656L / (int64_t)var4 >> 16)) * (int64_t)field_14 >> 16); - field_29[i]->motoComponents[index01]->xF16 = var1 + var6; - field_29[i]->motoComponents[index01]->yF16 = var2 + var7; - field_29[i]->motoComponents[5]->xF16 = var1 + var6; - field_29[i]->motoComponents[5]->yF16 = var2 + var7; - field_29[i]->field_260 = var8; + motoComponents[i]->reset(); + motoComponents[i]->radiusF16 = wheelRadiusValuesF16[radiusIdx]; + motoComponents[i]->radiusIndex = radiusIdx; + motoComponents[i]->inverseMassF16 = (int)((int64_t)((int)(281474976710656L / (int64_t)massValue >> 16)) * (int64_t)globalMassScalerF16 >> 16); + motoComponents[i]->stateBuffers[readBufferIndex]->xF16 = startX + xOffset; + motoComponents[i]->stateBuffers[readBufferIndex]->yF16 = startY + yOffset; + motoComponents[i]->stateBuffers[5]->xF16 = startX + xOffset; + motoComponents[i]->stateBuffers[5]->yF16 = startY + yOffset; + motoComponents[i]->leanInfluenceF16 = leanInf; } for (i = 0; i < 10; ++i) { - if (field_30[i] == nullptr) { - field_30[i] = std::make_unique(); + if (springConstraints[i] == nullptr) { + springConstraints[i] = std::make_unique(); } - field_30[i]->setToZeros(); - field_30[i]->xF16 = motoParam10; - field_30[i]->angleF16 = field_16; + springConstraints[i]->setToZeros(); + springConstraints[i]->xF16 = defaultXOffsetF16; + springConstraints[i]->angleF16 = defaultWheelAngleF16; } - field_30[0]->yF16 = 229376; - field_30[1]->yF16 = 229376; - field_30[2]->yF16 = 236293; - field_30[3]->yF16 = 236293; - field_30[4]->yF16 = 262144; - field_30[5]->yF16 = 219814; - field_30[6]->yF16 = 219814; - field_30[7]->yF16 = 185363; - field_30[8]->yF16 = 185363; - field_30[9]->yF16 = 327680; - field_30[5]->angleF16 = (int)((int64_t)field_16 * 45875L >> 16); - field_30[6]->xF16 = (int)(6553L * (int64_t)motoParam10 >> 16); - field_30[5]->xF16 = (int)(6553L * (int64_t)motoParam10 >> 16); - field_30[9]->xF16 = (int)(72089L * (int64_t)motoParam10 >> 16); - field_30[8]->xF16 = (int)(72089L * (int64_t)motoParam10 >> 16); - field_30[7]->xF16 = (int)(72089L * (int64_t)motoParam10 >> 16); + springConstraints[0]->yF16 = 229376; + springConstraints[1]->yF16 = 229376; + springConstraints[2]->yF16 = 236293; + springConstraints[3]->yF16 = 236293; + springConstraints[4]->yF16 = 262144; + springConstraints[5]->yF16 = 219814; + springConstraints[6]->yF16 = 219814; + springConstraints[7]->yF16 = 185363; + springConstraints[8]->yF16 = 185363; + springConstraints[9]->yF16 = 327680; + springConstraints[5]->angleF16 = (int)((int64_t)defaultWheelAngleF16 * 45875L >> 16); + springConstraints[6]->xF16 = (int)(6553L * (int64_t)defaultXOffsetF16 >> 16); + springConstraints[5]->xF16 = (int)(6553L * (int64_t)defaultXOffsetF16 >> 16); + springConstraints[9]->xF16 = (int)(72089L * (int64_t)defaultXOffsetF16 >> 16); + springConstraints[8]->xF16 = (int)(72089L * (int64_t)defaultXOffsetF16 >> 16); + springConstraints[7]->xF16 = (int)(72089L * (int64_t)defaultXOffsetF16 >> 16); } void GamePhysics::setRenderMinMaxX(int minX, int maxX) { - levelLoader->setMinMaxX(minX, maxX); + levelLoader->setLevelBounds(minX, maxX); } -void GamePhysics::processPointerReleased() +void GamePhysics::resetInputs() { isInputUp = isInputDown = isInputRight = isInputLeft = false; } -void GamePhysics::method_30(int var1, int var2) +void GamePhysics::updateInputs(int upDown, int leftRight) { if (!isGenerateInputAI) { isInputUp = isInputDown = isInputRight = isInputLeft = false; - if (var1 > 0) { + if (upDown > 0) { isInputUp = true; - } else if (var1 < 0) { + } else if (upDown < 0) { isInputDown = true; } - if (var2 > 0) { + if (leftRight > 0) { isInputRight = true; return; } - if (var2 < 0) { + if (leftRight < 0) { isInputLeft = true; } } @@ -299,7 +303,7 @@ void GamePhysics::method_30(int var1, int var2) void GamePhysics::enableGenerateInputAI() { - resetSmth(true); + resetPhysicsState(true); isGenerateInputAI = true; } @@ -310,158 +314,163 @@ void GamePhysics::disableGenerateInputAI() void GamePhysics::setInputFromAI() { - int var1 = field_29[1]->motoComponents[index01]->xF16 - field_29[2]->motoComponents[index01]->xF16; - int var2 = field_29[1]->motoComponents[index01]->yF16 - field_29[2]->motoComponents[index01]->yF16; - int var3 = getSmthLikeMaxAbs(var1, var2); - var2 = (int)(((int64_t)var2 << 32) / (int64_t)var3 >> 16); + int dx = motoComponents[1]->stateBuffers[readBufferIndex]->xF16 - motoComponents[2]->stateBuffers[readBufferIndex]->xF16; + int dy = motoComponents[1]->stateBuffers[readBufferIndex]->yF16 - motoComponents[2]->stateBuffers[readBufferIndex]->yF16; + int dist = fastVectorLengthF16(dx, dy); + dy = (int)(((int64_t)dy << 32) / (int64_t)dist >> 16); isInputBreak = false; - if (var2 < 0) { + if (dy < 0) { isInputBack = true; isInputForward = false; - } else if (var2 > 0) { + } else if (dy > 0) { isInputForward = true; isInputBack = false; } - bool var4; - if ((!(var4 = (field_29[2]->motoComponents[index01]->yF16 - field_29[0]->motoComponents[index01]->yF16 > 0 ? 1 : -1) * (field_29[2]->motoComponents[index01]->field_382 - field_29[0]->motoComponents[index01]->field_382 > 0 ? 1 : -1) > 0) || !isInputForward) && (var4 || !isInputBack)) { + bool pitchSign; // tilt direction matches horizontal motion difference + if ((!(pitchSign = (motoComponents[2]->stateBuffers[readBufferIndex]->yF16 - motoComponents[0]->stateBuffers[readBufferIndex]->yF16 > 0 ? 1 : -1) * (motoComponents[2]->stateBuffers[readBufferIndex]->velocityXF16 - motoComponents[0]->stateBuffers[readBufferIndex]->velocityXF16 > 0 ? 1 : -1) > 0) || !isInputForward) && (pitchSign || !isInputBack)) { isInputAcceleration = false; } else { isInputAcceleration = true; } } -void GamePhysics::method_35() +void GamePhysics::processLeanInput() { - if (!field_35) { - int var1 = field_29[1]->motoComponents[index01]->xF16 - field_29[2]->motoComponents[index01]->xF16; - int var2 = field_29[1]->motoComponents[index01]->yF16 - field_29[2]->motoComponents[index01]->yF16; - int var3 = getSmthLikeMaxAbs(var1, var2); - var1 = (int)(((int64_t)var1 << 32) / (int64_t)var3 >> 16); - var2 = (int)(((int64_t)var2 << 32) / (int64_t)var3 >> 16); - if (isInputAcceleration && field_31 >= -motoParam4) { - field_31 -= motoParam5; + if (!isBikeDestroyed) { + int dx = motoComponents[1]->stateBuffers[readBufferIndex]->xF16 - motoComponents[2]->stateBuffers[readBufferIndex]->xF16; + int dy = motoComponents[1]->stateBuffers[readBufferIndex]->yF16 - motoComponents[2]->stateBuffers[readBufferIndex]->yF16; + int dist = fastVectorLengthF16(dx, dy); + dx = (int)(((int64_t)dx << 32) / (int64_t)dist >> 16); + dy = (int)(((int64_t)dy << 32) / (int64_t)dist >> 16); + if (isInputAcceleration && engineMomentumF16 >= -maxEngineMomentumF16) { + engineMomentumF16 -= engineAccelerationRateF16; } if (isInputBreak) { - field_31 = 0; - field_29[1]->motoComponents[index01]->field_384 = (int)((int64_t)field_29[1]->motoComponents[index01]->field_384 * (int64_t)(65536 - motoParam6) >> 16); - field_29[2]->motoComponents[index01]->field_384 = (int)((int64_t)field_29[2]->motoComponents[index01]->field_384 * (int64_t)(65536 - motoParam6) >> 16); - if (field_29[1]->motoComponents[index01]->field_384 < 6553) { - field_29[1]->motoComponents[index01]->field_384 = 0; + engineMomentumF16 = 0; + motoComponents[1]->stateBuffers[readBufferIndex]->angularVelocityF16 = (int)((int64_t)motoComponents[1]->stateBuffers[readBufferIndex]->angularVelocityF16 * (int64_t)(65536 - brakeAngularDampingF16) >> 16); + motoComponents[2]->stateBuffers[readBufferIndex]->angularVelocityF16 = (int)((int64_t)motoComponents[2]->stateBuffers[readBufferIndex]->angularVelocityF16 * (int64_t)(65536 - brakeAngularDampingF16) >> 16); + if (motoComponents[1]->stateBuffers[readBufferIndex]->angularVelocityF16 < 6553) { + motoComponents[1]->stateBuffers[readBufferIndex]->angularVelocityF16 = 0; } - if (field_29[2]->motoComponents[index01]->field_384 < 6553) { - field_29[2]->motoComponents[index01]->field_384 = 0; + if (motoComponents[2]->stateBuffers[readBufferIndex]->angularVelocityF16 < 6553) { + motoComponents[2]->stateBuffers[readBufferIndex]->angularVelocityF16 = 0; } } - field_29[0]->field_259 = (int)(11915L * (int64_t)field_14 >> 16); - field_29[0]->field_259 = (int)(11915L * (int64_t)field_14 >> 16); - field_29[4]->field_259 = (int)(18724L * (int64_t)field_14 >> 16); - field_29[3]->field_259 = (int)(18724L * (int64_t)field_14 >> 16); - field_29[1]->field_259 = (int)(43690L * (int64_t)field_14 >> 16); - field_29[2]->field_259 = (int)(11915L * (int64_t)field_14 >> 16); - field_29[5]->field_259 = (int)(14563L * (int64_t)field_14 >> 16); + motoComponents[0]->inverseMassF16 = (int)(11915L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[0]->inverseMassF16 = (int)(11915L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[4]->inverseMassF16 = (int)(18724L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[3]->inverseMassF16 = (int)(18724L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[1]->inverseMassF16 = (int)(43690L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[2]->inverseMassF16 = (int)(11915L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[5]->inverseMassF16 = (int)(14563L * (int64_t)globalMassScalerF16 >> 16); if (isInputBack) { - field_29[0]->field_259 = (int)(18724L * (int64_t)field_14 >> 16); - field_29[4]->field_259 = (int)(14563L * (int64_t)field_14 >> 16); - field_29[3]->field_259 = (int)(18724L * (int64_t)field_14 >> 16); - field_29[1]->field_259 = (int)(43690L * (int64_t)field_14 >> 16); - field_29[2]->field_259 = (int)(10082L * (int64_t)field_14 >> 16); + motoComponents[0]->inverseMassF16 = (int)(18724L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[4]->inverseMassF16 = (int)(14563L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[3]->inverseMassF16 = (int)(18724L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[1]->inverseMassF16 = (int)(43690L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[2]->inverseMassF16 = (int)(10082L * (int64_t)globalMassScalerF16 >> 16); } else if (isInputForward) { - field_29[0]->field_259 = (int)(18724L * (int64_t)field_14 >> 16); - field_29[4]->field_259 = (int)(18724L * (int64_t)field_14 >> 16); - field_29[3]->field_259 = (int)(14563L * (int64_t)field_14 >> 16); - field_29[1]->field_259 = (int)(26214L * (int64_t)field_14 >> 16); - field_29[2]->field_259 = (int)(11915L * (int64_t)field_14 >> 16); + motoComponents[0]->inverseMassF16 = (int)(18724L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[4]->inverseMassF16 = (int)(18724L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[3]->inverseMassF16 = (int)(14563L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[1]->inverseMassF16 = (int)(26214L * (int64_t)globalMassScalerF16 >> 16); + motoComponents[2]->inverseMassF16 = (int)(11915L * (int64_t)globalMassScalerF16 >> 16); } if (isInputBack || isInputForward) { - int var4 = -var2; - TimerOrMotoPartOrMenuElem* var10000; - int var6; - int var7; - int var8; - int var9; - int var10; - int var11; - if (isInputBack && field_39 > -motoParam9) { - var6 = 65536; - if (field_39 < 0) { - var6 = (int)(((int64_t)(motoParam9 - (field_39 < 0 ? -field_39 : field_39)) << 32) / (int64_t)motoParam9 >> 16); + int negDy = -dy; + PhysicsElemOrMenuItem* elem; + int interpFactor; + int sensitivity; + int forceX; + int forceY; + int riderForceX; + int riderForceY; + if (isInputBack && leanRateAccumulatorF16 > -maxLeanRateF16) { + interpFactor = 65536; + if (leanRateAccumulatorF16 < 0) { + interpFactor = (int)(((int64_t)(maxLeanRateF16 - (leanRateAccumulatorF16 < 0 ? -leanRateAccumulatorF16 : leanRateAccumulatorF16)) << 32) / (int64_t)maxLeanRateF16 >> 16); } - var7 = (int)((int64_t)motoParam8 * (int64_t)var6 >> 16); - var8 = (int)((int64_t)var4 * (int64_t)var7 >> 16); - var9 = (int)((int64_t)var1 * (int64_t)var7 >> 16); - var10 = (int)((int64_t)var1 * (int64_t)var7 >> 16); - var11 = (int)((int64_t)var2 * (int64_t)var7 >> 16); - if (field_37 > 32768) { - field_37 = field_37 - 1638 < 0 ? 0 : field_37 - 1638; + sensitivity = (int)((int64_t)leanInputSensitivityF16 * (int64_t)interpFactor >> 16); + forceX = (int)((int64_t)negDy * (int64_t)sensitivity >> 16); + forceY = (int)((int64_t)dx * (int64_t)sensitivity >> 16); + riderForceX = (int)((int64_t)dx * (int64_t)sensitivity >> 16); + riderForceY = (int)((int64_t)dy * (int64_t)sensitivity >> 16); + if (leanF16 > 32768) { + leanF16 = leanF16 - 1638 < 0 ? 0 : leanF16 - 1638; } else { - field_37 = field_37 - 3276 < 0 ? 0 : field_37 - 3276; + leanF16 = leanF16 - 3276 < 0 ? 0 : leanF16 - 3276; } - var10000 = field_29[4]->motoComponents[index01].get(); - var10000->field_382 -= var8; - var10000 = field_29[4]->motoComponents[index01].get(); - var10000->field_383 -= var9; - var10000 = field_29[3]->motoComponents[index01].get(); - var10000->field_382 += var8; - var10000 = field_29[3]->motoComponents[index01].get(); - var10000->field_383 += var9; - var10000 = field_29[5]->motoComponents[index01].get(); - var10000->field_382 -= var10; - var10000 = field_29[5]->motoComponents[index01].get(); - var10000->field_383 -= var11; + // seat + elem = motoComponents[4]->stateBuffers[readBufferIndex].get(); + elem->velocityXF16 -= forceX; + elem = motoComponents[4]->stateBuffers[readBufferIndex].get(); + elem->velocityYF16 -= forceY; + // handlebar + elem = motoComponents[3]->stateBuffers[readBufferIndex].get(); + elem->velocityXF16 += forceX; + elem = motoComponents[3]->stateBuffers[readBufferIndex].get(); + elem->velocityYF16 += forceY; + // rider + elem = motoComponents[5]->stateBuffers[readBufferIndex].get(); + elem->velocityXF16 -= riderForceX; + elem = motoComponents[5]->stateBuffers[readBufferIndex].get(); + elem->velocityYF16 -= riderForceY; } - if (isInputForward && field_39 < motoParam9) { - var6 = 65536; - if (field_39 > 0) { - var6 = (int)(((int64_t)(motoParam9 - field_39) << 32) / (int64_t)motoParam9 >> 16); + if (isInputForward && leanRateAccumulatorF16 < maxLeanRateF16) { + interpFactor = 65536; + if (leanRateAccumulatorF16 > 0) { + interpFactor = (int)(((int64_t)(maxLeanRateF16 - leanRateAccumulatorF16) << 32) / (int64_t)maxLeanRateF16 >> 16); } - var7 = (int)((int64_t)motoParam8 * (int64_t)var6 >> 16); - var8 = (int)((int64_t)var4 * (int64_t)var7 >> 16); - var9 = (int)((int64_t)var1 * (int64_t)var7 >> 16); - var10 = (int)((int64_t)var1 * (int64_t)var7 >> 16); - var11 = (int)((int64_t)var2 * (int64_t)var7 >> 16); - if (field_37 > 32768) { - field_37 = field_37 + 1638 < 65536 ? field_37 + 1638 : 65536; + sensitivity = (int)((int64_t)leanInputSensitivityF16 * (int64_t)interpFactor >> 16); + forceX = (int)((int64_t)negDy * (int64_t)sensitivity >> 16); + forceY = (int)((int64_t)dx * (int64_t)sensitivity >> 16); + riderForceX = (int)((int64_t)dx * (int64_t)sensitivity >> 16); + riderForceY = (int)((int64_t)dy * (int64_t)sensitivity >> 16); + if (leanF16 > 32768) { + leanF16 = leanF16 + 1638 < 65536 ? leanF16 + 1638 : 65536; } else { - field_37 = field_37 + 3276 < 65536 ? field_37 + 3276 : 65536; + leanF16 = leanF16 + 3276 < 65536 ? leanF16 + 3276 : 65536; } - var10000 = field_29[4]->motoComponents[index01].get(); - var10000->field_382 += var8; - var10000 = field_29[4]->motoComponents[index01].get(); - var10000->field_383 += var9; - var10000 = field_29[3]->motoComponents[index01].get(); - var10000->field_382 -= var8; - var10000 = field_29[3]->motoComponents[index01].get(); - var10000->field_383 -= var9; - var10000 = field_29[5]->motoComponents[index01].get(); - var10000->field_382 += var10; - var10000 = field_29[5]->motoComponents[index01].get(); - var10000->field_383 += var11; + // seat + elem = motoComponents[4]->stateBuffers[readBufferIndex].get(); + elem->velocityXF16 += forceX; + elem = motoComponents[4]->stateBuffers[readBufferIndex].get(); + elem->velocityYF16 += forceY; + // handlebar + elem = motoComponents[3]->stateBuffers[readBufferIndex].get(); + elem->velocityXF16 -= forceX; + elem = motoComponents[3]->stateBuffers[readBufferIndex].get(); + elem->velocityYF16 -= forceY; + // rider + elem = motoComponents[5]->stateBuffers[readBufferIndex].get(); + elem->velocityXF16 += riderForceX; + elem = motoComponents[5]->stateBuffers[readBufferIndex].get(); + elem->velocityYF16 += riderForceY; } return; } - if (field_37 < 26214) { - field_37 += 3276; + // Auto-center lean when no input + if (leanF16 < 26214) { + leanF16 += 3276; return; } - - if (field_37 > 39321) { - field_37 -= 3276; + if (leanF16 > 39321) { + leanF16 -= 3276; return; } - - field_37 = 32768; + leanF16 = 32768; } } @@ -475,17 +484,17 @@ int GamePhysics::updatePhysics() setInputFromAI(); } - GameCanvas::method_151(); - method_35(); - int var1; - if ((var1 = method_39(field_7)) != 5 && !field_36) { - if (field_35) { + GameCanvas::flagAnimation(); + processLeanInput(); + int result; + if ((result = physicsSubstepLoop(physicsSubstepsPerFrame)) != 5 && !isPlayerHeadCrashed) { + if (isBikeDestroyed) { return 3; } else if (isTrackStarted()) { - field_69 = false; + frontWheelContactLatch = false; return 4; } else { - return var1; + return result; } } else { return 5; @@ -494,152 +503,167 @@ int GamePhysics::updatePhysics() bool GamePhysics::isTrackStarted() { - return field_29[1]->motoComponents[index01]->xF16 < levelLoader->method_92(); + return motoComponents[1]->stateBuffers[readBufferIndex]->xF16 < levelLoader->getStartFlagX(); } -bool GamePhysics::method_38() +bool GamePhysics::isTrackFinished() { - return field_29[1]->motoComponents[index10]->xF16 > levelLoader->method_91() || field_29[2]->motoComponents[index10]->xF16 > levelLoader->method_91(); + return motoComponents[1]->stateBuffers[writeBufferIndex]->xF16 > levelLoader->getFinishFlagX() || motoComponents[2]->stateBuffers[writeBufferIndex]->xF16 > levelLoader->getFinishFlagX(); } -int GamePhysics::method_39(int var1) +int GamePhysics::physicsSubstepLoop(int iterations) { - bool var2 = field_68; - int var3 = 0; - int var4 = var1; + bool isTrackFinishedPrev = isTrackFinishedFlag; + int low = 0; + int high = iterations; label77: do { - int var5; - while (var3 < var1) { - method_45(var4 - var3); - if (!var2 && method_38()) { - var5 = 3; + int collisionResult; + while (low < iterations) { + performPhysicsSubstep(high - low); + if (!isTrackFinishedPrev && isTrackFinished()) { + collisionResult = 3; } else { - var5 = method_46(index10); + collisionResult = checkTrackCollisions(writeBufferIndex); } - if (!var2 && field_68) { - if (var5 != 3) { + // Finish reached? + if (!isTrackFinishedPrev && isTrackFinishedFlag) { + if (collisionResult != 3) { return 2; } return 1; } - if (var5 == 0) { - var4 = (var3 + var4) >> 1; + if (collisionResult == 0) { + high = (low + high) >> 1; goto label77; } - if (var5 == 3) { - field_68 = true; - var4 = (var3 + var4) >> 1; + if (collisionResult == 3) { + isTrackFinishedFlag = true; + high = (low + high) >> 1; } else { - int var6; - if (var5 == 1) { + int res; + if (collisionResult == 1) { do { - method_47(index10); - if ((var6 = method_46(index10)) == 0) { + applyCollisionResponse(writeBufferIndex); + if ((res = checkTrackCollisions(writeBufferIndex)) == 0) { return 5; } - } while (var6 != 2); + } while (res != 2); } - var3 = var4; - var4 = var1; - index01 = index01 == 1 ? 0 : 1; - index10 = index10 == 1 ? 0 : 1; + low = high; + high = iterations; + readBufferIndex = readBufferIndex == 1 ? 0 : 1; + writeBufferIndex = writeBufferIndex == 1 ? 0 : 1; } } - if ((var5 = (int)((int64_t)(field_29[1]->motoComponents[index01]->xF16 - field_29[2]->motoComponents[index01]->xF16) * (int64_t)(field_29[1]->motoComponents[index01]->xF16 - field_29[2]->motoComponents[index01]->xF16) >> 16) + (int)((int64_t)(field_29[1]->motoComponents[index01]->yF16 - field_29[2]->motoComponents[index01]->yF16) * (int64_t)(field_29[1]->motoComponents[index01]->yF16 - field_29[2]->motoComponents[index01]->yF16) >> 16)) < 983040) { - field_35 = true; + // Check if bike breaks (distance between wheels too small or too large) + int dx = motoComponents[1]->stateBuffers[readBufferIndex]->xF16 - motoComponents[2]->stateBuffers[readBufferIndex]->xF16; + int dy = motoComponents[1]->stateBuffers[readBufferIndex]->yF16 - motoComponents[2]->stateBuffers[readBufferIndex]->yF16; + int distSq = (int)((int64_t)dx * (int64_t)dx >> 16) + (int)((int64_t)dy * (int64_t)dy >> 16); + + if (distSq < 983040) { + isBikeDestroyed = true; } - if (var5 > 4587520) { - field_35 = true; + if (distSq > 4587520) { + isBikeDestroyed = true; } return 0; - } while (((var4 = (var3 + var4) >> 1) - var3 < 0 ? -(var4 - var3) : var4 - var3) >= 65); + } while (((high = (low + high) >> 1) - low < 0 ? -(high - low) : high - low) >= 65); return 5; } -void GamePhysics::method_40(int var1) +void GamePhysics::applyForces(int bufferIndex) { - TimerOrMotoPartOrMenuElem* var3; - int var4; - for (var4 = 0; var4 < 6; ++var4) { - class_10* var2 = field_29[var4].get(); - var3 = var2->motoComponents[var1].get(); - var3->field_385 = 0; - - var3->field_386 = 0; - var3->field_387 = 0; - var3->field_386 -= (int)(((int64_t)field_8 << 32) / (int64_t)var2->field_259 >> 16); - } + PhysicsElemOrMenuItem* elem; + int i; - if (!field_35) { - method_42(field_29[0].get(), field_30[1].get(), field_29[2].get(), var1, 65536); - method_42(field_29[0].get(), field_30[0].get(), field_29[1].get(), var1, 65536); - method_42(field_29[2].get(), field_30[6].get(), field_29[4].get(), var1, 131072); - method_42(field_29[1].get(), field_30[5].get(), field_29[3].get(), var1, 131072); + for (i = 0; i < 6; ++i) { + MotoComponent* comp = motoComponents[i].get(); + elem = comp->stateBuffers[bufferIndex].get(); + elem->forceAccumXF16 = 0; + + elem->forceAccumYF16 = 0; + elem->torqueF16 = 0; + // Apply gravity: force = gravity / inverseMass = gravity * mass + elem->forceAccumYF16 -= (int)(((int64_t)gravityF16 << 32) / (int64_t)comp->inverseMassF16 >> 16); } - method_42(field_29[0].get(), field_30[2].get(), field_29[3].get(), var1, 65536); - method_42(field_29[0].get(), field_30[3].get(), field_29[4].get(), var1, 65536); - method_42(field_29[3].get(), field_30[4].get(), field_29[4].get(), var1, 65536); - method_42(field_29[5].get(), field_30[8].get(), field_29[3].get(), var1, 65536); - method_42(field_29[5].get(), field_30[7].get(), field_29[4].get(), var1, 65536); - method_42(field_29[5].get(), field_30[9].get(), field_29[0].get(), var1, 65536); - var3 = field_29[2]->motoComponents[var1].get(); - field_31 = (int)((int64_t)field_31 * (int64_t)(65536 - field_19) >> 16); - var3->field_387 = field_31; - if (var3->field_384 > motoParam3) { - var3->field_384 = motoParam3; - } + // Apply spring constraints between bike components - if (var3->field_384 < -motoParam3) { - var3->field_384 = -motoParam3; + if (!isBikeDestroyed) { + // Keep bike together while it's intact + applySpringConstraint(motoComponents[0].get(), springConstraints[1].get(), motoComponents[2].get(), bufferIndex, 65536); + applySpringConstraint(motoComponents[0].get(), springConstraints[0].get(), motoComponents[1].get(), bufferIndex, 65536); + applySpringConstraint(motoComponents[2].get(), springConstraints[6].get(), motoComponents[4].get(), bufferIndex, 131072); + applySpringConstraint(motoComponents[1].get(), springConstraints[5].get(), motoComponents[3].get(), bufferIndex, 131072); } - var4 = 0; - int var5 = 0; + applySpringConstraint(motoComponents[0].get(), springConstraints[2].get(), motoComponents[3].get(), bufferIndex, 65536); + applySpringConstraint(motoComponents[0].get(), springConstraints[3].get(), motoComponents[4].get(), bufferIndex, 65536); + applySpringConstraint(motoComponents[3].get(), springConstraints[4].get(), motoComponents[4].get(), bufferIndex, 65536); + applySpringConstraint(motoComponents[5].get(), springConstraints[8].get(), motoComponents[3].get(), bufferIndex, 65536); + applySpringConstraint(motoComponents[5].get(), springConstraints[7].get(), motoComponents[4].get(), bufferIndex, 65536); + applySpringConstraint(motoComponents[5].get(), springConstraints[9].get(), motoComponents[0].get(), bufferIndex, 65536); + + // Apply engine torque to back wheel and decay engine momentum + elem = motoComponents[2]->stateBuffers[bufferIndex].get(); + engineMomentumF16 = (int)((int64_t)engineMomentumF16 * (int64_t)(65536 - engineMomentumDecayF16) >> 16); + elem->torqueF16 = engineMomentumF16; + + // Clamp angular velocity + if (elem->angularVelocityF16 > maxAngularVelocityF16) { + elem->angularVelocityF16 = maxAngularVelocityF16; + } + if (elem->angularVelocityF16 < -maxAngularVelocityF16) { + elem->angularVelocityF16 = -maxAngularVelocityF16; + } - int var6; - for (var6 = 0; var6 < 6; ++var6) { - var4 += field_29[var6]->motoComponents[var1]->field_382; - var5 += field_29[var6]->motoComponents[var1]->field_383; + // Calculate center of mass velocity and clamp individual component velocities + int totalVx = 0; + int totalVy = 0; + for (i = 0; i < 6; ++i) { + totalVx += motoComponents[i]->stateBuffers[bufferIndex]->velocityXF16; + totalVy += motoComponents[i]->stateBuffers[bufferIndex]->velocityYF16; } - var4 = (int)(((int64_t)var4 << 32) / 393216L >> 16); - var5 = (int)(((int64_t)var5 << 32) / 393216L >> 16); - int var10 = 0; - - int var11; - for (var11 = 0; var11 < 6; ++var11) { - var6 = field_29[var11]->motoComponents[var1]->field_382 - var4; - int var7 = field_29[var11]->motoComponents[var1]->field_383 - var5; - if ((var10 = getSmthLikeMaxAbs(var6, var7)) > 1966080) { - int var8 = (int)(((int64_t)var6 << 32) / (int64_t)var10 >> 16); - int var9 = (int)(((int64_t)var7 << 32) / (int64_t)var10 >> 16); - field_29[var11]->motoComponents[var1]->field_382 -= var8; - field_29[var11]->motoComponents[var1]->field_383 -= var9; + // Average velocity (center of mass) + int avgVx = (int)(((int64_t)totalVx << 32) / 393216L >> 16); + int avgVy = (int)(((int64_t)totalVy << 32) / 393216L >> 16); + int maxRelVel = 0; + + for (i = 0; i < 6; ++i) { + int relVx = motoComponents[i]->stateBuffers[bufferIndex]->velocityXF16 - avgVx; + int relVy = motoComponents[i]->stateBuffers[bufferIndex]->velocityYF16 - avgVy; + if ((maxRelVel = fastVectorLengthF16(relVx, relVy)) > 1966080) { + // Normalize and clamp relative velocity + int normX = (int)(((int64_t)relVx << 32) / (int64_t)maxRelVel >> 16); + int normY = (int)(((int64_t)relVy << 32) / (int64_t)maxRelVel >> 16); + motoComponents[i]->stateBuffers[bufferIndex]->velocityXF16 -= normX; + motoComponents[i]->stateBuffers[bufferIndex]->velocityYF16 -= normY; } } - var11 = field_29[2]->motoComponents[var1]->yF16 - field_29[0]->motoComponents[var1]->yF16 >= 0 ? 1 : -1; - int var12 = field_29[2]->motoComponents[var1]->field_382 - field_29[0]->motoComponents[var1]->field_382 >= 0 ? 1 : -1; - if (var11 * var12 > 0) { - field_39 = var10; + // Update lean rate accumulator based on back wheel vs chassis movement + int backAboveCenter = motoComponents[2]->stateBuffers[bufferIndex]->yF16 - motoComponents[0]->stateBuffers[bufferIndex]->yF16 >= 0 ? 1 : -1; + int backVelFaster = motoComponents[2]->stateBuffers[bufferIndex]->velocityXF16 - motoComponents[0]->stateBuffers[bufferIndex]->velocityXF16 >= 0 ? 1 : -1; + if (backAboveCenter * backVelFaster > 0) { + leanRateAccumulatorF16 = maxRelVel; } else { - field_39 = -var10; + leanRateAccumulatorF16 = -maxRelVel; } } -int GamePhysics::getSmthLikeMaxAbs(int xF16, int yF16) +int GamePhysics::fastVectorLengthF16(int xF16, int yF16) { int absXF16 = xF16 < 0 ? -xF16 : xF16; int absYF16; @@ -653,175 +677,203 @@ int GamePhysics::getSmthLikeMaxAbs(int xF16, int yF16) minAbs = absYF16; } + // fast 2D vector length approximation return (int)(64448L * (int64_t)maxAbs >> 16) + (int)(28224L * (int64_t)minAbs >> 16); } -void GamePhysics::method_42(class_10* var1, TimerOrMotoPartOrMenuElem* var2, class_10* var3, int var4, int var5) +void GamePhysics::applySpringConstraint(MotoComponent* anchor, PhysicsElemOrMenuItem* spring, MotoComponent* target, int bufferIndex, int stiffnessF16) { - TimerOrMotoPartOrMenuElem* var6 = var1->motoComponents[var4].get(); - TimerOrMotoPartOrMenuElem* var7 = var3->motoComponents[var4].get(); - int var8 = var6->xF16 - var7->xF16; - int var9 = var6->yF16 - var7->yF16; - int var10; - if (((var10 = getSmthLikeMaxAbs(var8, var9)) < 0 ? -var10 : var10) >= 3) { - var8 = (int)(((int64_t)var8 << 32) / (int64_t)var10 >> 16); - var9 = (int)(((int64_t)var9 << 32) / (int64_t)var10 >> 16); - int var11 = var10 - var2->yF16; - int var12 = (int)((int64_t)var8 * (int64_t)((int)((int64_t)var11 * (int64_t)var2->xF16 >> 16)) >> 16); - int var13 = (int)((int64_t)var9 * (int64_t)((int)((int64_t)var11 * (int64_t)var2->xF16 >> 16)) >> 16); - int var14 = var6->field_382 - var7->field_382; - int var15 = var6->field_383 - var7->field_383; - int var16 = (int)((int64_t)((int)((int64_t)var8 * (int64_t)var14 >> 16) + (int)((int64_t)var9 * (int64_t)var15 >> 16)) * (int64_t)var2->angleF16 >> 16); - var12 += (int)((int64_t)var8 * (int64_t)var16 >> 16); - var13 += (int)((int64_t)var9 * (int64_t)var16 >> 16); - var12 = (int)((int64_t)var12 * (int64_t)var5 >> 16); - var13 = (int)((int64_t)var13 * (int64_t)var5 >> 16); - var6->field_385 -= var12; - var6->field_386 -= var13; - var7->field_385 += var12; - var7->field_386 += var13; + PhysicsElemOrMenuItem* anchorElem = anchor->stateBuffers[bufferIndex].get(); + PhysicsElemOrMenuItem* targetElem = target->stateBuffers[bufferIndex].get(); + int dx = anchorElem->xF16 - targetElem->xF16; + int dy = anchorElem->yF16 - targetElem->yF16; + int dist; + if (((dist = fastVectorLengthF16(dx, dy)) < 0 ? -dist : dist) >= 3) { + dx = (int)(((int64_t)dx << 32) / (int64_t)dist >> 16); + dy = (int)(((int64_t)dy << 32) / (int64_t)dist >> 16); + int springExtension = dist - spring->yF16; + int forceX = (int)((int64_t)dx * (int64_t)((int)((int64_t)springExtension * (int64_t)spring->xF16 >> 16)) >> 16); + int forceY = (int)((int64_t)dy * (int64_t)((int)((int64_t)springExtension * (int64_t)spring->xF16 >> 16)) >> 16); + int relVelX = anchorElem->velocityXF16 - targetElem->velocityXF16; + int relVelY = anchorElem->velocityYF16 - targetElem->velocityYF16; + int damping = (int)((int64_t)((int)((int64_t)dx * (int64_t)relVelX >> 16) + (int)((int64_t)dy * (int64_t)relVelY >> 16)) * (int64_t)spring->angleF16 >> 16); + forceX += (int)((int64_t)dx * (int64_t)damping >> 16); + forceY += (int)((int64_t)dy * (int64_t)damping >> 16); + forceX = (int)((int64_t)forceX * (int64_t)stiffnessF16 >> 16); + forceY = (int)((int64_t)forceY * (int64_t)stiffnessF16 >> 16); + anchorElem->forceAccumXF16 -= forceX; + anchorElem->forceAccumYF16 -= forceY; + targetElem->forceAccumXF16 += forceX; + targetElem->forceAccumYF16 += forceY; } } -void GamePhysics::method_43(int var1, int var2, int var3) +void GamePhysics::integratePosition(int fromBuffer, int toBuffer, int dtF16) { - for (int var7 = 0; var7 < 6; ++var7) { - TimerOrMotoPartOrMenuElem* var4 = field_29[var7]->motoComponents[var1].get(); - TimerOrMotoPartOrMenuElem* var5; - (var5 = field_29[var7]->motoComponents[var2].get())->xF16 = (int)((int64_t)var4->field_382 * (int64_t)var3 >> 16); - var5->yF16 = (int)((int64_t)var4->field_383 * (int64_t)var3 >> 16); - int var6 = (int)((int64_t)var3 * (int64_t)field_29[var7]->field_259 >> 16); - var5->field_382 = (int)((int64_t)var4->field_385 * (int64_t)var6 >> 16); - var5->field_383 = (int)((int64_t)var4->field_386 * (int64_t)var6 >> 16); + for (int i = 0; i < 6; ++i) { + PhysicsElemOrMenuItem* fromElem = motoComponents[i]->stateBuffers[fromBuffer].get(); + PhysicsElemOrMenuItem* toElem; + (toElem = motoComponents[i]->stateBuffers[toBuffer].get())->xF16 = (int)((int64_t)fromElem->velocityXF16 * (int64_t)dtF16 >> 16); + toElem->yF16 = (int)((int64_t)fromElem->velocityYF16 * (int64_t)dtF16 >> 16); + int invMassDt = (int)((int64_t)dtF16 * (int64_t)motoComponents[i]->inverseMassF16 >> 16); + toElem->velocityXF16 = (int)((int64_t)fromElem->forceAccumXF16 * (int64_t)invMassDt >> 16); + toElem->velocityYF16 = (int)((int64_t)fromElem->forceAccumYF16 * (int64_t)invMassDt >> 16); } } -void GamePhysics::method_44(int var1, int var2, int var3) +void GamePhysics::interpolatePosition(int toBuffer, int buf1, int buf2) { - for (int var7 = 0; var7 < 6; ++var7) { - TimerOrMotoPartOrMenuElem* var4 = field_29[var7]->motoComponents[var1].get(); - TimerOrMotoPartOrMenuElem* var5 = field_29[var7]->motoComponents[var2].get(); - TimerOrMotoPartOrMenuElem* var6 = field_29[var7]->motoComponents[var3].get(); - var4->xF16 = var5->xF16 + (var6->xF16 >> 1); - var4->yF16 = var5->yF16 + (var6->yF16 >> 1); - var4->field_382 = var5->field_382 + (var6->field_382 >> 1); - var4->field_383 = var5->field_383 + (var6->field_383 >> 1); + for (int i = 0; i < 6; ++i) { + PhysicsElemOrMenuItem* toElem = motoComponents[i]->stateBuffers[toBuffer].get(); + PhysicsElemOrMenuItem* elem1 = motoComponents[i]->stateBuffers[buf1].get(); + PhysicsElemOrMenuItem* elem2 = motoComponents[i]->stateBuffers[buf2].get(); + toElem->xF16 = elem1->xF16 + (elem2->xF16 >> 1); + toElem->yF16 = elem1->yF16 + (elem2->yF16 >> 1); + toElem->velocityXF16 = elem1->velocityXF16 + (elem2->velocityXF16 >> 1); + toElem->velocityYF16 = elem1->velocityYF16 + (elem2->velocityYF16 >> 1); } } -void GamePhysics::method_45(int var1) +void GamePhysics::performPhysicsSubstep(int dtF16) { - method_40(index01); - method_43(index01, 2, var1); - method_44(4, index01, 2); - method_40(4); - method_43(4, 3, var1 >> 1); - method_44(4, index01, 3); - method_44(index10, index01, 2); - method_44(index10, index10, 3); - - for (int var4 = 1; var4 <= 2; ++var4) { - TimerOrMotoPartOrMenuElem* var2 = field_29[var4]->motoComponents[index01].get(); - TimerOrMotoPartOrMenuElem* var3; - (var3 = field_29[var4]->motoComponents[index10].get())->angleF16 = var2->angleF16 + (int)((int64_t)var1 * (int64_t)var2->field_384 >> 16); - var3->field_384 = var2->field_384 + (int)((int64_t)var1 * (int64_t)((int)((int64_t)field_29[var4]->field_260 * (int64_t)var2->field_387 >> 16)) >> 16); + applyForces(readBufferIndex); + integratePosition(readBufferIndex, 2, dtF16); + interpolatePosition(4, readBufferIndex, 2); + applyForces(4); + integratePosition(4, 3, dtF16 >> 1); + interpolatePosition(4, readBufferIndex, 3); + interpolatePosition(writeBufferIndex, readBufferIndex, 2); + interpolatePosition(writeBufferIndex, writeBufferIndex, 3); + + for (int i = 1; i <= 2; ++i) { + PhysicsElemOrMenuItem* fromElem = motoComponents[i]->stateBuffers[readBufferIndex].get(); + PhysicsElemOrMenuItem* toElem; + (toElem = motoComponents[i]->stateBuffers[writeBufferIndex].get())->angleF16 = fromElem->angleF16 + (int)((int64_t)dtF16 * (int64_t)fromElem->angularVelocityF16 >> 16); + toElem->angularVelocityF16 = fromElem->angularVelocityF16 + (int)((int64_t)dtF16 * (int64_t)((int)((int64_t)motoComponents[i]->leanInfluenceF16 * (int64_t)fromElem->torqueF16 >> 16)) >> 16); } } -int GamePhysics::method_46(int var1) +int GamePhysics::checkTrackCollisions(int bufferIndex) { - int8_t var2 = 2; - int var4 = std::max({ field_29[1]->motoComponents[var1]->xF16, field_29[2]->motoComponents[var1]->xF16, field_29[5]->motoComponents[var1]->xF16 }); - int var5 = std::min({ field_29[1]->motoComponents[var1]->xF16, field_29[2]->motoComponents[var1]->xF16, field_29[5]->motoComponents[var1]->xF16 }); - levelLoader->method_100(var5 - const175_1_half[0], var4 + const175_1_half[0], field_29[5]->motoComponents[var1]->yF16); - int var6 = field_29[1]->motoComponents[var1]->xF16 - field_29[2]->motoComponents[var1]->xF16; - int var7 = field_29[1]->motoComponents[var1]->yF16 - field_29[2]->motoComponents[var1]->yF16; - int var8 = getSmthLikeMaxAbs(var6, var7); - var6 = (int)(((int64_t)var6 << 32) / (int64_t)var8 >> 16); - int var9 = -((int)(((int64_t)var7 << 32) / (int64_t)var8 >> 16)); - int var10 = var6; - - for (int var11 = 0; var11 < 6; ++var11) { - if (var11 != 4 && var11 != 3) { - TimerOrMotoPartOrMenuElem* var3 = field_29[var11]->motoComponents[var1].get(); - if (var11 == 0) { - var3->xF16 += (int)((int64_t)var9 * 65536L >> 16); - var3->yF16 += (int)((int64_t)var10 * 65536L >> 16); + // 2=no collision, 1=collision with response, 0=collision without response + int8_t collisionResult = 2; + int maxXF16 = std::max({ motoComponents[1]->stateBuffers[bufferIndex]->xF16, + motoComponents[2]->stateBuffers[bufferIndex]->xF16, motoComponents[5]->stateBuffers[bufferIndex]->xF16 }); + int minXF16 = std::min({ motoComponents[1]->stateBuffers[bufferIndex]->xF16, + motoComponents[2]->stateBuffers[bufferIndex]->xF16, motoComponents[5]->stateBuffers[bufferIndex]->xF16 }); + levelLoader->updateVisibleSegmentRange(minXF16 - wheelRadiusValuesF16[0], maxXF16 + wheelRadiusValuesF16[0], motoComponents[5]->stateBuffers[bufferIndex]->yF16); + + // Calculate normalized bike direction vector + int dxF16 = motoComponents[1]->stateBuffers[bufferIndex]->xF16 - motoComponents[2]->stateBuffers[bufferIndex]->xF16; + int dyF16 = motoComponents[1]->stateBuffers[bufferIndex]->yF16 - motoComponents[2]->stateBuffers[bufferIndex]->yF16; + int dist = fastVectorLengthF16(dxF16, dyF16); + dxF16 = (int)(((int64_t)dxF16 << 32) / (int64_t)dist >> 16); + int negDyF16 = -((int)(((int64_t)dyF16 << 32) / (int64_t)dist >> 16)); + int normalXF16 = dxF16; + + for (int compIdx = 0; compIdx < 6; ++compIdx) { + // Skip handlebar and seat for collision + if (compIdx != 4 && compIdx != 3) { + PhysicsElemOrMenuItem* elem = motoComponents[compIdx]->stateBuffers[bufferIndex].get(); + + // Offset chassis position for accurate collision + if (compIdx == 0) { + elem->xF16 += (int)((int64_t)negDyF16 * 65536L >> 16); + elem->yF16 += (int)((int64_t)normalXF16 * 65536L >> 16); } - int var12 = levelLoader->method_101(var3, field_29[var11]->field_258); - if (var11 == 0) { - var3->xF16 -= (int)((int64_t)var9 * 65536L >> 16); - var3->yF16 -= (int)((int64_t)var10 * 65536L >> 16); + int isCollision = levelLoader->checkSegmentCollisions(elem, motoComponents[compIdx]->radiusIndex); + + // Restore chassis position + if (compIdx == 0) { + elem->xF16 -= (int)((int64_t)negDyF16 * 65536L >> 16); + elem->yF16 -= (int)((int64_t)normalXF16 * 65536L >> 16); } - field_33 = levelLoader->field_137; - field_34 = levelLoader->field_138; - if (var11 == 5 && var12 != 2) { - field_36 = true; + // Store collision normal from LevelLoader + collisionNormalXF16 = levelLoader->lastCollisionNormalXF16; + collisionNormalYF16 = levelLoader->lastCollisionNormalYF16; + + // Check for player head crash + if (compIdx == 5 && isCollision != 2) { + isPlayerHeadCrashed = true; } - if (var11 == 1 && var12 != 2) { - field_69 = true; + // Front wheel contact latch (never resets) + if (compIdx == 1 && isCollision != 2) { + frontWheelContactLatch = true; } - if (var12 == 1) { - field_28 = var11; - var2 = 1; - } else if (var12 == 0) { - field_28 = var11; - var2 = 0; + if (isCollision == 1) { + lastCollidedComponentIndex = compIdx; + collisionResult = 1; + } else if (isCollision == 0) { + lastCollidedComponentIndex = compIdx; + collisionResult = 0; break; } } } - return var2; + return collisionResult; } -void GamePhysics::method_47(int var1) +void GamePhysics::applyCollisionResponse(int bufferIndex) { - class_10* var2; - TimerOrMotoPartOrMenuElem* var3; - TimerOrMotoPartOrMenuElem* var10000 = var3 = (var2 = field_29[field_28].get())->motoComponents[var1].get(); - var10000->xF16 += (int)((int64_t)field_33 * 3276L >> 16); - var3->yF16 += (int)((int64_t)field_34 * 3276L >> 16); - int var4; - int var5; - int var6; - int var7; - int var8; - if (isInputBreak && (field_28 == 2 || field_28 == 1) && var3->field_384 < 6553) { - var4 = field_9 - motoParam7; - var5 = 13107; - var6 = 39321; - var7 = 26214 - motoParam7; - var8 = 26214 - motoParam7; + MotoComponent* collidedComp = motoComponents[lastCollidedComponentIndex].get(); + PhysicsElemOrMenuItem* elem = collidedComp->stateBuffers[bufferIndex].get(); + + // Push element out of collision along normal + elem->xF16 += (int)((int64_t)collisionNormalXF16 * 3276L >> 16); + elem->yF16 += (int)((int64_t)collisionNormalYF16 * 3276L >> 16); + + int frictionNormalF16; + int frictionTangentialF16; + int restitutionLocalF16; + int leanForceXF16; + int leanForceYF16; + + // Apply brake friction modifier when braking on wheels + if (isInputBreak && (lastCollidedComponentIndex == 2 || lastCollidedComponentIndex == 1) && elem->angularVelocityF16 < 6553) { + frictionNormalF16 = normalFrictionF16 - brakeFrictionModifierF16; + frictionTangentialF16 = 13107; + restitutionLocalF16 = 39321; + leanForceXF16 = 26214 - brakeFrictionModifierF16; + leanForceYF16 = 26214 - brakeFrictionModifierF16; } else { - var4 = field_9; - var5 = field_10; - var6 = field_11; - var7 = motoParam1; - var8 = motoParam2; + frictionNormalF16 = normalFrictionF16; + frictionTangentialF16 = tangentialFrictionF16; + restitutionLocalF16 = restitutionF16; + leanForceXF16 = leanForceCoefficientXF16; + leanForceYF16 = leanForceCoefficientYF16; } - int var9 = getSmthLikeMaxAbs(field_33, field_34); - field_33 = (int)(((int64_t)field_33 << 32) / (int64_t)var9 >> 16); - field_34 = (int)(((int64_t)field_34 << 32) / (int64_t)var9 >> 16); - int var10 = var3->field_382; - int var11 = var3->field_383; - int var12 = -((int)((int64_t)var10 * (int64_t)field_33 >> 16) + (int)((int64_t)var11 * (int64_t)field_34 >> 16)); - int var13 = -((int)((int64_t)var10 * (int64_t)(-field_34) >> 16) + (int)((int64_t)var11 * (int64_t)field_33 >> 16)); - int var14 = (int)((int64_t)var4 * (int64_t)var3->field_384 >> 16) - (int)((int64_t)var5 * (int64_t)((int)(((int64_t)var13 << 32) / (int64_t)var2->field_257 >> 16)) >> 16); - int var15 = (int)((int64_t)var7 * (int64_t)var13 >> 16) - (int)((int64_t)var6 * (int64_t)((int)((int64_t)var3->field_384 * (int64_t)var2->field_257 >> 16)) >> 16); - int var16 = -((int)((int64_t)var8 * (int64_t)var12 >> 16)); - int var17 = (int)((int64_t)(-var15) * (int64_t)(-field_34) >> 16); - int var18 = (int)((int64_t)(-var15) * (int64_t)field_33 >> 16); - int var19 = (int)((int64_t)(-var16) * (int64_t)field_33 >> 16); - int var20 = (int)((int64_t)(-var16) * (int64_t)field_34 >> 16); - var3->field_384 = var14; - var3->field_382 = var17 + var19; - var3->field_383 = var18 + var20; + // Normalize collision normal + int normalMag = fastVectorLengthF16(collisionNormalXF16, collisionNormalYF16); + collisionNormalXF16 = (int)(((int64_t)collisionNormalXF16 << 32) / (int64_t)normalMag >> 16); + collisionNormalYF16 = (int)(((int64_t)collisionNormalYF16 << 32) / (int64_t)normalMag >> 16); + + int velXF16 = elem->velocityXF16; + int velYF16 = elem->velocityYF16; + + // Calculate velocity in collision frame (normal and tangential components) + int velNormal = -((int)((int64_t)velXF16 * (int64_t)collisionNormalXF16 >> 16) + (int)((int64_t)velYF16 * (int64_t)collisionNormalYF16 >> 16)); + int velTangent = -((int)((int64_t)velXF16 * (int64_t)(-collisionNormalYF16) >> 16) + (int)((int64_t)velYF16 * (int64_t)collisionNormalXF16 >> 16)); + + // Apply friction to angular velocity and tangential velocity + int newAngularVel = (int)((int64_t)frictionNormalF16 * (int64_t)elem->angularVelocityF16 >> 16) - (int)((int64_t)frictionTangentialF16 * (int64_t)((int)(((int64_t)velTangent << 32) / (int64_t)collidedComp->radiusF16 >> 16)) >> 16); + int newVelTangent = (int)((int64_t)leanForceXF16 * (int64_t)velTangent >> 16) - (int)((int64_t)restitutionLocalF16 * (int64_t)((int)((int64_t)elem->angularVelocityF16 * (int64_t)collidedComp->radiusF16 >> 16)) >> 16); + int newVelNormal = -((int)((int64_t)leanForceYF16 * (int64_t)velNormal >> 16)); + + // Transform back to world coordinates + int newVelXF16 = (int)((int64_t)(-newVelTangent) * (int64_t)(-collisionNormalYF16) >> 16); + int newVelYF16 = (int)((int64_t)(-newVelTangent) * (int64_t)collisionNormalXF16 >> 16); + int normalVelXF16 = (int)((int64_t)(-newVelNormal) * (int64_t)collisionNormalXF16 >> 16); + int normalVelYF16 = (int)((int64_t)(-newVelNormal) * (int64_t)collisionNormalYF16 >> 16); + + elem->angularVelocityF16 = newAngularVel; + elem->velocityXF16 = newVelXF16 + normalVelXF16; + elem->velocityYF16 = newVelYF16 + normalVelYF16; } void GamePhysics::setEnableLookAhead(bool value) @@ -831,85 +883,86 @@ void GamePhysics::setEnableLookAhead(bool value) void GamePhysics::setMinimalScreenWH(int minWH) { - field_73 = (int)(((int64_t)((int)(655360L * (int64_t)(minWH << 16) >> 16)) << 32) / 8388608L >> 16); + // Set camera look-ahead limit based on minimum screen dimension + cameraLookAheadLimit = (int)(((int64_t)((int)(655360L * (int64_t)(minWH << 16) >> 16)) << 32) / 8388608L >> 16); } int GamePhysics::getCamPosX() { if (isEnableLookAhead) { - camShiftX = (int)(((int64_t)motoComponents[0]->field_382 << 32) / 1572864L >> 16) + (int)((int64_t)camShiftX * 57344L >> 16); + camShiftX = (int)(((int64_t)renderCache[0]->velocityXF16 << 32) / 1572864L >> 16) + (int)((int64_t)camShiftX * 57344L >> 16); } else { camShiftX = 0; } - // camShiftX = clamp(camShiftX, -field_73, field_73); - camShiftX = camShiftX < field_73 ? camShiftX : field_73; - camShiftX = camShiftX < -field_73 ? -field_73 : camShiftX; - return (motoComponents[0]->xF16 + camShiftX) << 2 >> 16; + camShiftX = camShiftX < cameraLookAheadLimit ? camShiftX : cameraLookAheadLimit; + camShiftX = camShiftX < -cameraLookAheadLimit ? -cameraLookAheadLimit : camShiftX; + return (renderCache[0]->xF16 + camShiftX) << 2 >> 16; } int GamePhysics::getCamPosY() { if (isEnableLookAhead) { - camShiftY = (int)(((int64_t)motoComponents[0]->field_383 << 32) / 1572864L >> 16) + (int)((int64_t)camShiftY * 57344L >> 16); + camShiftY = (int)(((int64_t)renderCache[0]->velocityYF16 << 32) / 1572864L >> 16) + (int)((int64_t)camShiftY * 57344L >> 16); } else { camShiftY = 0; } - camShiftY = camShiftY < field_73 ? camShiftY : field_73; - camShiftY = camShiftY < -field_73 ? -field_73 : camShiftY; - return (motoComponents[0]->yF16 + camShiftY) << 2 >> 16; + camShiftY = camShiftY < cameraLookAheadLimit ? camShiftY : cameraLookAheadLimit; + camShiftY = camShiftY < -cameraLookAheadLimit ? -cameraLookAheadLimit : camShiftY; + return (renderCache[0]->yF16 + camShiftY) << 2 >> 16; } -int GamePhysics::method_52() +int GamePhysics::getRawXDistance() { - int var1 = motoComponents[1]->xF16 < motoComponents[2]->xF16 ? motoComponents[2]->xF16 : motoComponents[1]->xF16; - return field_35 ? levelLoader->method_95(motoComponents[0]->xF16) : levelLoader->method_95(var1); + // Return max X position of wheels, or chassis X if bike is destroyed + int maxWheelXF16 = renderCache[1]->xF16 < renderCache[2]->xF16 ? renderCache[2]->xF16 : renderCache[1]->xF16; + return isBikeDestroyed ? levelLoader->getTrackProgressRatio(renderCache[0]->xF16) : levelLoader->getTrackProgressRatio(maxWheelXF16); } -void GamePhysics::method_53() +void GamePhysics::captureRenderSnapshot() { - // synchronized (field_29) { - for (int var2 = 0; var2 < 6; ++var2) { - field_29[var2]->motoComponents[5]->xF16 = field_29[var2]->motoComponents[index01]->xF16; - field_29[var2]->motoComponents[5]->yF16 = field_29[var2]->motoComponents[index01]->yF16; - field_29[var2]->motoComponents[5]->angleF16 = field_29[var2]->motoComponents[index01]->angleF16; + // Synchronize buffer 5 (render buffer) with current physics buffer + for (int i = 0; i < 6; ++i) { + motoComponents[i]->stateBuffers[5]->xF16 = motoComponents[i]->stateBuffers[readBufferIndex]->xF16; + motoComponents[i]->stateBuffers[5]->yF16 = motoComponents[i]->stateBuffers[readBufferIndex]->yF16; + motoComponents[i]->stateBuffers[5]->angleF16 = motoComponents[i]->stateBuffers[readBufferIndex]->angleF16; } - field_29[0]->motoComponents[5]->field_382 = field_29[0]->motoComponents[index01]->field_382; - field_29[0]->motoComponents[5]->field_383 = field_29[0]->motoComponents[index01]->field_383; - field_29[2]->motoComponents[5]->field_384 = field_29[2]->motoComponents[index01]->field_384; - // } + // Copy chassis velocity and back wheel angular velocity to render buffer + motoComponents[0]->stateBuffers[5]->velocityXF16 = motoComponents[0]->stateBuffers[readBufferIndex]->velocityXF16; + motoComponents[0]->stateBuffers[5]->velocityYF16 = motoComponents[0]->stateBuffers[readBufferIndex]->velocityYF16; + motoComponents[2]->stateBuffers[5]->angularVelocityF16 = motoComponents[2]->stateBuffers[readBufferIndex]->angularVelocityF16; } -void GamePhysics::setMotoComponents() +void GamePhysics::prepareRenderCache() { - // synchronized (field_29) { + // Copy render buffer (5) to stateBuffers for rendering for (int i = 0; i < 6; ++i) { - motoComponents[i]->xF16 = field_29[i]->motoComponents[5]->xF16; - motoComponents[i]->yF16 = field_29[i]->motoComponents[5]->yF16; - motoComponents[i]->angleF16 = field_29[i]->motoComponents[5]->angleF16; + renderCache[i]->xF16 = motoComponents[i]->stateBuffers[5]->xF16; + renderCache[i]->yF16 = motoComponents[i]->stateBuffers[5]->yF16; + renderCache[i]->angleF16 = motoComponents[i]->stateBuffers[5]->angleF16; } - motoComponents[0]->field_382 = field_29[0]->motoComponents[5]->field_382; - motoComponents[0]->field_383 = field_29[0]->motoComponents[5]->field_383; - motoComponents[2]->field_384 = field_29[2]->motoComponents[5]->field_384; - // } + // Copy chassis velocity and back wheel angular velocity + renderCache[0]->velocityXF16 = motoComponents[0]->stateBuffers[5]->velocityXF16; + renderCache[0]->velocityYF16 = motoComponents[0]->stateBuffers[5]->velocityYF16; + renderCache[2]->angularVelocityF16 = motoComponents[2]->stateBuffers[5]->angularVelocityF16; } -void GamePhysics::renderEngine(GameCanvas* gameCanvas, int var2, int var3) +void GamePhysics::renderEngine(GameCanvas* gameCanvas, int upXF16, int upYF16) { - int engineAngle4F16 = MathF16::atan2F16(motoComponents[0]->xF16 - motoComponents[3]->xF16, motoComponents[0]->yF16 - motoComponents[3]->yF16); - int fenderAngle4F16 = MathF16::atan2F16(motoComponents[0]->xF16 - motoComponents[4]->xF16, motoComponents[0]->yF16 - motoComponents[4]->yF16); - int engineXF16 = (motoComponents[0]->xF16 >> 1) + (motoComponents[3]->xF16 >> 1); - int engineYF16 = (motoComponents[0]->yF16 >> 1) + (motoComponents[3]->yF16 >> 1); - int fenderXF16 = (motoComponents[0]->xF16 >> 1) + (motoComponents[4]->xF16 >> 1); - int fenderYF16 = (motoComponents[0]->yF16 >> 1) + (motoComponents[4]->yF16 >> 1); - int var10 = -var3; - engineXF16 += (int)((int64_t)var10 * 65536L >> 16) - (int)((int64_t)var2 * 32768L >> 16); - engineYF16 += (int)((int64_t)var2 * 65536L >> 16) - (int)((int64_t)var3 * 32768L >> 16); - fenderXF16 += (int)((int64_t)var10 * 65536L >> 16) - (int)((int64_t)var2 * 117964L >> 16); - fenderYF16 += (int)((int64_t)var2 * 65536L >> 16) - (int)((int64_t)var3 * 131072L >> 16); + int engineAngle4F16 = MathF16::atan2F16(renderCache[0]->xF16 - renderCache[3]->xF16, renderCache[0]->yF16 - renderCache[3]->yF16); + int fenderAngle4F16 = MathF16::atan2F16(renderCache[0]->xF16 - renderCache[4]->xF16, renderCache[0]->yF16 - renderCache[4]->yF16); + int engineXF16 = (renderCache[0]->xF16 >> 1) + (renderCache[3]->xF16 >> 1); + int engineYF16 = (renderCache[0]->yF16 >> 1) + (renderCache[3]->yF16 >> 1); + int fenderXF16 = (renderCache[0]->xF16 >> 1) + (renderCache[4]->xF16 >> 1); + int fenderYF16 = (renderCache[0]->yF16 >> 1) + (renderCache[4]->yF16 >> 1); + int negYF16 = -upYF16; + engineXF16 += (int)((int64_t)negYF16 * 65536L >> 16) - (int)((int64_t)upXF16 * 32768L >> 16); + engineYF16 += (int)((int64_t)upXF16 * 65536L >> 16) - (int)((int64_t)upYF16 * 32768L >> 16); + fenderXF16 += (int)((int64_t)negYF16 * 65536L >> 16) - (int)((int64_t)upXF16 * 117964L >> 16); + fenderYF16 += (int)((int64_t)upXF16 * 65536L >> 16) - (int)((int64_t)upYF16 * 131072L >> 16); gameCanvas->renderFender(fenderXF16 << 2 >> 16, fenderYF16 << 2 >> 16, fenderAngle4F16); gameCanvas->renderEngine(engineXF16 << 2 >> 16, engineYF16 << 2 >> 16, engineAngle4F16); } @@ -917,14 +970,14 @@ void GamePhysics::renderEngine(GameCanvas* gameCanvas, int var2, int var3) void GamePhysics::renderMotoFork(GameCanvas* canvas) { canvas->setColor(128, 128, 128); - canvas->drawLineF16(motoComponents[3]->xF16, motoComponents[3]->yF16, motoComponents[1]->xF16, motoComponents[1]->yF16); + canvas->drawLineF16(renderCache[3]->xF16, renderCache[3]->yF16, renderCache[1]->xF16, renderCache[1]->yF16); } void GamePhysics::renderWheelTires(GameCanvas* canvas) { int8_t backWheelIsThin = 1; int8_t forwardWheelIsThin = 1; - switch (curentMotoLeague) { + switch (currentLeague) { case 1: backWheelIsThin = 0; break; @@ -935,283 +988,345 @@ void GamePhysics::renderWheelTires(GameCanvas* canvas) } // back wheel - canvas->drawWheelTires(motoComponents[2]->xF16 << 2 >> 16, motoComponents[2]->yF16 << 2 >> 16, backWheelIsThin); + canvas->drawWheelTires(renderCache[2]->xF16 << 2 >> 16, renderCache[2]->yF16 << 2 >> 16, backWheelIsThin); // forward wheel - canvas->drawWheelTires(motoComponents[1]->xF16 << 2 >> 16, motoComponents[1]->yF16 << 2 >> 16, forwardWheelIsThin); + canvas->drawWheelTires(renderCache[1]->xF16 << 2 >> 16, renderCache[1]->yF16 << 2 >> 16, forwardWheelIsThin); } void GamePhysics::renderWheelSpokes(GameCanvas* gameCanvas) { - int var2; - int xxxF16 = (int)((int64_t)(var2 = field_29[1]->field_257) * 58982L >> 16); - int yyyF16 = (int)((int64_t)var2 * 45875L >> 16); + int wheelRadiusF16; + int xxxF16 = (int)((int64_t)(wheelRadiusF16 = motoComponents[1]->radiusF16) * 58982L >> 16); + int yyyF16 = (int)((int64_t)wheelRadiusF16 * 45875L >> 16); gameCanvas->setColor(0, 0, 0); if (Micro::isInGameMenu) { - gameCanvas->drawCircle(motoComponents[1]->xF16 << 2 >> 16, motoComponents[1]->yF16 << 2 >> 16, (var2 + var2) << 2 >> 16); - gameCanvas->drawCircle(motoComponents[1]->xF16 << 2 >> 16, motoComponents[1]->yF16 << 2 >> 16, (xxxF16 + xxxF16) << 2 >> 16); - gameCanvas->drawCircle(motoComponents[2]->xF16 << 2 >> 16, motoComponents[2]->yF16 << 2 >> 16, (var2 + var2) << 2 >> 16); - gameCanvas->drawCircle(motoComponents[2]->xF16 << 2 >> 16, motoComponents[2]->yF16 << 2 >> 16, (yyyF16 + yyyF16) << 2 >> 16); + gameCanvas->drawCircle(renderCache[1]->xF16 << 2 >> 16, renderCache[1]->yF16 << 2 >> 16, (wheelRadiusF16 + wheelRadiusF16) << 2 >> 16); + gameCanvas->drawCircle(renderCache[1]->xF16 << 2 >> 16, renderCache[1]->yF16 << 2 >> 16, (xxxF16 + xxxF16) << 2 >> 16); + gameCanvas->drawCircle(renderCache[2]->xF16 << 2 >> 16, renderCache[2]->yF16 << 2 >> 16, (wheelRadiusF16 + wheelRadiusF16) << 2 >> 16); + gameCanvas->drawCircle(renderCache[2]->xF16 << 2 >> 16, renderCache[2]->yF16 << 2 >> 16, (yyyF16 + yyyF16) << 2 >> 16); } - int8_t var6 = 0; + int8_t radialOffsetYF16 = 0; // Radial Y offset for spoke calculation (0 = from center) int angle; - int cosF16 = MathF16::cosF16(angle = motoComponents[1]->angleF16); + int cosF16 = MathF16::cosF16(angle = renderCache[1]->angleF16); int sinF16 = MathF16::sinF16(angle); - int dxF16 = (int)((int64_t)cosF16 * (int64_t)xxxF16 >> 16) + (int)((int64_t)(-sinF16) * (int64_t)var6 >> 16); - int dyF16 = (int)((int64_t)sinF16 * (int64_t)xxxF16 >> 16) + (int)((int64_t)cosF16 * (int64_t)var6 >> 16); + int dxF16 = (int)((int64_t)cosF16 * (int64_t)xxxF16 >> 16) + (int)((int64_t)(-sinF16) * (int64_t)radialOffsetYF16 >> 16); + int dyF16 = (int)((int64_t)sinF16 * (int64_t)xxxF16 >> 16) + (int)((int64_t)cosF16 * (int64_t)radialOffsetYF16 >> 16); angle = 82354; cosF16 = MathF16::cosF16(82354); sinF16 = MathF16::sinF16(angle); - int var10; + int prevDxF16; // Previous DX for rotation matrix calculation int i; for (i = 0; i < 5; ++i) { // forward wheel spokes - gameCanvas->drawLineF16(motoComponents[1]->xF16, motoComponents[1]->yF16, motoComponents[1]->xF16 + dxF16, motoComponents[1]->yF16 + dyF16); - var10 = dxF16; + gameCanvas->drawLineF16(renderCache[1]->xF16, renderCache[1]->yF16, renderCache[1]->xF16 + dxF16, renderCache[1]->yF16 + dyF16); + prevDxF16 = dxF16; dxF16 = (int)((int64_t)cosF16 * (int64_t)dxF16 >> 16) + (int)((int64_t)(-sinF16) * (int64_t)dyF16 >> 16); - dyF16 = (int)((int64_t)sinF16 * (int64_t)var10 >> 16) + (int)((int64_t)cosF16 * (int64_t)dyF16 >> 16); + dyF16 = (int)((int64_t)sinF16 * (int64_t)prevDxF16 >> 16) + (int)((int64_t)cosF16 * (int64_t)dyF16 >> 16); } - var6 = 0; - cosF16 = MathF16::cosF16(angle = motoComponents[2]->angleF16); + radialOffsetYF16 = 0; + cosF16 = MathF16::cosF16(angle = renderCache[2]->angleF16); sinF16 = MathF16::sinF16(angle); - dxF16 = (int)((int64_t)cosF16 * (int64_t)xxxF16 >> 16) + (int)((int64_t)(-sinF16) * (int64_t)var6 >> 16); - dyF16 = (int)((int64_t)sinF16 * (int64_t)xxxF16 >> 16) + (int)((int64_t)cosF16 * (int64_t)var6 >> 16); + dxF16 = (int)((int64_t)cosF16 * (int64_t)xxxF16 >> 16) + (int)((int64_t)(-sinF16) * (int64_t)radialOffsetYF16 >> 16); + dyF16 = (int)((int64_t)sinF16 * (int64_t)xxxF16 >> 16) + (int)((int64_t)cosF16 * (int64_t)radialOffsetYF16 >> 16); angle = 82354; cosF16 = MathF16::cosF16(82354); sinF16 = MathF16::sinF16(angle); for (i = 0; i < 5; ++i) { // back wheel spokes - gameCanvas->drawLineF16(motoComponents[2]->xF16, motoComponents[2]->yF16, motoComponents[2]->xF16 + dxF16, motoComponents[2]->yF16 + dyF16); - var10 = dxF16; + gameCanvas->drawLineF16(renderCache[2]->xF16, renderCache[2]->yF16, renderCache[2]->xF16 + dxF16, renderCache[2]->yF16 + dyF16); + prevDxF16 = dxF16; dxF16 = (int)((int64_t)cosF16 * (int64_t)dxF16 >> 16) + (int)((int64_t)(-sinF16) * (int64_t)dyF16 >> 16); - dyF16 = (int)((int64_t)sinF16 * (int64_t)var10 >> 16) + (int)((int64_t)cosF16 * (int64_t)dyF16 >> 16); + dyF16 = (int)((int64_t)sinF16 * (int64_t)prevDxF16 >> 16) + (int)((int64_t)cosF16 * (int64_t)dyF16 >> 16); } - if (curentMotoLeague > 0) { + if (currentLeague > 0) { gameCanvas->setColor(255, 0, 0); - if (curentMotoLeague > 2) { + if (currentLeague > 2) { gameCanvas->setColor(100, 100, 255); } - gameCanvas->drawCircle(motoComponents[2]->xF16 << 2 >> 16, motoComponents[2]->yF16 << 2 >> 16, 4); - gameCanvas->drawCircle(motoComponents[1]->xF16 << 2 >> 16, motoComponents[1]->yF16 << 2 >> 16, 4); + gameCanvas->drawCircle(renderCache[2]->xF16 << 2 >> 16, renderCache[2]->yF16 << 2 >> 16, 4); + gameCanvas->drawCircle(renderCache[1]->xF16 << 2 >> 16, renderCache[1]->yF16 << 2 >> 16, 4); } } -void GamePhysics::renderSmth(GameCanvas* gameCanvas, int var2, int var3, int var4, int var5) +void GamePhysics::renderRider(GameCanvas* gameCanvas, int upXF16, int upYF16, int fwdXF16, int fwdYF16) { - int8_t var6 = 0; - int var7 = 65536; - int var8 = motoComponents[0]->xF16; - int var9 = motoComponents[0]->yF16; - int x6F16 = 0; - int y6F16 = 0; - int xF16 = 0; - int yF16 = 0; - int var14 = 0; - int var15 = 0; - int x2F16 = 0; - int y2F16 = 0; - int x3F16 = 0; - int y3F16 = 0; - int x4F16 = 0; - int y4F16 = 0; - int circleXF16 = 0; - int circleYF16 = 0; - int x5F16 = 0; - int y5F16 = 0; - std::vector> var27, var28, var29; - if (field_46) { - if (field_37 < 32768) { - var28 = hardcodedArr2; - var29 = hardcodedArr1; - var7 = (int)((int64_t)field_37 * 131072L >> 16); - } else if (field_37 > 32768) { - var6 = 1; - var28 = hardcodedArr1; - var29 = hardcodedArr3; - var7 = (int)((int64_t)(field_37 - 32768) * 131072L >> 16); + int posePhase = 0; + int lerpFactorF16 = 65536; // 1.0 in 16.16 fixed-point + + // Rider's root anchor point is tied to the main chassis component + int chassisXF16 = renderCache[0]->xF16; + int chassisYF16 = renderCache[0]->yF16; + + // Skeletal joint coordinates + int handlebarXF16 = 0, handlebarYF16 = 0; + int ankleXF16 = 0, ankleYF16 = 0; + int footPegXF16 = 0, footPegYF16 = 0; + int kneeXF16 = 0, kneeYF16 = 0; + int hipXF16 = 0, hipYF16 = 0; + int shoulderXF16 = 0, shoulderYF16 = 0; + int headXF16 = 0, headYF16 = 0; + int elbowXF16 = 0, elbowYF16 = 0; + + std::vector> exactPose, startPose, endPose; + + // Determine which keyframes to interpolate based on lean angle + if (isRenderBodySprites) { + // Leaning Back + if (leanF16 < 32768) { + startPose = riderPoseLeanBackSprites; + endPose = riderPoseCenterSprites; + // 131072L >> 16 is effectively multiplying by 2.0. + // Normalizes the 0 - 32768 range into a 0 - 65536 (0.0 to 1.0) lerp factor. + lerpFactorF16 = (int)((int64_t)leanF16 * 131072L >> 16); + } + // Leaning Forward + else if (leanF16 > 32768) { + posePhase = 1; + startPose = riderPoseCenterSprites; + endPose = riderPoseLeanForwardSprites; + lerpFactorF16 = (int)((int64_t)(leanF16 - 32768) * 131072L >> 16); + } + // Perfectly Centered + else { + exactPose = riderPoseCenterSprites; + } + } + + // Line drawing mode + else { + if (leanF16 < 32768) { + startPose = riderPoseLeanBackLine; + endPose = riderPoseCenterLine; + lerpFactorF16 = (int)((int64_t)leanF16 * 131072L >> 16); + } else if (leanF16 > 32768) { + posePhase = 1; + startPose = riderPoseCenterLine; + endPose = riderPoseLeanForwardLine; + lerpFactorF16 = (int)((int64_t)(leanF16 - 32768) * 131072L >> 16); } else { - var27 = hardcodedArr1; + exactPose = riderPoseCenterLine; } - } else if (field_37 < 32768) { - var28 = hardcodedArr5; - var29 = hardcodedArr4; - var7 = (int)((int64_t)field_37 * 131072L >> 16); - } else if (field_37 > 32768) { - var6 = 1; - var28 = hardcodedArr4; - var29 = hardcodedArr6; - var7 = (int)((int64_t)(field_37 - 32768) * 131072L >> 16); - } else { - var27 = hardcodedArr4; } - for (std::size_t var30 = 0; var30 < hardcodedArr1.size(); ++var30) { - int var31; - int var32; - if (!var28.empty()) { - var32 = (int)((int64_t)var28[var30][0] * (int64_t)(65536 - var7) >> 16) + (int)((int64_t)var29[var30][0] * (int64_t)var7 >> 16); - var31 = (int)((int64_t)var28[var30][1] * (int64_t)(65536 - var7) >> 16) + (int)((int64_t)var29[var30][1] * (int64_t)var7 >> 16); + // Interpolate keyframes and apply 2D chassis transformation + for (std::size_t jointIdx = 0; jointIdx < riderPoseCenterLine.size(); ++jointIdx) { + int localXF16; + int localYF16; + + if (!startPose.empty()) { + // Linear interpolation: startPose * (1.0 - lerp) + endPose * (lerp) + localXF16 = (int)((int64_t)startPose[jointIdx][0] * (int64_t)(65536 - lerpFactorF16) >> 16) + (int)((int64_t)endPose[jointIdx][0] * (int64_t)lerpFactorF16 >> 16); + localYF16 = (int)((int64_t)startPose[jointIdx][1] * (int64_t)(65536 - lerpFactorF16) >> 16) + (int)((int64_t)endPose[jointIdx][1] * (int64_t)lerpFactorF16 >> 16); } else { - var32 = var27[var30][0]; - var31 = var27[var30][1]; + localXF16 = exactPose[jointIdx][0]; + localYF16 = exactPose[jointIdx][1]; } - int xxF16 = var8 + (int)((int64_t)var4 * (int64_t)var32 >> 16) + (int)((int64_t)var2 * (int64_t)var31 >> 16); - int yyF16 = var9 + (int)((int64_t)var5 * (int64_t)var32 >> 16) + (int)((int64_t)var3 * (int64_t)var31 >> 16); - switch (var30) { + // Apply 2D rotation matrix from bike chassis + int worldXF16 = chassisXF16 + (int)((int64_t)fwdXF16 * (int64_t)localXF16 >> 16) + (int)((int64_t)upXF16 * (int64_t)localYF16 >> 16); + int worldYF16 = chassisYF16 + (int)((int64_t)fwdYF16 * (int64_t)localXF16 >> 16) + (int)((int64_t)upYF16 * (int64_t)localYF16 >> 16); + + // Assign to specific joints + switch (jointIdx) { case 0: - x2F16 = xxF16; - y2F16 = yyF16; + kneeXF16 = worldXF16; + kneeYF16 = worldYF16; break; case 1: - x3F16 = xxF16; - y3F16 = yyF16; + hipXF16 = worldXF16; + hipYF16 = worldYF16; break; case 2: - x4F16 = xxF16; - y4F16 = yyF16; + shoulderXF16 = worldXF16; + shoulderYF16 = worldYF16; break; case 3: - circleXF16 = xxF16; - circleYF16 = yyF16; + headXF16 = worldXF16; + headYF16 = worldYF16; break; case 4: - x5F16 = xxF16; - y5F16 = yyF16; + elbowXF16 = worldXF16; + elbowYF16 = worldYF16; break; case 5: - xF16 = xxF16; - yF16 = yyF16; + ankleXF16 = worldXF16; + ankleYF16 = worldYF16; break; case 6: - var14 = xxF16; - var15 = yyF16; + footPegXF16 = worldXF16; + footPegYF16 = worldYF16; break; case 7: - x6F16 = xxF16; - y6F16 = yyF16; + handlebarXF16 = worldXF16; + handlebarYF16 = worldYF16; + break; } } - int var26 = (int)((int64_t)field_80[var6][0] * (int64_t)(65536 - var7) >> 16) + (int)((int64_t)field_80[var6 + 1][0] * (int64_t)var7 >> 16); - if (field_46) { - gameCanvas->renderBodyPart(xF16 << 2, yF16 << 2, x2F16 << 2, y2F16 << 2, 1); - gameCanvas->renderBodyPart(x2F16 << 2, y2F16 << 2, x3F16 << 2, y3F16 << 2, 1); - gameCanvas->renderBodyPart(x3F16 << 2, y3F16 << 2, x4F16 << 2, y4F16 << 2, 2, var26); - gameCanvas->renderBodyPart(x4F16 << 2, y4F16 << 2, x5F16 << 2, y5F16 << 2, 0); - int var30 = MathF16::atan2F16(var2, var3); - if (field_37 > 32768) { - var30 += 20588; + // Interpolate the anchor point so the torso sprite "slides" along the spine as the rider leans. + int torsoAnchorF16 = (int)((int64_t)torsoAnchorOffsets[posePhase][0] * (int64_t)(65536 - lerpFactorF16) >> 16) + (int)((int64_t)torsoAnchorOffsets[posePhase + 1][0] * (int64_t)lerpFactorF16 >> 16); + + // Render the rider + if (isRenderBodySprites) { + gameCanvas->renderBodyPart(ankleXF16 << 2, ankleYF16 << 2, kneeXF16 << 2, kneeYF16 << 2, 1); + gameCanvas->renderBodyPart(kneeXF16 << 2, kneeYF16 << 2, hipXF16 << 2, hipYF16 << 2, 1); + gameCanvas->renderBodyPart(hipXF16 << 2, hipYF16 << 2, shoulderXF16 << 2, shoulderYF16 << 2, 2, torsoAnchorF16); + gameCanvas->renderBodyPart(shoulderXF16 << 2, shoulderYF16 << 2, elbowXF16 << 2, elbowYF16 << 2, 0); + + // Calculate helmet angle based on the chassis rotation matrix components + int helmetAngleF16 = MathF16::atan2F16(upXF16, upYF16); + if (leanF16 > 32768) { + helmetAngleF16 += 20588; // Offset helmet if leaning forward } - gameCanvas->drawHelmet(circleXF16 << 2 >> 16, circleYF16 << 2 >> 16, var30); - } else { + gameCanvas->drawHelmet(headXF16 << 2 >> 16, headYF16 << 2 >> 16, helmetAngleF16); + } + + // Line drawing mode + else { gameCanvas->setColor(0, 0, 0); - gameCanvas->drawLineF16(xF16, yF16, x2F16, y2F16); - gameCanvas->drawLineF16(x2F16, y2F16, x3F16, y3F16); - gameCanvas->setColor(0, 0, 128); - gameCanvas->drawLineF16(x3F16, y3F16, x4F16, y4F16); - gameCanvas->drawLineF16(x4F16, y4F16, x5F16, y5F16); - gameCanvas->drawLineF16(x5F16, y5F16, x6F16, y6F16); - int var30 = 65536; - gameCanvas->setColor(156, 0, 0); - gameCanvas->drawCircle(circleXF16 << 2 >> 16, circleYF16 << 2 >> 16, (var30 + var30) << 2 >> 16); + gameCanvas->drawLineF16(ankleXF16, ankleYF16, kneeXF16, kneeYF16); + gameCanvas->drawLineF16(kneeXF16, kneeYF16, hipXF16, hipYF16); + + gameCanvas->setColor(0, 0, 128); // Blue torso + gameCanvas->drawLineF16(hipXF16, hipYF16, shoulderXF16, shoulderYF16); + gameCanvas->drawLineF16(shoulderXF16, shoulderYF16, elbowXF16, elbowYF16); + gameCanvas->drawLineF16(elbowXF16, elbowYF16, handlebarXF16, handlebarYF16); + + int radiusBaseF16 = 65536; // 1.0 radius factor + gameCanvas->setColor(156, 0, 0); // Red helmet fallback + gameCanvas->drawCircle(headXF16 << 2 >> 16, headYF16 << 2 >> 16, (radiusBaseF16 + radiusBaseF16) << 2 >> 16); } + // Attachment points (handlebar/foot peg) gameCanvas->setColor(0, 0, 0); - gameCanvas->drawForthSpriteByCenter(x6F16 << 2 >> 16, y6F16 << 2 >> 16); - gameCanvas->drawForthSpriteByCenter(var14 << 2 >> 16, var15 << 2 >> 16); + gameCanvas->drawAttachmentPointSprite(handlebarXF16 << 2 >> 16, handlebarYF16 << 2 >> 16); + gameCanvas->drawAttachmentPointSprite(footPegXF16 << 2 >> 16, footPegYF16 << 2 >> 16); } -void GamePhysics::renderMotoAsLines(GameCanvas* gameCanvas, int var2, int var3, int var4, int var5) +void GamePhysics::renderMotoAsLines(GameCanvas* gameCanvas, int upXF16, int upYF16, int fwdXF16, int fwdYF16) { - int var7 = motoComponents[2]->xF16; - int var8 = motoComponents[2]->yF16; - int var9 = var7 + (int)((int64_t)var4 * (int64_t)32768 >> 16); - int var10 = var8 + (int)((int64_t)var5 * (int64_t)32768 >> 16); - int var11 = var7 - (int)((int64_t)var4 * (int64_t)32768 >> 16); - int var12 = var8 - (int)((int64_t)var5 * (int64_t)32768 >> 16); - int var13 = motoComponents[0]->xF16 + (int)((int64_t)var2 * 32768L >> 16); - int var14 = motoComponents[0]->yF16 + (int)((int64_t)var3 * 32768L >> 16); - int var15 = var13 - (int)((int64_t)var2 * 131072L >> 16); - int var16 = var14 - (int)((int64_t)var3 * 131072L >> 16); - int var17 = var15 + (int)((int64_t)var4 * 65536L >> 16); - int var18 = var16 + (int)((int64_t)var5 * 65536L >> 16); - int var19 = var15 + (int)((int64_t)var2 * 49152L >> 16) + (int)((int64_t)var4 * 49152L >> 16); - int var20 = var16 + (int)((int64_t)var3 * 49152L >> 16) + (int)((int64_t)var5 * 49152L >> 16); - int var21 = var15 + (int)((int64_t)var4 * 32768L >> 16); - int var22 = var16 + (int)((int64_t)var5 * 32768L >> 16); - int var23 = motoComponents[1]->xF16; - int var24 = motoComponents[1]->yF16; - int var25 = motoComponents[4]->xF16 - (int)((int64_t)var2 * 49152L >> 16); - int var26 = motoComponents[4]->yF16 - (int)((int64_t)var3 * 49152L >> 16); - int var27 = var25 - (int)((int64_t)var4 * 32768L >> 16); - int var28 = var26 - (int)((int64_t)var5 * 32768L >> 16); - int var29 = var25 - (int)((int64_t)var2 * 131072L >> 16) + (int)((int64_t)var4 * 16384L >> 16); - int var30 = var26 - (int)((int64_t)var3 * 131072L >> 16) + (int)((int64_t)var5 * 16384L >> 16); - int var31 = motoComponents[3]->xF16; - int var32 = motoComponents[3]->yF16; - int var33 = var31 + (int)((int64_t)var4 * 32768L >> 16); - int var34 = var32 + (int)((int64_t)var5 * 32768L >> 16); - int var35 = var31 + (int)((int64_t)var4 * 114688L >> 16) - (int)((int64_t)var2 * 32768L >> 16); - int var36 = var32 + (int)((int64_t)var5 * 114688L >> 16) - (int)((int64_t)var3 * 32768L >> 16); + // Handlebars / Fork Top (Component 2) + int hbarX = renderCache[2]->xF16; + int hbarY = renderCache[2]->yF16; + int hbarLeftX = hbarX + (int)((int64_t)fwdXF16 * 32768 >> 16); + int hbarLeftY = hbarY + (int)((int64_t)fwdYF16 * 32768 >> 16); + int hbarRightX = hbarX - (int)((int64_t)fwdXF16 * 32768 >> 16); + int hbarRightY = hbarY - (int)((int64_t)fwdYF16 * 32768 >> 16); + + // Main Chassis Frame (Component 0) + int frameRootX = renderCache[0]->xF16; + int frameRootY = renderCache[0]->yF16; + int seatPostTopX = frameRootX + (int)((int64_t)upXF16 * 32768 >> 16); + int seatPostTopY = frameRootY + (int)((int64_t)upYF16 * 32768 >> 16); + + // Bottom of the frame (swung down 2 units from the seat post top) + int frameBottomX = seatPostTopX - (int)((int64_t)upXF16 * 131072 >> 16); + int frameBottomY = seatPostTopY - (int)((int64_t)upYF16 * 131072 >> 16); + + // Engine Guard / Lower Frame + int engGuardX = frameBottomX + (int)((int64_t)fwdXF16 * 65536 >> 16); + int engGuardY = frameBottomY + (int)((int64_t)fwdYF16 * 65536 >> 16); + + // Fuel Tank / Top Tube area + int tankX = frameBottomX + (int)((int64_t)upXF16 * 49152 >> 16) + (int)((int64_t)fwdXF16 * 49152 >> 16); + int tankY = frameBottomY + (int)((int64_t)upYF16 * 49152 >> 16) + (int)((int64_t)fwdYF16 * 49152 >> 16); + + int seatX = frameBottomX + (int)((int64_t)fwdXF16 * 32768 >> 16); + int seatY = frameBottomY + (int)((int64_t)fwdYF16 * 32768 >> 16); + + // Wheel Mount Points + int frontWheelX = renderCache[1]->xF16; + int frontWheelY = renderCache[1]->yF16; + int rearWheelX = renderCache[3]->xF16; + int rearWheelY = renderCache[3]->yF16; + + // Rear Swingarm / Exhaust assembly (Component 4) + int exhaustStartX = renderCache[4]->xF16 - (int)((int64_t)upXF16 * 49152 >> 16); + int exhaustStartY = renderCache[4]->yF16 - (int)((int64_t)upYF16 * 49152 >> 16); + int swingarmPivotX = exhaustStartX - (int)((int64_t)fwdXF16 * 32768 >> 16); + int swingarmPivotY = exhaustStartY - (int)((int64_t)fwdYF16 * 32768 >> 16); + int exhaustEndX = exhaustStartX - (int)((int64_t)upXF16 * 131072 >> 16) + (int)((int64_t)fwdXF16 * 16384 >> 16); + int exhaustEndY = exhaustStartY - (int)((int64_t)upYF16 * 131072 >> 16) + (int)((int64_t)fwdYF16 * 16384 >> 16); + + // Rear Axle supports + int rearSupportX = rearWheelX + (int)((int64_t)fwdXF16 * 32768 >> 16); + int rearSupportY = rearWheelY + (int)((int64_t)fwdYF16 * 32768 >> 16); + int sissyBarTopX = rearWheelX + (int)((int64_t)fwdXF16 * 114688 >> 16) - (int)((int64_t)upXF16 * 32768 >> 16); + int sissyBarTopY = rearWheelY + (int)((int64_t)fwdYF16 * 114688 >> 16) - (int)((int64_t)upYF16 * 32768 >> 16); + gameCanvas->setColor(50, 50, 50); - gameCanvas->drawCircle(var21 << 2 >> 16, var22 << 2 >> 16, (32768 + 32768) << 2 >> 16); - if (!field_35) { - gameCanvas->drawLineF16(var9, var10, var17, var18); - gameCanvas->drawLineF16(var11, var12, var15, var16); + + // Draw the seat/frame circle + gameCanvas->drawCircle(seatX << 2 >> 16, seatY << 2 >> 16, 65536 << 2 >> 16); + + if (!isBikeDestroyed) { + gameCanvas->drawLineF16(hbarLeftX, hbarLeftY, engGuardX, engGuardY); + gameCanvas->drawLineF16(hbarRightX, hbarRightY, frameBottomX, frameBottomY); } - gameCanvas->drawLineF16(var13, var14, var15, var16); - gameCanvas->drawLineF16(var13, var14, var31, var32); - gameCanvas->drawLineF16(var19, var20, var33, var34); - gameCanvas->drawLineF16(var33, var34, var35, var36); - if (!field_35) { - gameCanvas->drawLineF16(var31, var32, var23, var24); - gameCanvas->drawLineF16(var35, var36, var23, var24); + // Connect the frame nodes + gameCanvas->drawLineF16(seatPostTopX, seatPostTopY, frameBottomX, frameBottomY); + gameCanvas->drawLineF16(seatPostTopX, seatPostTopY, rearWheelX, rearWheelY); + gameCanvas->drawLineF16(tankX, tankY, rearSupportX, rearSupportY); + gameCanvas->drawLineF16(rearSupportX, rearSupportY, sissyBarTopX, sissyBarTopY); + + if (!isBikeDestroyed) { + gameCanvas->drawLineF16(rearWheelX, rearWheelY, frontWheelX, frontWheelY); + gameCanvas->drawLineF16(sissyBarTopX, sissyBarTopY, frontWheelX, frontWheelY); } - gameCanvas->drawLineF16(var17, var18, var27, var28); - gameCanvas->drawLineF16(var19, var20, var25, var26); - gameCanvas->drawLineF16(var25, var26, var29, var30); - gameCanvas->drawLineF16(var27, var28, var29, var30); + // Connect swingarm/exhaust geometry + gameCanvas->drawLineF16(engGuardX, engGuardY, swingarmPivotX, swingarmPivotY); + gameCanvas->drawLineF16(tankX, tankY, exhaustStartX, exhaustStartY); + gameCanvas->drawLineF16(exhaustStartX, exhaustStartY, exhaustEndX, exhaustEndY); + gameCanvas->drawLineF16(swingarmPivotX, swingarmPivotY, exhaustEndX, exhaustEndY); } void GamePhysics::renderGame(GameCanvas* gameCanvas) { gameCanvas->clearScreenWithWhite(); - int xxF16 = motoComponents[3]->xF16 - motoComponents[4]->xF16; - int yyF16 = motoComponents[3]->yF16 - motoComponents[4]->yF16; - int maxAbs; - if ((maxAbs = getSmthLikeMaxAbs(xxF16, yyF16)) != 0) { - xxF16 = (int)(((int64_t)xxF16 << 32) / (int64_t)maxAbs >> 16); - yyF16 = (int)(((int64_t)yyF16 << 32) / (int64_t)maxAbs >> 16); + + // Calculate the vector between front and rear wheels to determine bike tilt + // Component 3: Rear Wheel, Component 4: Front Wheel + int upXF16 = renderCache[3]->xF16 - renderCache[4]->xF16; + int upYF16 = renderCache[3]->yF16 - renderCache[4]->yF16; + + int length = fastVectorLengthF16(upXF16, upYF16); + if (length != 0) { + // Normalize the Up Vector + upXF16 = (int)(((int64_t)upXF16 << 32) / (int64_t)length >> 16); + upYF16 = (int)(((int64_t)upYF16 << 32) / (int64_t)length >> 16); } - int var5 = -yyF16; - if (field_35) { - int var8 = motoComponents[4]->xF16; - int var7; - if ((var7 = motoComponents[3]->xF16) >= var8) { - int var9 = var7; - var7 = var8; - var8 = var9; - } + // Derive the perpendicular Forward Vector (rotate Up Vector by 90 degrees) + int fwdXF16 = -upYF16; + int fwdYF16 = upXF16; - levelLoader->gameLevel->method_183(var7, var8); + if (isBikeDestroyed) { + // Find the bounding X-range of the crash for camera/level logic + int frontX = renderCache[4]->xF16; + int rearX = renderCache[3]->xF16; + if (rearX >= frontX) { + levelLoader->gameLevel->setShadowBoundariesHalf(frontX, rearX); + } else { + levelLoader->gameLevel->setShadowBoundariesHalf(rearX, frontX); + } } if (LevelLoader::isEnabledPerspective) { - levelLoader->renderLevel3D(gameCanvas, motoComponents[0]->xF16, motoComponents[0]->yF16); + levelLoader->renderTrack3D(gameCanvas, renderCache[0]->xF16, renderCache[0]->yF16); } + // Render mechanical parts if (isRenderMotoWithSprites) { - renderEngine(gameCanvas, xxF16, yyF16); + renderEngine(gameCanvas, upXF16, upYF16); } if (!Micro::isInGameMenu) { @@ -1219,21 +1334,27 @@ void GamePhysics::renderGame(GameCanvas* gameCanvas) } renderWheelSpokes(gameCanvas); - if (isRenderMotoWithSprites) { - gameCanvas->setColor(170, 0, 0); - } else { - gameCanvas->setColor(50, 50, 50); - } - gameCanvas->method_142(motoComponents[1]->xF16 << 2 >> 16, motoComponents[1]->yF16 << 2 >> 16, const175_1_half[0] << 2 >> 16, MathF16::atan2F16(xxF16, yyF16)); - if (!field_35) { + gameCanvas->setColor(isRenderMotoWithSprites ? 170 : 50, 0, 0); + + // Draw the front wheel hub/details + int frontWheelAngle = MathF16::atan2F16(upXF16, upYF16); + gameCanvas->drawWheelHub( + renderCache[1]->xF16 << 2 >> 16, + renderCache[1]->yF16 << 2 >> 16, + wheelRadiusValuesF16[0] << 2 >> 16, + frontWheelAngle); + + if (!isBikeDestroyed) { renderMotoFork(gameCanvas); } - renderSmth(gameCanvas, xxF16, yyF16, var5, xxF16); + // Pass the basis vectors (Up and Forward) to the renderers + renderRider(gameCanvas, upXF16, upYF16, fwdXF16, fwdYF16); + if (!isRenderMotoWithSprites) { - renderMotoAsLines(gameCanvas, xxF16, yyF16, var5, xxF16); + renderMotoAsLines(gameCanvas, upXF16, upYF16, fwdXF16, fwdYF16); } - levelLoader->renderTrackNearestLine(gameCanvas); + levelLoader->renderTrackCenterline(gameCanvas); } diff --git a/src/GamePhysics.h b/src/GamePhysics.h index ed62c64..c4188b6 100644 --- a/src/GamePhysics.h +++ b/src/GamePhysics.h @@ -2,36 +2,89 @@ #include #include -#include "TimerOrMotoPartOrMenuElem.h" +#include "PhysicsElemOrMenuItem.h" class LevelLoader; -class class_10; +class MotoComponent; class GameCanvas; +/** + * PHYSICS BUFFERING SCHEME + * + * Each MotoComponent has 6 stateBuffers for multi-stage physics integration: + * + * 0 - readBuffer - Current validated state (read source for simulation) + * 1 - writeBuffer - Next state under construction (write target) + * 2 - workA - Integration intermediate (predictor step) + * 3 - workB - Integration intermediate (corrector step) + * 4 - workMid - Midpoint state for predictor-corrector + * 5 - renderCache - Snapshot for rendering (decoupled from physics) + * + * Double buffering: readBuffer/writeBuffer swap after each physics frame. + * Integration: Uses buffers 2-4 for predictor-corrector (RK2-like) scheme. + * Rendering: Reads from buffer 5 via renderCache copy. + */ class GamePhysics { private: - int index01 = 0; - int index10 = 1; - int field_28 = -1; - std::vector> field_30; + // Current validated physics state (read source) + int readBufferIndex = 0; + // Next physics state under construction (write target) + int writeBufferIndex = 1; + // Last collided component index (-1 if none) + int lastCollidedComponentIndex = -1; + /** + * Spring/suspension connections between bike components, storing rest length, stiffness, and damping + * + * 0 - chassis ↔ front wheel + * 1 - chassis ↔ back wheel + * 2 - chassis ↔ handlebar + * 3 - chassis ↔ seat + * 4 - handlebar ↔ seat + * 5 - front wheel ↔ handlebar + * 6 - back wheel ↔ seat + * 7 - rider ↔ seat + * 8 - rider ↔ handlebar + * 9 - rider ↔ chassis + */ + std::vector> springConstraints; - int field_31 = 0; + // Engine momentum / throttle value (drives wheel torque) + int engineMomentumF16 = 0; LevelLoader* levelLoader; - int field_33 = 0; - int field_34 = 0; - bool field_35 = false; - bool field_36 = false; - int field_37 = 32768; - const int field_38 = 3276; - int field_39 = 0; - bool field_42 = false; + // Collision normal X - from LevelLoader + int collisionNormalXF16 = 0; + // Collision normal Y - from LevelLoader + int collisionNormalYF16 = 0; + // Bike destroyed / crashed flag + bool isBikeDestroyed = false; + // Player head crash flag - set when rider head (component 5) touches ground + bool isPlayerHeadCrashed = false; + /** + * Lean back/forward + * 0 - back + * 32768 - center + * 65536 - forward + */ + int leanF16 = 32768; + // Lean restore rate + const int leanRestoreRateF16 = 3276; + // Lean rate accumulator / angular momentum + int leanRateAccumulatorF16 = 0; + // Track started flag - set when track has started + bool isTrackStartedFlag = false; /** - * 1 - forward wheel + * Render-ready cache from stateBuffer[5]. + * + * 0 - center + * 1 - front wheel * 2 - back wheel * 3 - handlebar + * 4 - seat + * 5 - player */ - std::vector> motoComponents = std::vector>(6); - int field_44; + std::vector> renderCache = std::vector>(6); + // Physics frame counter - incremented each physics frame + int physicsFrameCounter; bool isInputAcceleration; bool isInputBreak; bool isInputBack; @@ -40,88 +93,127 @@ class GamePhysics { bool isInputDown; bool isInputLeft; bool isInputRight; - bool field_68; + // Track finished flag - set when isTrackFinished() returns true + bool isTrackFinishedFlag; bool isEnableLookAhead; int camShiftX; int camShiftY; - int field_73; - const std::vector> hardcodedArr1 = { { 183500, -52428 }, { 262144, -163840 }, { 406323, -65536 }, { 445644, -39321 }, { 235929, 39321 }, { 16384, -144179 }, { 13107, -78643 }, { 288358, 81920 } }; - const std::vector> hardcodedArr2 = { { 190054, -111411 }, { 308019, -235929 }, { 334233, -114688 }, { 393216, -58982 }, { 262144, 98304 }, { 65536, -124518 }, { 13107, -78643 }, { 288358, 81920 } }; - const std::vector> hardcodedArr3 = { { 157286, 13107 }, { 294912, -13107 }, { 367001, 91750 }, { 406323, 190054 }, { 347340, 72089 }, { 39321, -98304 }, { 13107, -52428 }, { 294912, 81920 } }; - const std::vector> hardcodedArr4 = { { 183500, -39321 }, { 262144, -131072 }, { 393216, -65536 }, { 458752, -39321 }, { 294912, 6553 }, { 16384, -144179 }, { 13107, -78643 }, { 288358, 85196 } }; - const std::vector> hardcodedArr5 = { { 190054, -91750 }, { 255590, -235929 }, { 334233, -114688 }, { 393216, -42598 }, { 301465, 6553 }, { 65536, -78643 }, { 13107, -78643 }, { 288358, 85196 } }; - const std::vector> hardcodedArr6 = { { 157286, 13107 }, { 294912, -13107 }, { 367001, 104857 }, { 406323, 176947 }, { 347340, 72089 }, { 39321, -98304 }, { 13107, -52428 }, { 288358, 85196 } }; - std::vector> field_80; + // Camera look-ahead limit (pixels) - clamps camShiftX and camShiftY + int cameraLookAheadLimit; + + // Rider pose keyframes for different lean angles. + // Indices represent skeletal joints: + // 0: Knee + // 1: Hip/Pelvis + // 2: Shoulder + // 3: Head/Neck + // 4: Elbow/Hand + // 5: Ankle/Foot base + // 6: Foot peg position + // 7: Handlebar grip position + + // Keyframes (when sprite rendering) + const std::vector> riderPoseCenterSprites = { { 183500, -52428 }, { 262144, -163840 }, { 406323, -65536 }, { 445644, -39321 }, { 235929, 39321 }, { 16384, -144179 }, { 13107, -78643 }, { 288358, 81920 } }; + const std::vector> riderPoseLeanBackSprites = { { 190054, -111411 }, { 308019, -235929 }, { 334233, -114688 }, { 393216, -58982 }, { 262144, 98304 }, { 65536, -124518 }, { 13107, -78643 }, { 288358, 81920 } }; + const std::vector> riderPoseLeanForwardSprites = { { 157286, 13107 }, { 294912, -13107 }, { 367001, 91750 }, { 406323, 190054 }, { 347340, 72089 }, { 39321, -98304 }, { 13107, -52428 }, { 294912, 81920 } }; + // Keyframes (when line drawing) + const std::vector> riderPoseCenterLine = { { 183500, -39321 }, { 262144, -131072 }, { 393216, -65536 }, { 458752, -39321 }, { 294912, 6553 }, { 16384, -144179 }, { 13107, -78643 }, { 288358, 85196 } }; + const std::vector> riderPoseLeanBackLine = { { 190054, -91750 }, { 255590, -235929 }, { 334233, -114688 }, { 393216, -42598 }, { 301465, 6553 }, { 65536, -78643 }, { 13107, -78643 }, { 288358, 85196 } }; + const std::vector> riderPoseLeanForwardLine = { { 157286, 13107 }, { 294912, -13107 }, { 367001, 104857 }, { 406323, 176947 }, { 347340, 72089 }, { 39321, -98304 }, { 13107, -52428 }, { 288358, 85196 } }; + + std::vector> torsoAnchorOffsets; - void method_27(int var1, int var2); + void resetPhysics(int startX, int startY); void setInputFromAI(); - void method_35(); - int method_39(int var1); - void method_40(int var1); - void method_42(class_10* var1, TimerOrMotoPartOrMenuElem* var2, class_10* var3, int var4, int var5); - void method_43(int var1, int var2, int var3); - void method_44(int var1, int var2, int var3); - void method_45(int var1); - int method_46(int var1); - void method_47(int var1); - void renderEngine(GameCanvas* gameCanvas, int var2, int var3); + void processLeanInput(); + int physicsSubstepLoop(int iterations); + void applyForces(int bufferIndex); + void applySpringConstraint(MotoComponent* anchor, PhysicsElemOrMenuItem* spring, MotoComponent* target, int bufferIndex, int stiffnessF16); + void integratePosition(int fromBuffer, int toBuffer, int dtF16); + void interpolatePosition(int toBuffer, int buf1, int buf2); + void performPhysicsSubstep(int dtF16); + int checkTrackCollisions(int bufferIndex); + void applyCollisionResponse(int bufferIndex); + void renderEngine(GameCanvas* gameCanvas, int upXF16, int upYF16); void renderMotoFork(GameCanvas* canvas); void renderWheelTires(GameCanvas* canvas); void renderWheelSpokes(GameCanvas* gameCanvas); - void renderSmth(GameCanvas* gameCanvas, int var2, int var3, int var4, int var5); - void renderMotoAsLines(GameCanvas* gameCanvas, int var2, int var3, int var4, int var5); + void renderRider(GameCanvas* gameCanvas, int cosTheta, int sinTheta, int cosPhi, int sinPhi); + void renderMotoAsLines(GameCanvas* gameCanvas, int upXF16, int upYF16, int fwdXF16, int fwdYF16); public: - inline static int field_7; - inline static int field_8; - inline static int field_9; - inline static int field_10; - inline static int field_11; - inline static int motoParam1; - inline static int motoParam2; - inline static int field_14; - inline static int motoParam10; - inline static int field_16; - inline static std::vector const175_1_half = { 114688, 65536, 32768 }; - inline static int motoParam3; - inline static int field_19; - inline static int motoParam4; - inline static int motoParam5; - inline static int motoParam6; - inline static int motoParam7; - inline static int motoParam8; - inline static int motoParam9; - std::vector> field_29; - bool field_41 = false; - int field_45; - bool field_46; + // Physics step count / substeps per frame - passed to physicsSubstepLoop() + inline static int physicsSubstepsPerFrame; + // Gravity - applied as downward force + inline static int gravityF16; + // Normal friction coefficient - used in collision response + inline static int normalFrictionF16; + // Tangential friction coefficient - used in collision response + inline static int tangentialFrictionF16; + // Restitution/bounce coefficient - used in collision bounce + inline static int restitutionF16; + // Lean force coefficient X + inline static int leanForceCoefficientXF16; + // Lean force coefficient Y + inline static int leanForceCoefficientYF16; + // Global mass/inertia scaler - used to compute per-component mass + inline static int globalMassScalerF16; + // Default X position offset - used to initialize component X positions + inline static int defaultXOffsetF16; + // Default wheel angle (radians) - typically 262144 (4π) + inline static int defaultWheelAngleF16; + // Wheel radius values - index 0,1,2 map to different wheel sizes + inline static std::vector wheelRadiusValuesF16 = { 114688, 65536, 32768 }; + // Max angular velocity clamp - wheel angular velocity limited to ±this value + inline static int maxAngularVelocityF16; + // Engine momentum decay rate - exponential decay: momentum *= (65536 - decay) + inline static int engineMomentumDecayF16; + // Max engine momentum (throttle limit) + inline static int maxEngineMomentumF16; + // Engine acceleration rate - subtracted from engineMomentum when accelerating + inline static int engineAccelerationRateF16; + // Brake angular damping - angular vel *= (65536 - damping) when braking + inline static int brakeAngularDampingF16; + // Brake friction modifier - subtracted from friction when braking on wheels + inline static int brakeFrictionModifierF16; + // Lean input sensitivity - scales leanF16 force from input + inline static int leanInputSensitivityF16; + // Max leanF16 rate - leanRateAccumulator clamped to ±this value + inline static int maxLeanRateF16; + // Moto components (6 bike parts: chassis, wheels, rider, etc.) + std::vector> motoComponents; + // Track started flag (duplicate, used in resetPhysicsState) + bool isTrackStartedFlag2 = false; + int renderMode; + bool isRenderBodySprites; bool isRenderMotoWithSprites; - inline static int curentMotoLeague = 0; - bool field_69; + inline static int currentLeague = 0; + // Front wheel contact latch - becomes true when front wheel touches ground, never reset + bool frontWheelContactLatch; bool isGenerateInputAI = false; GamePhysics(LevelLoader* levelLoader); - int method_21(); - void method_22(int var1); + int getRenderModeIndex(); + void setRenderFlags(int flags); void setMode(int mode); void setMotoLeague(int league); - void resetSmth(bool unused); - void method_26(bool var1); + void resetPhysicsState(bool unused); + void invertYPositions(bool isInverted); void setRenderMinMaxX(int minX, int maxX); - void processPointerReleased(); - void method_30(int var1, int var2); + void resetInputs(); + void updateInputs(int upDown, int leftRight); void enableGenerateInputAI(); void disableGenerateInputAI(); int updatePhysics(); bool isTrackStarted(); - bool method_38(); - static int getSmthLikeMaxAbs(int xF16, int yF16); + bool isTrackFinished(); + static int fastVectorLengthF16(int xF16, int yF16); void setEnableLookAhead(bool value); void setMinimalScreenWH(int minWH); int getCamPosX(); int getCamPosY(); - int method_52(); - void method_53(); - void setMotoComponents(); + int getRawXDistance(); + void captureRenderSnapshot(); + void prepareRenderCache(); void renderGame(GameCanvas* gameCanvas); }; diff --git a/src/IGameMenuElement.h b/src/IGameMenuElement.h index 29d5f11..2eead5c 100644 --- a/src/IGameMenuElement.h +++ b/src/IGameMenuElement.h @@ -9,5 +9,5 @@ class IGameMenuElement { virtual void setText(std::string text) = 0; virtual void render(Graphics* graphics, int y, int x) = 0; virtual bool isNotTextRender() = 0; - virtual void menuElemMethod(int var1) = 0; + virtual void menuElemMethod(int action) = 0; }; diff --git a/src/IMenuManager.h b/src/IMenuManager.h index 8ef48a0..0158326 100644 --- a/src/IMenuManager.h +++ b/src/IMenuManager.h @@ -4,8 +4,8 @@ class IMenuManager { public: - virtual GameMenu* getGameMenu() = 0; - virtual void method_1(GameMenu* var1, bool var2) = 0; - virtual void saveSmthToRecordStoreAndCloseIt() = 0; - virtual void processMenu(IGameMenuElement* var1) = 0; + virtual GameMenu* getCurrentMenu() = 0; + virtual void switchToMenu(GameMenu* menu, bool skipSelectionReset) = 0; + virtual void saveStateAndCloseRecordStore() = 0; + virtual void handleMenuSelection(IGameMenuElement* element) = 0; }; diff --git a/src/LevelLoader.cpp b/src/LevelLoader.cpp index fa3d73c..1c5491e 100644 --- a/src/LevelLoader.cpp +++ b/src/LevelLoader.cpp @@ -5,24 +5,19 @@ #include #include -int LevelLoader::field_133 = 0; -int LevelLoader::field_134 = 0; -int LevelLoader::field_135 = 0; -int LevelLoader::field_136 = 0; - -const int LevelLoader::field_114 = 0; -const int LevelLoader::field_115 = 1; -const int LevelLoader::field_116 = 2; -const int LevelLoader::field_117 = 0; -const int LevelLoader::field_118 = 1; +int LevelLoader::visibleSegmentStartIdx = 0; +int LevelLoader::visibleSegmentEndIdx = 0; +int LevelLoader::visibleSegmentStartX = 0; +int LevelLoader::visibleSegmentEndX = 0; + bool LevelLoader::isEnabledPerspective = true; bool LevelLoader::isEnabledShadows = true; LevelLoader::LevelLoader(const std::filesystem::path& mrgFilePath) { for (int i = 0; i < 3; ++i) { - field_123[i] = (int)((int64_t)((GamePhysics::const175_1_half[i] + 19660) >> 1) * (int64_t)((GamePhysics::const175_1_half[i] + 19660) >> 1) >> 16); - field_124[i] = (int)((int64_t)((GamePhysics::const175_1_half[i] - 19660) >> 1) * (int64_t)((GamePhysics::const175_1_half[i] - 19660) >> 1) >> 16); + collisionRadiusSqOuter[i] = (int)((int64_t)((GamePhysics::wheelRadiusValuesF16[i] + 19660) >> 1) * (int64_t)((GamePhysics::wheelRadiusValuesF16[i] + 19660) >> 1) >> 16); + collisionRadiusSqInner[i] = (int)((int64_t)((GamePhysics::wheelRadiusValuesF16[i] - 19660) >> 1) * (int64_t)((GamePhysics::wheelRadiusValuesF16[i] - 19660) >> 1) >> 16); } if (!mrgFilePath.string().empty()) { @@ -37,7 +32,7 @@ LevelLoader::LevelLoader(const std::filesystem::path& mrgFilePath) } loadLevels(); - method_87(); + loadCurrentTrack(); } LevelLoader::~LevelLoader() @@ -47,25 +42,30 @@ LevelLoader::~LevelLoader() void LevelLoader::loadLevels() { - std::vector var3(40); - std::vector var4(3); - - for (int league = 0; league < 3; ++league) { - levelFileStream->readVariable(&var4[league], true); - levelOffsetInFile[league] = std::vector(var4[league]); - levelNames[league] = std::vector(var4[league]); - - for (int levelNp = 0; levelNp < var4[league]; ++levelNp) { - int var7; - levelFileStream->readVariable(&var7, true); - levelOffsetInFile[league][levelNp] = var7; - - for (int var8 = 0; var8 < 40; ++var8) { - levelFileStream->readVariable(&var3[var8], true); - if (var3[var8] == 0) { - std::string s = std::string(reinterpret_cast(var3.data()), var8); - std::replace(s.begin(), s.end(), '_', ' '); - levelNames[league][levelNp] = s; + // Buffer for reading level name (null-terminated) + std::vector nameBuffer(40); + // Number of tracks per level + std::vector trackCounts(3); + + for (int level = 0; level < 3; ++level) { + // Read number of levels in this league + levelFileStream->readVariable(&trackCounts[level], true); + trackOffsetInFile[level] = std::vector(trackCounts[level]); + trackNames[level] = std::vector(trackCounts[level]); + + for (int track = 0; track < trackCounts[level]; ++track) { + // Read byte offset of track data in MRG file + int byteOffset; + levelFileStream->readVariable(&byteOffset, true); + trackOffsetInFile[level][track] = byteOffset; + + // Read level name (null-terminated string, max 40 chars) + for (int charIdx = 0; charIdx < 40; ++charIdx) { + levelFileStream->readVariable(&nameBuffer[charIdx], true); + if (nameBuffer[charIdx] == 0) { + std::string levelName = std::string(reinterpret_cast(nameBuffer.data()), charIdx); + std::replace(levelName.begin(), levelName.end(), '_', ' '); + trackNames[level][track] = levelName; break; } } @@ -75,246 +75,283 @@ void LevelLoader::loadLevels() std::string LevelLoader::getName(int league, int level) { - return league < 3 && level < static_cast(levelNames[league].size()) ? levelNames[league][level] : "---"; + return league < 3 && level < static_cast(trackNames[league].size()) ? trackNames[league][level] : "---"; } -void LevelLoader::method_87() +void LevelLoader::loadCurrentTrack() { - method_88(field_125, field_126 + 1); + loadTrack(currentLevel, currentTrack + 1); } -int LevelLoader::method_88(int var1, int var2) +int LevelLoader::loadTrack(int league, int track) { - field_125 = var1; - field_126 = var2; - if (field_126 >= static_cast(levelNames[field_125].size())) { - field_126 = 0; + currentLevel = league; + currentTrack = track; + // Wrap track index if out of bounds + if (currentTrack >= static_cast(trackNames[currentLevel].size())) { + currentTrack = 0; } - method_89(field_125 + 1, field_126 + 1); - return field_126; + // Convert to 1-based indexing for seekAndLoadTrackData + seekAndLoadTrackData(currentLevel + 1, currentTrack + 1); + return currentTrack; } -void LevelLoader::method_89(int var1, int var2) +void LevelLoader::seekAndLoadTrackData(int league, int track) { - levelFileStream->setPos(levelOffsetInFile[var1 - 1][var2 - 1]); + // Seek to track byte offset in MRG file (1-based indices) + levelFileStream->setPos(trackOffsetInFile[league - 1][track - 1]); if (gameLevel == nullptr) { gameLevel = new GameLevel(); } gameLevel->load(levelFileStream); - method_96(gameLevel); + precomputeTrackGeometry(gameLevel); } -void LevelLoader::method_90(int var1) +void LevelLoader::cacheStartPosition() { - (void)var1; - field_129 = gameLevel->startPosX << 1; - field_130 = gameLevel->startPosY << 1; + // Cache start position in F16 format (shifted left by 1) + cachedStartPosXF16 = gameLevel->startPosX << 1; + cachedStartPosYF16 = gameLevel->startPosY << 1; } -int LevelLoader::method_91() +int LevelLoader::getFinishFlagX() { return gameLevel->pointPositions[gameLevel->finishFlagPoint][0] << 1; } -int LevelLoader::method_92() +int LevelLoader::getStartFlagX() { return gameLevel->pointPositions[gameLevel->startFlagPoint][0] << 1; } -int LevelLoader::method_93() +int LevelLoader::getStartPosX() { return gameLevel->startPosX << 1; } -int LevelLoader::method_94() +int LevelLoader::getStartPosY() { return gameLevel->startPosY << 1; } -int LevelLoader::method_95(int var1) +int LevelLoader::getTrackProgressRatio(int xF16) { - return gameLevel->method_181(var1 >> 1); + // Convert from F16 and compute progress ratio along track + return gameLevel->calculateProgressPercent(xF16 >> 1); } -void LevelLoader::method_96(GameLevel* gameLevel) +void LevelLoader::precomputeTrackGeometry(GameLevel* level) { - field_131 = INT_MIN; - this->gameLevel = gameLevel; - int var2 = gameLevel->pointsCount; - if (field_121.empty() || field_132 < var2) { - field_132 = var2 < 100 ? 100 : var2; - field_121.assign(field_132, std::vector(2)); + trackMinX = INT_MIN; + this->gameLevel = level; + int pointsCount = level->pointsCount; + + // Resize trackSegmentNormals array if needed (min capacity: 100) + if (trackSegmentNormals.empty() || trackNormalsCapacity < pointsCount) { + trackNormalsCapacity = pointsCount < 100 ? 100 : pointsCount; + trackSegmentNormals.assign(trackNormalsCapacity, std::vector(2)); } - field_133 = 0; - field_134 = 0; - field_135 = gameLevel->pointPositions[field_133][0]; - field_136 = gameLevel->pointPositions[field_134][0]; + // Reset visible segment range + visibleSegmentStartIdx = 0; + visibleSegmentEndIdx = 0; + visibleSegmentStartX = level->pointPositions[visibleSegmentStartIdx][0]; + visibleSegmentEndX = level->pointPositions[visibleSegmentEndIdx][0]; + + // Compute normalized normal vectors for each track segment + for (int i = 0; i < pointsCount; ++i) { + int dx = level->pointPositions[(i + 1) % pointsCount][0] - level->pointPositions[i][0]; + int dy = level->pointPositions[(i + 1) % pointsCount][1] - level->pointPositions[i][1]; - for (int var3 = 0; var3 < var2; ++var3) { - int var4 = gameLevel->pointPositions[(var3 + 1) % var2][0] - gameLevel->pointPositions[var3][0]; - int var5 = gameLevel->pointPositions[(var3 + 1) % var2][1] - gameLevel->pointPositions[var3][1]; - if (var3 != 0 && var3 != var2 - 1) { - field_131 = field_131 < gameLevel->pointPositions[var3][0] ? gameLevel->pointPositions[var3][0] : field_131; + // Track minimum X (excluding first and last point) + if (i != 0 && i != pointsCount - 1) { + trackMinX = trackMinX < level->pointPositions[i][0] ? level->pointPositions[i][0] : trackMinX; } - int var6 = -var5; - int var8 = GamePhysics::getSmthLikeMaxAbs(var6, var4); - field_121[var3][0] = (int)(((int64_t)var6 << 32) / (int64_t)var8 >> 16); - field_121[var3][1] = (int)(((int64_t)var4 << 32) / (int64_t)var8 >> 16); - if (gameLevel->startFlagPoint == 0 && gameLevel->pointPositions[var3][0] > gameLevel->startPosX) { - gameLevel->startFlagPoint = var3 + 1; + // Compute normal vector (perpendicular to segment) + int nx = -dy; + int length = GamePhysics::fastVectorLengthF16(nx, dx); + trackSegmentNormals[i][0] = (int)(((int64_t)nx << 32) / (int64_t)length >> 16); + trackSegmentNormals[i][1] = (int)(((int64_t)dx << 32) / (int64_t)length >> 16); + + // Determine start flag point index + if (level->startFlagPoint == 0 && level->pointPositions[i][0] > level->startPosX) { + level->startFlagPoint = i + 1; } - if (gameLevel->finishFlagPoint == 0 && gameLevel->pointPositions[var3][0] > gameLevel->finishPosX) { - gameLevel->finishFlagPoint = var3; + // Determine finish flag point index + if (level->finishFlagPoint == 0 && level->pointPositions[i][0] > level->finishPosX) { + level->finishFlagPoint = i; } } - field_133 = 0; - field_134 = 0; - field_135 = 0; - field_136 = 0; + // Reset visible segment cache + visibleSegmentStartIdx = 0; + visibleSegmentEndIdx = 0; + visibleSegmentStartX = 0; + visibleSegmentEndX = 0; } -void LevelLoader::setMinMaxX(int minX, int maxX) +void LevelLoader::setLevelBounds(int minX, int maxX) { gameLevel->setMinMaxX(minX, maxX); } -void LevelLoader::renderLevel3D(GameCanvas* gameCanvas, int xF16, int yF16) +void LevelLoader::renderTrack3D(GameCanvas* canvas, int cameraXF16, int cameraYF16) { - if (gameCanvas != nullptr) { - gameCanvas->setColor(0, 170, 0); - xF16 >>= 1; - yF16 >>= 1; - gameLevel->renderLevel3D(gameCanvas, xF16, yF16); + if (canvas != nullptr) { + canvas->setColor(0, 170, 0); // Green track + // Convert from F16 to internal format + cameraXF16 >>= 1; + cameraYF16 >>= 1; + gameLevel->renderLevel3D(canvas, cameraXF16, cameraYF16); } } -void LevelLoader::renderTrackNearestLine(GameCanvas* canvas) +void LevelLoader::renderTrackCenterline(GameCanvas* canvas) { - canvas->setColor(0, 255, 0); + canvas->setColor(0, 255, 0); // Bright green centerline gameLevel->renderTrackNearestGreenLine(canvas); } -void LevelLoader::method_100(int var1, int var2, int var3) +void LevelLoader::updateVisibleSegmentRange(int minXF16, int maxXF16, int centerYF16) { - gameLevel->method_184((var1 + 98304) >> 1, (var2 - 98304) >> 1, var3 >> 1); - var2 >>= 1; - var1 >>= 1; - field_134 = field_134 < gameLevel->pointsCount - 1 ? field_134 : gameLevel->pointsCount - 1; - field_133 = field_133 < 0 ? 0 : field_133; - if (var2 > field_136) { - while (field_134 < gameLevel->pointsCount - 1 && var2 > gameLevel->pointPositions[++field_134][0]) { + // Update level rendering bounds (with margin) + gameLevel->setShadowBoundaries((minXF16 + 98304) >> 1, (maxXF16 - 98304) >> 1, centerYF16 >> 1); + + // Convert bounds from F16 to internal format + maxXF16 >>= 1; + minXF16 >>= 1; + + // Clamp visible segment indices to valid range + visibleSegmentEndIdx = visibleSegmentEndIdx < gameLevel->pointsCount - 1 ? visibleSegmentEndIdx : gameLevel->pointsCount - 1; + visibleSegmentStartIdx = visibleSegmentStartIdx < 0 ? 0 : visibleSegmentStartIdx; + + // Expand visible range based on camera bounds + if (maxXF16 > visibleSegmentEndX) { + // Expand right boundary + while (visibleSegmentEndIdx < gameLevel->pointsCount - 1 && maxXF16 > gameLevel->pointPositions[++visibleSegmentEndIdx][0]) { } - } else if (var1 < field_135) { - while (field_133 > 0 && var1 < gameLevel->pointPositions[--field_133][0]) { + } else if (minXF16 < visibleSegmentStartX) { + // Expand left boundary + while (visibleSegmentStartIdx > 0 && minXF16 < gameLevel->pointPositions[--visibleSegmentStartIdx][0]) { } } else { - while (field_133 < gameLevel->pointsCount && var1 > gameLevel->pointPositions[++field_133][0]) { + // Recalculate visible range from scratch + while (visibleSegmentStartIdx < gameLevel->pointsCount && minXF16 > gameLevel->pointPositions[++visibleSegmentStartIdx][0]) { } - if (field_133 > 0) { - --field_133; + if (visibleSegmentStartIdx > 0) { + --visibleSegmentStartIdx; } - while (field_134 > 0 && var2 < gameLevel->pointPositions[--field_134][0]) { + while (visibleSegmentEndIdx > 0 && maxXF16 < gameLevel->pointPositions[--visibleSegmentEndIdx][0]) { } - field_134 = field_134 + 1 < gameLevel->pointsCount - 1 ? field_134 + 1 : gameLevel->pointsCount - 1; + visibleSegmentEndIdx = visibleSegmentEndIdx + 1 < gameLevel->pointsCount - 1 ? visibleSegmentEndIdx + 1 : gameLevel->pointsCount - 1; } - field_135 = gameLevel->pointPositions[field_133][0]; - field_136 = gameLevel->pointPositions[field_134][0]; + // Cache X positions of visible segment boundaries + visibleSegmentStartX = gameLevel->pointPositions[visibleSegmentStartIdx][0]; + visibleSegmentEndX = gameLevel->pointPositions[visibleSegmentEndIdx][0]; } -int LevelLoader::method_101(TimerOrMotoPartOrMenuElem* var1, int var2) +int LevelLoader::checkSegmentCollisions(PhysicsElemOrMenuItem* obj, int radiusIndex) { - int var16 = 0; - int8_t var17 = 2; - int var18 = var1->xF16 >> 1; - int var19 = var1->yF16 >> 1; + int collisionCount = 0; + int8_t collisionType = 2; // 0=deep, 1=surface, 2=none + int objX = obj->xF16 >> 1; + int objY = obj->yF16 >> 1; if (isEnabledPerspective) { - var19 -= 65536; + objY -= 65536; } - int var20 = 0, var21 = 0; + int accumulatedNormalX = 0, accumulatedNormalY = 0; - for (int var22 = field_133; var22 < field_134; ++var22) { - int var4 = gameLevel->pointPositions[var22][0]; - int var5 = gameLevel->pointPositions[var22][1]; - int var6 = gameLevel->pointPositions[var22 + 1][0]; - int var7; - if ((var7 = gameLevel->pointPositions[var22 + 1][1]) < var5) { - ; + // Check collision with each visible track segment + for (int i = visibleSegmentStartIdx; i < visibleSegmentEndIdx; ++i) { + int segStartX = gameLevel->pointPositions[i][0]; + int segStartY = gameLevel->pointPositions[i][1]; + int segEndX = gameLevel->pointPositions[i + 1][0]; + int segEndY; + if ((segEndY = gameLevel->pointPositions[i + 1][1]) < segStartY) { + ; // No-op, just assignment } - if (var18 - field_123[var2] <= var6 && var18 + field_123[var2] >= var4) { - int var8 = var4 - var6; - int var9 = var5 - var7; - int var10 = (int)((int64_t)var8 * (int64_t)var8 >> 16) + (int)((int64_t)var9 * (int64_t)var9 >> 16); - int var11 = (int)((int64_t)(var18 - var4) * (int64_t)(-var8) >> 16) + (int)((int64_t)(var19 - var5) * (int64_t)(-var9) >> 16); - int var12; - if ((var10 < 0 ? -var10 : var10) >= 3) { - var12 = (int)(((int64_t)var11 << 32) / (int64_t)var10 >> 16); + // Quick bounding box check + if (objX - collisionRadiusSqOuter[radiusIndex] <= segEndX && objX + collisionRadiusSqOuter[radiusIndex] >= segStartX) { + int segDx = segStartX - segEndX; + int segDy = segStartY - segEndY; + int segLengthSq = (int)((int64_t)segDx * (int64_t)segDx >> 16) + (int)((int64_t)segDy * (int64_t)segDy >> 16); + + // Project object onto segment line + int projection = (int)((int64_t)(objX - segStartX) * (int64_t)(-segDx) >> 16) + (int)((int64_t)(objY - segStartY) * (int64_t)(-segDy) >> 16); + int t; // Parametric position on segment (0-65536) + if ((segLengthSq < 0 ? -segLengthSq : segLengthSq) >= 3) { + t = (int)(((int64_t)projection << 32) / (int64_t)segLengthSq >> 16); } else { - var12 = (var11 > 0 ? 1 : -1) * (var10 > 0 ? 1 : -1) * INT_MAX; + t = (projection > 0 ? 1 : -1) * (segLengthSq > 0 ? 1 : -1) * INT_MAX; } - if (var12 < 0) { - var12 = 0; - } - - if (var12 > 65536) { - var12 = 65536; - } - - int var13 = var4 + (int)((int64_t)var12 * (int64_t)(-var8) >> 16); - int var14 = var5 + (int)((int64_t)var12 * (int64_t)(-var9) >> 16); - var8 = var18 - var13; - var9 = var19 - var14; - int8_t var3; - int64_t var23; - if ((var23 = ((int64_t)var8 * (int64_t)var8 >> 16) + ((int64_t)var9 * (int64_t)var9 >> 16)) < (int64_t)field_123[var2]) { - if (var23 >= (int64_t)field_124[var2]) { - var3 = 1; + // Clamp t to [0, 65536] + if (t < 0) + t = 0; + if (t > 65536) + t = 65536; + + // Find closest point on segment + int closestX = segStartX + (int)((int64_t)t * (int64_t)(-segDx) >> 16); + int closestY = segStartY + (int)((int64_t)t * (int64_t)(-segDy) >> 16); + segDx = objX - closestX; + segDy = objY - closestY; + + int8_t localCollisionType; + int64_t distSq; + if ((distSq = ((int64_t)segDx * (int64_t)segDx >> 16) + ((int64_t)segDy * (int64_t)segDy >> 16)) < (int64_t)collisionRadiusSqOuter[radiusIndex]) { + if (distSq >= (int64_t)collisionRadiusSqInner[radiusIndex]) { + localCollisionType = 1; // Surface collision } else { - var3 = 0; + localCollisionType = 0; // Deep collision } } else { - var3 = 2; + localCollisionType = 2; // No collision } - if (var3 == 0 && (int)((int64_t)field_121[var22][0] * (int64_t)var1->field_382 >> 16) + (int)((int64_t)field_121[var22][1] * (int64_t)var1->field_383 >> 16) < 0) { - field_137 = field_121[var22][0]; - field_138 = field_121[var22][1]; + // Check if object is moving towards the segment + int dotProduct = (int)((int64_t)trackSegmentNormals[i][0] * (int64_t)obj->velocityXF16 >> 16) + (int)((int64_t)trackSegmentNormals[i][1] * (int64_t)obj->velocityYF16 >> 16); + + if (localCollisionType == 0 && dotProduct < 0) { + // Deep collision - store normal and return immediately + lastCollisionNormalXF16 = trackSegmentNormals[i][0]; + lastCollisionNormalYF16 = trackSegmentNormals[i][1]; return 0; } - if (var3 == 1 && (int)((int64_t)field_121[var22][0] * (int64_t)var1->field_382 >> 16) + (int)((int64_t)field_121[var22][1] * (int64_t)var1->field_383 >> 16) < 0) { - ++var16; - var17 = 1; - if (var16 == 1) { - var20 = field_121[var22][0]; - var21 = field_121[var22][1]; + if (localCollisionType == 1 && dotProduct < 0) { + // Surface collision - accumulate normals for response + ++collisionCount; + collisionType = 1; + if (collisionCount == 1) { + accumulatedNormalX = trackSegmentNormals[i][0]; + accumulatedNormalY = trackSegmentNormals[i][1]; } else { - var20 += field_121[var22][0]; - var21 += field_121[var22][1]; + accumulatedNormalX += trackSegmentNormals[i][0]; + accumulatedNormalY += trackSegmentNormals[i][1]; } } } } - if (var17 == 1) { - if ((int)((int64_t)var20 * (int64_t)var1->field_382 >> 16) + (int)((int64_t)var21 * (int64_t)var1->field_383 >> 16) >= 0) { - return 2; + if (collisionType == 1) { + // Check if accumulated normal indicates collision response needed + if ((int)((int64_t)accumulatedNormalX * (int64_t)obj->velocityXF16 >> 16) + (int)((int64_t)accumulatedNormalY * (int64_t)obj->velocityYF16 >> 16) >= 0) { + return 2; // No response needed } - field_137 = var20; - field_138 = var21; + lastCollisionNormalXF16 = accumulatedNormalX; + lastCollisionNormalYF16 = accumulatedNormalY; } - return var17; + return collisionType; } diff --git a/src/LevelLoader.h b/src/LevelLoader.h index 66c6912..e75374c 100644 --- a/src/LevelLoader.h +++ b/src/LevelLoader.h @@ -9,62 +9,70 @@ #include "GamePhysics.h" #include "GameCanvas.h" #include "GameLevel.h" -#include "TimerOrMotoPartOrMenuElem.h" +#include "PhysicsElemOrMenuItem.h" #include "utils/FileStream.h" class LevelLoader { private: - std::vector> field_121; - int field_123[3]; - int field_124[3]; - inline static std::vector> levelOffsetInFile = std::vector>(3); + // Track segment normals (array of [nx, ny]) + std::vector> trackSegmentNormals; + // Collision radius² outer threshold + int collisionRadiusSqOuter[3]; + // Collision radius² inner threshold + int collisionRadiusSqInner[3]; + inline static std::vector> trackOffsetInFile = std::vector>(3); - int field_132 = 0; - static int field_133; - static int field_134; - static int field_135; - static int field_136; + // Allocated capacity of trackSegmentNormals array + int trackNormalsCapacity = 0; + // Leftmost visible track point index + static int visibleSegmentStartIdx; + // Rightmost visible track point index + static int visibleSegmentEndIdx; + // Cached X position of visibleSegmentStartIdx + static int visibleSegmentStartX; + // Cached X position of visibleSegmentEndIdx + static int visibleSegmentEndX; FileStream* levelFileStream; void loadLevels(); public: - static const int field_114; - static const int field_115; - static const int field_116; - static const int field_117; - static const int field_118; static bool isEnabledPerspective; static bool isEnabledShadows; GameLevel* gameLevel = nullptr; - int field_125 = 0; - int field_126 = -1; - std::vector> levelNames = std::vector>(3); - int field_129; - int field_130; - int field_131; - int field_137; - int field_138; + int currentLevel = 0; + int currentTrack = -1; + std::vector> trackNames = std::vector>(3); + // Start position X in F16 format (startPosX << 1) + int cachedStartPosXF16; + // Start position Y in F16 format (startPosY << 1) + int cachedStartPosYF16; + // Minimum X among track points (excluding first/last) + int trackMinX; + int lastCollisionNormalXF16; + int lastCollisionNormalYF16; LevelLoader(const std::filesystem::path& mrgFilePath); ~LevelLoader(); std::string getName(int league, int level); - void method_87(); - int method_88(int var1, int var2); - void method_89(int var1, int var2); + void loadCurrentTrack(); + int loadTrack(int league, int track); + void seekAndLoadTrackData(int league, int track); - void method_90(int var1); - int method_91(); - int method_92(); - int method_93(); - int method_94(); - int method_95(int var1); - void method_96(GameLevel* gameLevel); - void setMinMaxX(int minX, int maxX); - void renderLevel3D(GameCanvas* gameCanvas, int xF16, int yF16); - void renderTrackNearestLine(GameCanvas* canvas); - void method_100(int var1, int var2, int var3); - int method_101(TimerOrMotoPartOrMenuElem* var1, int var2); + // Caches start position coordinates (xF16, yF16) + void cacheStartPosition(); + int getFinishFlagX(); + int getStartFlagX(); + int getStartPosX(); + int getStartPosY(); + // Returns progress ratio (0-65536, F16) + int getTrackProgressRatio(int xF16); + void precomputeTrackGeometry(GameLevel* level); + void setLevelBounds(int minX, int maxX); + void renderTrack3D(GameCanvas* canvas, int cameraXF16, int cameraYF16); + void renderTrackCenterline(GameCanvas* canvas); + void updateVisibleSegmentRange(int minXF16, int maxXF16, int centerYF16); + int checkSegmentCollisions(PhysicsElemOrMenuItem* obj, int radiusIndex); }; diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index caff4e0..b915d46 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -11,437 +11,441 @@ #include "SettingsStringRender.h" #include "utils/Time.h" - -MenuManager::MenuManager(Micro* var1) +MenuManager::MenuManager(Micro* micro) { - micro = var1; - field_376 = std::make_unique("", var1); + this->micro = micro; + helpSeparatorText = std::make_unique("", micro); // Empty separator for help menus } -void MenuManager::initPart(int var1) +void MenuManager::initializePhase(int phase) { - int var4; - switch (var1) { + int levelIndex; + switch (phase) { case 1: - field_341 = defaultInputString; - field_374 = { "On", "Off" }; - field_375 = { "Keyset 1", "Keyset 2", "Keyset 3" }; + // Initialize basic state and open record store + playerName = defaultPlayerName; + onOffOptions = { "On", "Off" }; + inputMethodNames = { "Keyset 1", "Keyset 2", "Keyset 3" }; recordManager = new RecordManager(); - field_337 = -1L; - field_338 = -1; - field_339 = -1; - field_340.clear(); + finishTime = -1L; + finishTimeSeconds = -1; + finishTimeCentiseconds = -1; + finishTimeFormatted.clear(); isRecordStoreOpened = false; - field_278 = std::vector(19); + savedStateBuffer = std::vector(19); - for (int var11 = 0; var11 < 19; ++var11) { - field_278[var11] = -127; + // Initialize buffer to -127 (uninitialized marker) + for (int i = 0; i < 19; ++i) { + savedStateBuffer[i] = -127; } try { recordStore = RecordStore::openRecordStore("GDTRStates", true); isRecordStoreOpened = true; return; - } catch (RecordStoreException& var9) { + } catch (RecordStoreException& e) { isRecordStoreOpened = false; return; } case 2: { - recorcStoreRecordId = -1; + // Load saved state from record store + savedStateRecordId = -1; RecordEnumeration* records; try { records = recordStore->enumerateRecords(nullptr, nullptr, false); - } catch (RecordStoreNotOpenException& var8) { + } catch (RecordStoreNotOpenException& e) { return; } - std::vector var3; + std::vector recordData; if (records->numRecords() > 0) { try { - var3 = records->nextRecord(); + recordData = records->nextRecord(); records->reset(); - recorcStoreRecordId = records->nextRecordId(); - } catch (RecordStoreException& var7) { + savedStateRecordId = records->nextRecordId(); + } catch (RecordStoreException& e) { return; } - if (var3.size() <= 19) { - for (std::size_t i = 0; i < var3.size(); ++i) { - field_278[i] = var3[i]; + if (recordData.size() <= 19) { + for (std::size_t i = 0; i < recordData.size(); ++i) { + savedStateBuffer[i] = recordData[i]; } } records->destroy(); } - var3 = method_216(16, (int8_t)-1); - if (!var3.empty() && var3[0] != -1) { - for (var4 = 0; var4 < 3; ++var4) { - field_341[var4] = var3[var4]; + // Load player name from buffer (indices 16-18) + recordData = loadPlayerName(16, (int8_t)-1); + if (!recordData.empty() && recordData[0] != -1) { + for (levelIndex = 0; levelIndex < 3; ++levelIndex) { + playerName[levelIndex] = recordData[levelIndex]; } } - if (field_341[0] == 82 && field_341[1] == 75 && field_341[2] == 69) { - availableLeagues = 3; - field_344 = 2; - field_342[0] = (int8_t)(micro->levelLoader->levelNames[0].size() - 1); - field_342[1] = (int8_t)(micro->levelLoader->levelNames[1].size() - 1); - field_342[2] = (int8_t)(micro->levelLoader->levelNames[2].size() - 1); + // Cheat code detection: "RKE" unlocks all + if (playerName[0] == 82 && playerName[1] == 75 && playerName[2] == 69) { + maxAvailableLeagues = 3; // All 4 leagues + maxAvailableLevels = 2; // Cheat mode (unlocks all levels) + maxUnlockedTracksPerLevel[0] = (int8_t)(micro->levelLoader->trackNames[0].size() - 1); + maxUnlockedTracksPerLevel[1] = (int8_t)(micro->levelLoader->trackNames[1].size() - 1); + maxUnlockedTracksPerLevel[2] = (int8_t)(micro->levelLoader->trackNames[2].size() - 1); return; } - availableLeagues = 0; - field_344 = 1; - field_342[0] = 0; - field_342[1] = 0; - field_342[2] = -1; + // Default progression state + maxAvailableLeagues = 0; + maxAvailableLevels = 1; + maxUnlockedTracksPerLevel[0] = 0; + maxUnlockedTracksPerLevel[1] = 0; + maxUnlockedTracksPerLevel[2] = -1; } return; case 3: - isDisablePerspective = method_217(0, isDisablePerspective); - isDisabledShadows = method_217(1, isDisabledShadows); - isDisabledDriverSprite = method_217(2, isDisabledDriverSprite); - isDisabledBikeSprite = method_217(3, isDisabledBikeSprite); - field_367 = method_217(14, field_367); - isDisableLookAhead = method_217(4, isDisableLookAhead); - field_369 = method_217(11, field_369); - field_370 = method_217(10, field_370); - field_371 = method_217(12, field_371); - field_373 = method_217(15, field_373); - field_354 = field_370; - field_355 = field_369; - - if (field_341[0] != 82 || field_341[1] != 75 || field_341[2] != 69) { - availableLeagues = method_217(5, availableLeagues); - field_344 = method_217(6, field_344); - - for (var4 = 0; var4 < 3; ++var4) { - field_342[var4] = method_217(7 + var4, field_342[var4]); + // Apply settings from saved state + perspectiveDisabled = loadSavedStateValue(0, perspectiveDisabled); + shadowsDisabled = loadSavedStateValue(1, shadowsDisabled); + driverSpriteDisabled = loadSavedStateValue(2, driverSpriteDisabled); + bikeSpriteDisabled = loadSavedStateValue(3, bikeSpriteDisabled); + inputMethod = loadSavedStateValue(14, inputMethod); + lookAheadDisabled = loadSavedStateValue(4, lookAheadDisabled); + lastSelectedTrack = loadSavedStateValue(11, lastSelectedTrack); + lastSelectedLevel = loadSavedStateValue(10, lastSelectedLevel); + lastSelectedLeague = loadSavedStateValue(12, lastSelectedLeague); + unknownSetting15 = loadSavedStateValue(15, unknownSetting15); + savedLevelBeforeMenu = lastSelectedLevel; + savedTrackBeforeMenu = lastSelectedTrack; + + // Skip progression loading for cheat code + if (playerName[0] != 82 || playerName[1] != 75 || playerName[2] != 69) { + maxAvailableLeagues = loadSavedStateValue(5, maxAvailableLeagues); + maxAvailableLevels = loadSavedStateValue(6, maxAvailableLevels); + + for (levelIndex = 0; levelIndex < 3; ++levelIndex) { + maxUnlockedTracksPerLevel[levelIndex] = loadSavedStateValue(7 + levelIndex, maxUnlockedTracksPerLevel[levelIndex]); } } try { - field_345.at(field_370) = field_369; - } catch (std::exception& var6) { - field_370 = 0; - field_369 = 0; - field_345[field_370] = field_369; + lastSelectedTrackPerLevel.at(lastSelectedLevel) = lastSelectedTrack; + } catch (std::exception& e) { + lastSelectedLevel = 0; + lastSelectedTrack = 0; + lastSelectedTrackPerLevel[lastSelectedLevel] = lastSelectedTrack; } - LevelLoader::isEnabledPerspective = isDisablePerspective == 0; - LevelLoader::isEnabledShadows = isDisabledShadows == 0; - micro->gamePhysics->setEnableLookAhead(isDisableLookAhead == 0); - micro->gameCanvas->method_163(field_367); - micro->gameCanvas->method_124(field_372 == 0); - leagueNamesAll4 = { "100cc", "175cc", "220cc", "325cc" }; - levelNames = micro->levelLoader->levelNames; - if (availableLeagues < 3) { + // Apply graphics and physics settings + LevelLoader::isEnabledPerspective = perspectiveDisabled == 0; + LevelLoader::isEnabledShadows = shadowsDisabled == 0; + micro->gamePhysics->setEnableLookAhead(lookAheadDisabled == 0); + micro->gameCanvas->setInputConfigIndex(inputMethod); + micro->gameCanvas->setInputConfigEnabled(unknownGraphicsSetting == 0); // unknownGraphicsSetting never read + allLeagueNames = { "100cc", "175cc", "220cc", "325cc" }; + trackNamesByLevel = micro->levelLoader->trackNames; + if (maxAvailableLeagues < 3) { this->leagueNames = { "100cc", "175cc", "220cc" }; } else { - this->leagueNames = leagueNamesAll4; + this->leagueNames = allLeagueNames; } - field_360 = field_371; + highscoreLeagueViewIndex = lastSelectedLeague; return; case 4: { - gameMenuMain = new GameMenu("Main", micro, nullptr); - gameMenuPlay = new GameMenu("Play", micro, gameMenuMain); - gameMenuOptions = new GameMenu("Options", micro, gameMenuMain); - gameMenuAbout = new GameMenu("About", micro, gameMenuMain); - gameMenuHelp = new GameMenu("Help", micro, gameMenuMain); - settingStringBack = new SettingsStringRender("Back", 0, this, std::vector(), false, micro, gameMenuMain, true); - settingStringGoToMain = new SettingsStringRender("Go to Main", 0, this, std::vector(), false, micro, gameMenuMain, true); - settingStringContinue = new SettingsStringRender("Continue", 0, this, std::vector(), false, micro, gameMenuMain, true); - settingStringPlayMenu = new SettingsStringRender("Play Menu", 0, this, std::vector(), false, micro, gameMenuMain, true); - + // Create main menu screens + mainMenu = new GameMenu("Main", micro, nullptr); + playMenu = new GameMenu("Play", micro, mainMenu); + optionsMenu = new GameMenu("Options", micro, mainMenu); + aboutMenu = new GameMenu("About", micro, mainMenu); + helpMenu = new GameMenu("Help", micro, mainMenu); + backSetting = new SettingsStringRender("Back", 0, this, std::vector(), false, micro, mainMenu, true); + goToMainSetting = new SettingsStringRender("Go to Main", 0, this, std::vector(), false, micro, mainMenu, true); + continueSetting = new SettingsStringRender("Continue", 0, this, std::vector(), false, micro, mainMenu, true); + playMenuSetting = new SettingsStringRender("Play Menu", 0, this, std::vector(), false, micro, mainMenu, true); + std::shared_ptr boldSmallFont = FontStorage::getFont(Font::STYLE_BOLD, Font::SIZE_SMALL); - if (gameMenuAbout->xPos + boldSmallFont->stringWidth("http://www.codebrew.se/") >= getCanvasWidth()) { - textRenderCodeBrewLink = new TextRender("www.codebrew.se", micro); + if (aboutMenu->xPos + boldSmallFont->stringWidth("http://www.codebrew.se/") >= getCanvasWidth()) { + codebrewLinkText = new TextRender("www.codebrew.se", micro); } else { - textRenderCodeBrewLink = new TextRender("http://www.codebrew.se/", micro); + codebrewLinkText = new TextRender("http://www.codebrew.se/", micro); } - textRenderCodeBrewLink->setFont(boldSmallFont); - gameMenuHighscore = new GameMenu("Highscore", micro, gameMenuPlay); - gameMenuFinished = new GameMenu("Finished!", micro, gameMenuPlay); + codebrewLinkText->setFont(boldSmallFont); + highscoreMenu = new GameMenu("Highscore", micro, playMenu); + finishedTrackMenu = new GameMenu("Finished!", micro, playMenu); } return; case 5: - gameMenuIngame = new GameMenu("Ingame", micro, gameMenuPlay); - gameMenuEnterName = new GameMenu("Enter Name", micro, gameMenuFinished, field_341); - gameMenuConfirmClear = new GameMenu("Confirm Clear", micro, gameMenuOptions); - gameMenuConfirmReset = new GameMenu("Confirm Reset", micro, gameMenuConfirmClear); - taskPlayMenu = new TimerOrMotoPartOrMenuElem("Play Menu", gameMenuPlay, this); - taskOptions = new TimerOrMotoPartOrMenuElem("Options", gameMenuOptions, this); - taskHelp = new TimerOrMotoPartOrMenuElem("Help", gameMenuHelp, this); - taskAbout = new TimerOrMotoPartOrMenuElem("About", gameMenuAbout, this); - settingStringExitGame = new SettingsStringRender("Exit Game", 0, this, std::vector(), false, micro, gameMenuMain, true); - gameMenuMain->addMenuElement(taskPlayMenu); - gameMenuMain->addMenuElement(taskOptions); - gameMenuMain->addMenuElement(taskHelp); - gameMenuMain->addMenuElement(taskAbout); - gameMenuMain->addMenuElement(settingStringExitGame); - settingStringLevel = new SettingsStringRender("Level", field_370, this, field_361, false, micro, gameMenuPlay, false); - settingsStringTrack = new SettingsStringRender("Track", field_345[field_370], this, levelNames[field_370], false, micro, gameMenuPlay, false); - settingsStringLeague = new SettingsStringRender("League", field_371, this, leagueNames, false, micro, gameMenuPlay, false); + // Create ingame and confirmation menus + ingameMenu = new GameMenu("Ingame", micro, playMenu); + enterNameMenu = new GameMenu("Enter Name", micro, finishedTrackMenu, playerName); + confirmClearHighscoresMenu = new GameMenu("Confirm Clear", micro, optionsMenu); + confirmFullResetMenu = new GameMenu("Confirm Reset", micro, confirmClearHighscoresMenu); + playMenuTask = new PhysicsElemOrMenuItem("Play Menu", playMenu, this); + optionsTask = new PhysicsElemOrMenuItem("Options", optionsMenu, this); + helpTask = new PhysicsElemOrMenuItem("Help", helpMenu, this); + aboutTask = new PhysicsElemOrMenuItem("About", aboutMenu, this); + exitGameSetting = new SettingsStringRender("Exit Game", 0, this, std::vector(), false, micro, mainMenu, true); + mainMenu->addMenuElement(playMenuTask); + mainMenu->addMenuElement(optionsTask); + mainMenu->addMenuElement(helpTask); + mainMenu->addMenuElement(aboutTask); + mainMenu->addMenuElement(exitGameSetting); + levelSetting = new SettingsStringRender("Level", lastSelectedLevel, this, levelNames, false, micro, playMenu, false); + trackSetting = new SettingsStringRender("Track", lastSelectedTrackPerLevel[lastSelectedLevel], this, trackNamesByLevel[lastSelectedLevel], false, micro, playMenu, false); + leagueSetting = new SettingsStringRender("League", lastSelectedLeague, this, leagueNames, false, micro, playMenu, false); try { - settingsStringTrack->setAvailableOptions(field_342[field_370]); - } catch (std::exception& var5) { - settingsStringTrack->setAvailableOptions(0); + trackSetting->setAvailableOptions(maxUnlockedTracksPerLevel[lastSelectedLevel]); + } catch (std::exception& e) { + trackSetting->setAvailableOptions(0); } - settingStringLevel->setAvailableOptions(field_344); - settingsStringLeague->setAvailableOptions(availableLeagues); - gameTimerTaskHighscore = new TimerOrMotoPartOrMenuElem("Highscore", gameMenuHighscore, this); - gameMenuHighscore->addMenuElement(settingStringBack); - taskStart = new SettingsStringRender("Start>", 0, this, std::vector(), false, micro, gameMenuMain, true); - gameMenuPlay->addMenuElement(taskStart); - gameMenuPlay->addMenuElement(settingStringLevel); - gameMenuPlay->addMenuElement(settingsStringTrack); - gameMenuPlay->addMenuElement(settingsStringLeague); - gameMenuPlay->addMenuElement(gameTimerTaskHighscore); - gameMenuPlay->addMenuElement(settingStringGoToMain); - - perspectiveSetting = new SettingsStringRender("Perspective", isDisablePerspective, this, field_374, true, micro, gameMenuOptions, false); - shadowsSetting = new SettingsStringRender("Shadows", isDisabledShadows, this, field_374, true, micro, gameMenuOptions, false); - driverSpriteSetting = new SettingsStringRender("Driver sprite", isDisabledDriverSprite, this, field_374, true, micro, gameMenuOptions, false); - bikeSpriteSetting = new SettingsStringRender("Bike sprite", isDisabledBikeSprite, this, field_374, true, micro, gameMenuOptions, false); - inputSetting = new SettingsStringRender("Input", field_367, this, field_375, false, micro, gameMenuOptions, false); - lookAheadSetting = new SettingsStringRender("Look ahead", isDisableLookAhead, this, field_374, true, micro, gameMenuOptions, false); - clearHighscoreSetting = new TimerOrMotoPartOrMenuElem("Clear highscore", gameMenuConfirmClear, this); + levelSetting->setAvailableOptions(maxAvailableLevels); + leagueSetting->setAvailableOptions(maxAvailableLeagues); + highscoreTask = new PhysicsElemOrMenuItem("Highscore", highscoreMenu, this); + highscoreMenu->addMenuElement(backSetting); + startTask = new SettingsStringRender("Start>", 0, this, std::vector(), false, micro, mainMenu, true); + playMenu->addMenuElement(startTask); + playMenu->addMenuElement(levelSetting); + playMenu->addMenuElement(trackSetting); + playMenu->addMenuElement(leagueSetting); + playMenu->addMenuElement(highscoreTask); + playMenu->addMenuElement(goToMainSetting); + + perspectiveSetting = new SettingsStringRender("Perspective", perspectiveDisabled, this, onOffOptions, true, micro, optionsMenu, false); + shadowsSetting = new SettingsStringRender("Shadows", shadowsDisabled, this, onOffOptions, true, micro, optionsMenu, false); + driverSpriteSetting = new SettingsStringRender("Driver sprite", driverSpriteDisabled, this, onOffOptions, true, micro, optionsMenu, false); + bikeSpriteSetting = new SettingsStringRender("Bike sprite", bikeSpriteDisabled, this, onOffOptions, true, micro, optionsMenu, false); + inputSetting = new SettingsStringRender("Input", inputMethod, this, inputMethodNames, false, micro, optionsMenu, false); + lookAheadSetting = new SettingsStringRender("Look ahead", lookAheadDisabled, this, onOffOptions, true, micro, optionsMenu, false); + clearHighscoreTask = new PhysicsElemOrMenuItem("Clear highscore", confirmClearHighscoresMenu, this); return; case 6: - gameMenuOptions->addMenuElement(perspectiveSetting); - gameMenuOptions->addMenuElement(shadowsSetting); - gameMenuOptions->addMenuElement(driverSpriteSetting); - gameMenuOptions->addMenuElement(bikeSpriteSetting); - gameMenuOptions->addMenuElement(inputSetting); - gameMenuOptions->addMenuElement(lookAheadSetting); - gameMenuOptions->addMenuElement(clearHighscoreSetting); - gameMenuOptions->addMenuElement(settingStringBack); - field_315 = new SettingsStringRender("No", 0, this, std::vector(), false, micro, gameMenuMain, true); - field_314 = new SettingsStringRender("Yes", 0, this, std::vector(), false, micro, gameMenuMain, true); - field_313 = new TimerOrMotoPartOrMenuElem("Full Reset", gameMenuConfirmReset, this); - addTextRender(gameMenuConfirmClear, "Clearing the highscores can not be undone. It will remove all the registered times on all tracks."); - addTextRender(gameMenuConfirmClear, "Would you like to clear the highscores?"); - gameMenuConfirmClear->addMenuElement(field_315); - gameMenuConfirmClear->addMenuElement(field_314); - gameMenuConfirmClear->addMenuElement(field_313); - addTextRender(gameMenuConfirmReset, "A full reset can not be undone. It will relock all tracks and leagues and clear back all settings to default. A full reset will exit the application."); - addTextRender(gameMenuConfirmReset, "Would you like to do a full reset?"); - gameMenuConfirmReset->addMenuElement(field_315); - gameMenuConfirmReset->addMenuElement(field_314); - field_317 = new GameMenu("Objective", micro, gameMenuHelp); - field_318 = new TimerOrMotoPartOrMenuElem("Objective", field_317, this); - addTextRender(field_317, "Race to the finish line as fast as you can without crashing. By leaning forward and backward you can adjust the rotation of your bike. By landing on both wheels after jumping, your bike won't crash as easily. Beware, the levels tend to get harder and harder..."); - field_317->addMenuElement(settingStringBack); - gameMenuHelp->addMenuElement(field_318); - field_319 = new GameMenu("Keys", micro, gameMenuHelp); - field_320 = new TimerOrMotoPartOrMenuElem("Keys", field_319, this); - addTextRender(field_319, "- " + field_375[0] + " -"); - addTextRender(field_319, "UP accelerates, DOWN brakes, RIGHT leans forward and LEFT leans backward. 1 accelerates and leans backward. 3 accelerates and leans forward. 7 brakes and leans backward. 9 brakes and leans forward."); - field_319->addMenuElement(field_376.get()); - addTextRender(field_319, "- " + field_375[1] + " -"); - addTextRender(field_319, "1 accelerates, 4 brakes, 6 leans forward and 5 leans backward."); - field_319->addMenuElement(field_376.get()); - addTextRender(field_319, "- " + field_375[2] + " -"); - addTextRender(field_319, "3 accelerates, 6 brakes, 5 leans forward and 4 leans backward."); - field_319->addMenuElement(settingStringBack); - gameMenuHelp->addMenuElement(field_320); - field_321 = new GameMenu("Unlocking", micro, gameMenuHelp); - field_322 = new TimerOrMotoPartOrMenuElem("Unlocking", field_321, this); - addTextRender(field_321, "By completing the easier levels, new levels will be unlocked. You will also gain access to higher leagues where more advanced bikes with different characteristics are available."); - field_321->addMenuElement(settingStringBack); - gameMenuHelp->addMenuElement(field_322); - gameMenuOptionsHighscoreDescription = new GameMenu("Highscore", micro, gameMenuHelp); - taskHighscore = new TimerOrMotoPartOrMenuElem("Highscore", gameMenuOptionsHighscoreDescription, this); - addTextRender(gameMenuOptionsHighscoreDescription, "The three best times on every track are saved for each league. When beating a time on a track you will be asked to enter your name. The highscores can be viewed from the Play Menu. By pressing left and right in the highscore view you can view the highscore for a specific league. The highscore can be cleared from the options menu."); - gameMenuOptionsHighscoreDescription->addMenuElement(settingStringBack); - gameMenuHelp->addMenuElement(taskHighscore); + // Populate options menu and create help menus + optionsMenu->addMenuElement(perspectiveSetting); + optionsMenu->addMenuElement(shadowsSetting); + optionsMenu->addMenuElement(driverSpriteSetting); + optionsMenu->addMenuElement(bikeSpriteSetting); + optionsMenu->addMenuElement(inputSetting); + optionsMenu->addMenuElement(lookAheadSetting); + optionsMenu->addMenuElement(clearHighscoreTask); + optionsMenu->addMenuElement(backSetting); + confirmNoSetting = new SettingsStringRender("No", 0, this, std::vector(), false, micro, mainMenu, true); + confirmYesSetting = new SettingsStringRender("Yes", 0, this, std::vector(), false, micro, mainMenu, true); + fullResetTask = new PhysicsElemOrMenuItem("Full Reset", confirmFullResetMenu, this); + addMultilineTextToMenu(confirmClearHighscoresMenu, "Clearing the highscores can not be undone. It will remove all the registered times on all tracks."); + addMultilineTextToMenu(confirmClearHighscoresMenu, "Would you like to clear the highscores?"); + confirmClearHighscoresMenu->addMenuElement(confirmNoSetting); + confirmClearHighscoresMenu->addMenuElement(confirmYesSetting); + confirmClearHighscoresMenu->addMenuElement(fullResetTask); + addMultilineTextToMenu(confirmFullResetMenu, "A full reset can not be undone. It will relock all tracks and leagues and clear back all settings to default. A full reset will exit the application."); + addMultilineTextToMenu(confirmFullResetMenu, "Would you like to do a full reset?"); + confirmFullResetMenu->addMenuElement(confirmNoSetting); + confirmFullResetMenu->addMenuElement(confirmYesSetting); + helpObjectiveMenu = new GameMenu("Objective", micro, helpMenu); + helpObjectiveTask = new PhysicsElemOrMenuItem("Objective", helpObjectiveMenu, this); + addMultilineTextToMenu(helpObjectiveMenu, "Race to the finish line as fast as you can without crashing. By leaning forward and backward you can adjust the rotation of your bike. By landing on both wheels after jumping, your bike won't crash as easily. Beware, the levels tend to get harder and harder..."); + helpObjectiveMenu->addMenuElement(backSetting); + helpMenu->addMenuElement(helpObjectiveTask); + helpKeysMenu = new GameMenu("Keys", micro, helpMenu); + helpKeysTask = new PhysicsElemOrMenuItem("Keys", helpKeysMenu, this); + addMultilineTextToMenu(helpKeysMenu, "- " + inputMethodNames[0] + " -"); + addMultilineTextToMenu(helpKeysMenu, "UP accelerates, DOWN brakes, RIGHT leans forward and LEFT leans backward. 1 accelerates and leans backward. 3 accelerates and leans forward. 7 brakes and leans backward. 9 brakes and leans forward."); + helpKeysMenu->addMenuElement(helpSeparatorText.get()); + addMultilineTextToMenu(helpKeysMenu, "- " + inputMethodNames[1] + " -"); + addMultilineTextToMenu(helpKeysMenu, "1 accelerates, 4 brakes, 6 leans forward and 5 leans backward."); + helpKeysMenu->addMenuElement(helpSeparatorText.get()); + addMultilineTextToMenu(helpKeysMenu, "- " + inputMethodNames[2] + " -"); + addMultilineTextToMenu(helpKeysMenu, "3 accelerates, 6 brakes, 5 leans forward and 4 leans backward."); + helpKeysMenu->addMenuElement(backSetting); + helpMenu->addMenuElement(helpKeysTask); + helpUnlockingMenu = new GameMenu("Unlocking", micro, helpMenu); + helpUnlockingTask = new PhysicsElemOrMenuItem("Unlocking", helpUnlockingMenu, this); + addMultilineTextToMenu(helpUnlockingMenu, "By completing the easier levels, new levels will be unlocked. You will also gain access to higher leagues where more advanced bikes with different characteristics are available."); + helpUnlockingMenu->addMenuElement(backSetting); + helpMenu->addMenuElement(helpUnlockingTask); + helpHighscoreDescriptionMenu = new GameMenu("Highscore", micro, helpMenu); + helpHighscoreTask = new PhysicsElemOrMenuItem("Highscore", helpHighscoreDescriptionMenu, this); + addMultilineTextToMenu(helpHighscoreDescriptionMenu, "The three best times on every track are saved for each league. When beating a time on a track you will be asked to enter your name. The highscores can be viewed from the Play Menu. By pressing left and right in the highscore view you can view the highscore for a specific league. The highscore can be cleared from the options menu."); + helpHighscoreDescriptionMenu->addMenuElement(backSetting); + helpMenu->addMenuElement(helpHighscoreTask); return; case 7: - gameMenuOptions2 = new GameMenu("Options", micro, gameMenuHelp); - field_326 = new TimerOrMotoPartOrMenuElem("Options", gameMenuOptions2, this); - - addTextRender(gameMenuOptions2, "Perspective: On/Off"); - addTextRender(gameMenuOptions2, "Default: . Turns on and off the perspective view of the tracks."); - gameMenuOptions2->addMenuElement(field_376.get()); - addTextRender(gameMenuOptions2, "Shadows: On/Off"); - addTextRender(gameMenuOptions2, "Default: . Turns on and off the shadows."); - gameMenuOptions2->addMenuElement(field_376.get()); - addTextRender(gameMenuOptions2, "Driver Sprite: On / Off"); - addTextRender(gameMenuOptions2, "Default: . uses a texture for the driver. uses line graphics."); - gameMenuOptions2->addMenuElement(field_376.get()); - addTextRender(gameMenuOptions2, "Bike Sprite: On / Off"); - addTextRender(gameMenuOptions2, "Default: . uses a texture for the bike. uses line graphics."); - gameMenuOptions2->addMenuElement(field_376.get()); - addTextRender(gameMenuOptions2, "Input: Keyset 1,2,3 "); - addTextRender(gameMenuOptions2, "Default: <1>. Determines which type of input should be used when playing. See \"Keys\" in the help menu for more info."); - gameMenuOptions2->addMenuElement(field_376.get()); - addTextRender(gameMenuOptions2, "Look ahead: On/Off"); - addTextRender(gameMenuOptions2, "Default: . Turns on and off smart camera movement."); - gameMenuOptions2->addMenuElement(field_376.get()); - addTextRender(gameMenuOptions2, "Clear highscore"); - addTextRender(gameMenuOptions2, "Lets you clear the highscores. Here you can also do a \"Full Reset\" which will reset the game to original state (clear settings, highscores, unlocked levels and leagues)."); - gameMenuOptions2->addMenuElement(field_376.get()); - gameMenuOptions2->addMenuElement(settingStringBack); - gameMenuHelp->addMenuElement(field_326); - gameMenuHelp->addMenuElement(settingStringBack); - addTextRender(gameMenuAbout, "\"Gravity Defied - Trial Racing\" v1.0 by Codebrew Software © 2004."); - addTextRender(gameMenuAbout, "brought 2 you by pascha. For information visit:"); - gameMenuAbout->addMenuElement(textRenderCodeBrewLink); - gameMenuAbout->addMenuElement(settingStringBack); - field_334 = new SettingsStringRender("Track: " + micro->levelLoader->getName(0, 1), 0, this, std::vector(), false, micro, gameMenuMain, true); - field_333 = new SettingsStringRender("Restart: " + micro->levelLoader->getName(0, 0), 0, this, std::vector(), false, micro, gameMenuMain, true); - gameMenuIngame->addMenuElement(settingStringContinue); - gameMenuIngame->addMenuElement(field_333); - gameMenuIngame->addMenuElement(taskOptions); - gameMenuIngame->addMenuElement(taskHelp); - gameMenuIngame->addMenuElement(settingStringPlayMenu); - field_335 = new SettingsStringRender("Ok", 0, this, std::vector(), false, micro, gameMenuMain, true); - field_336 = new SettingsStringRender("Name - " + std::string(field_341), 0, this, std::vector(), false, micro, gameMenuMain, true); - commandOk = new Command("Ok", 4, 1); - commandBack = new Command("Back", 2, 1); - method_1(gameMenuMain, false); - - rasterImage = std::make_unique("raster.png"); + // Create help options description menu + helpOptionsDescriptionMenu = new GameMenu("Options", micro, helpMenu); + helpOptionsTask = new PhysicsElemOrMenuItem("Options", helpOptionsDescriptionMenu, this); + + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Perspective: On/Off"); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Default: . Turns on and off the perspective view of the tracks."); + helpOptionsDescriptionMenu->addMenuElement(helpSeparatorText.get()); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Shadows: On/Off"); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Default: . Turns on and off the shadows."); + helpOptionsDescriptionMenu->addMenuElement(helpSeparatorText.get()); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Driver Sprite: On / Off"); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Default: . uses a texture for the driver. uses line graphics."); + helpOptionsDescriptionMenu->addMenuElement(helpSeparatorText.get()); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Bike Sprite: On / Off"); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Default: . uses a texture for the bike. uses line graphics."); + helpOptionsDescriptionMenu->addMenuElement(helpSeparatorText.get()); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Input: Keyset 1,2,3 "); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Default: <1>. Determines which type of input should be used when playing. See \"Keys\" in the help menu for more info."); + helpOptionsDescriptionMenu->addMenuElement(helpSeparatorText.get()); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Look ahead: On/Off"); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Default: . Turns on and off smart camera movement."); + helpOptionsDescriptionMenu->addMenuElement(helpSeparatorText.get()); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Clear highscore"); + addMultilineTextToMenu(helpOptionsDescriptionMenu, "Lets you clear the highscores. Here you can also do a \"Full Reset\" which will reset the game to original state (clear settings, highscores, unlocked levels and leagues)."); + helpOptionsDescriptionMenu->addMenuElement(helpSeparatorText.get()); + helpOptionsDescriptionMenu->addMenuElement(backSetting); + helpMenu->addMenuElement(helpOptionsTask); + helpMenu->addMenuElement(backSetting); + addMultilineTextToMenu(aboutMenu, "\"Gravity Defied - Trial Racing\" v1.0 by Codebrew Software © 2004."); + addMultilineTextToMenu(aboutMenu, "brought 2 you by pascha. For information visit:"); + aboutMenu->addMenuElement(codebrewLinkText); + aboutMenu->addMenuElement(backSetting); + nextTrackSetting = new SettingsStringRender("Track: " + micro->levelLoader->getName(0, 1), 0, this, std::vector(), false, micro, mainMenu, true); + restartTrackSetting = new SettingsStringRender("Restart: " + micro->levelLoader->getName(0, 0), 0, this, std::vector(), false, micro, mainMenu, true); + ingameMenu->addMenuElement(continueSetting); + ingameMenu->addMenuElement(restartTrackSetting); + ingameMenu->addMenuElement(optionsTask); + ingameMenu->addMenuElement(helpTask); + ingameMenu->addMenuElement(playMenuSetting); + okSetting = new SettingsStringRender("Ok", 0, this, std::vector(), false, micro, mainMenu, true); + nameEntrySetting = new SettingsStringRender("Name - " + std::string(playerName), 0, this, std::vector(), false, micro, mainMenu, true); + okCommand = new Command("Ok", 4, 1); + backCommand = new Command("Back", 2, 1); + switchToMenu(mainMenu, false); + + menuBackgroundImage = std::make_unique("raster.png"); default: break; } } -void MenuManager::addTextRender(GameMenu* gameMenu, std::string text) +void MenuManager::addMultilineTextToMenu(GameMenu* menu, const std::string& text) { - std::vector var3 = TextRender::makeMultilineTextRenders(text, micro); + std::vector textRenders = TextRender::makeMultilineTextRenders(text, micro); - for (std::size_t var4 = 0; var4 < var3.size(); ++var4) { - gameMenu->addMenuElement(var3[var4]); + for (std::size_t i = 0; i < textRenders.size(); ++i) { + menu->addMenuElement(textRenders[i]); } } -int MenuManager::getCurrentLevel() -{ - return settingStringLevel->getCurrentOptionPos(); -} - -int MenuManager::getCurrentTrack() +bool MenuManager::consumeShouldStartRaceFlag() { - return settingsStringTrack->getCurrentOptionPos(); -} - -bool MenuManager::method_196() -{ - if (field_357) { - field_357 = false; + if (shouldStartRaceImmediately) { + shouldStartRaceImmediately = false; return true; } else { return false; } } -void MenuManager::method_197() +void MenuManager::saveHighscoreAndShowResults() { - recordManager->method_17(settingsStringLeague->getCurrentOptionPos(), field_341, field_337); + // Save the record and update progression + recordManager->addRecord(leagueSetting->getCurrentOptionPos(), playerName, finishTime); recordManager->writeRecordInfo(); - field_356 = false; - gameMenuFinished->clearVector(); - gameMenuFinished->addMenuElement(new TextRender("Time: " + field_340, micro)); - std::vector var1 = recordManager->getRecordDescription(settingsStringLeague->getCurrentOptionPos()); - - for (std::size_t var2 = 0; var2 < var1.size(); ++var2) { - if (var1[var2] != "") { - gameMenuFinished->addMenuElement(new TextRender(std::to_string(var2 + 1) + "." + var1[var2], micro)); + isAllTracksCompletedAtLevel = false; + finishedTrackMenu->clearVector(); + finishedTrackMenu->addMenuElement(new TextRender("Time: " + finishTimeFormatted, micro)); + std::vector recordDescriptions = recordManager->getRecordDescription(leagueSetting->getCurrentOptionPos()); + + for (std::size_t i = 0; i < recordDescriptions.size(); ++i) { + if (recordDescriptions[i] != "") { + finishedTrackMenu->addMenuElement(new TextRender(std::to_string(i + 1) + "." + recordDescriptions[i], micro)); } } recordManager->closeRecordStore(); - int8_t availableLeagues = -1; - if (settingsStringTrack->getMaxAvailableOptionPos() >= settingsStringTrack->getCurrentOptionPos()) { - settingsStringTrack->setAvailableOptions(settingsStringTrack->getCurrentOptionPos() + 1 < field_342[settingStringLevel->getCurrentOptionPos()] ? field_342[settingStringLevel->getCurrentOptionPos()] : settingsStringTrack->getCurrentOptionPos() + 1); - field_342[settingStringLevel->getCurrentOptionPos()] = (int8_t)settingsStringTrack->getMaxAvailableOptionPos() < field_342[settingStringLevel->getCurrentOptionPos()] ? field_342[settingStringLevel->getCurrentOptionPos()] : (int8_t)settingsStringTrack->getMaxAvailableOptionPos(); + int8_t newlyUnlockedLeague = -1; + if (trackSetting->getMaxAvailableOptionPos() >= trackSetting->getCurrentOptionPos()) { + trackSetting->setAvailableOptions(trackSetting->getCurrentOptionPos() + 1 < maxUnlockedTracksPerLevel[levelSetting->getCurrentOptionPos()] ? maxUnlockedTracksPerLevel[levelSetting->getCurrentOptionPos()] : trackSetting->getCurrentOptionPos() + 1); + maxUnlockedTracksPerLevel[levelSetting->getCurrentOptionPos()] = (int8_t)trackSetting->getMaxAvailableOptionPos() < maxUnlockedTracksPerLevel[levelSetting->getCurrentOptionPos()] ? maxUnlockedTracksPerLevel[levelSetting->getCurrentOptionPos()] : (int8_t)trackSetting->getMaxAvailableOptionPos(); } - if (settingsStringTrack->getCurrentOptionPos() == settingsStringTrack->getMaxOptionPos()) { - field_356 = true; - switch (settingStringLevel->getCurrentOptionPos()) { + // Check if all tracks at this level are completed + if (trackSetting->getCurrentOptionPos() == trackSetting->getMaxOptionPos()) { + isAllTracksCompletedAtLevel = true; + switch (levelSetting->getCurrentOptionPos()) { case 0: - if (availableLeagues < 1) { - availableLeagues = 1; - availableLeagues = 1; - settingsStringLeague->setAvailableOptions(availableLeagues); + if (newlyUnlockedLeague < 1) { + newlyUnlockedLeague = 1; + newlyUnlockedLeague = 1; + leagueSetting->setAvailableOptions(newlyUnlockedLeague); } break; case 1: - if (availableLeagues < 2) { - availableLeagues = 2; - availableLeagues = 2; - settingsStringLeague->setAvailableOptions(availableLeagues); + if (newlyUnlockedLeague < 2) { + newlyUnlockedLeague = 2; + newlyUnlockedLeague = 2; + leagueSetting->setAvailableOptions(newlyUnlockedLeague); } break; case 2: - if (availableLeagues < 3) { - availableLeagues = 3; - availableLeagues = 3; - settingsStringLeague->setOptionsList(leagueNamesAll4); - leagueNames = leagueNamesAll4; - settingsStringLeague->setAvailableOptions(availableLeagues); + if (newlyUnlockedLeague < 3) { + newlyUnlockedLeague = 3; + newlyUnlockedLeague = 3; + leagueSetting->setOptionsList(allLeagueNames); + leagueNames = allLeagueNames; + leagueSetting->setAvailableOptions(newlyUnlockedLeague); } } - settingStringLevel->setAvailableOptions(settingStringLevel->getMaxAvailableOptionPos() + 1); - if (field_342[settingStringLevel->getMaxAvailableOptionPos()] == -1) { - field_342[settingStringLevel->getMaxAvailableOptionPos()] = 0; + levelSetting->setAvailableOptions(levelSetting->getMaxAvailableOptionPos() + 1); + if (maxUnlockedTracksPerLevel[levelSetting->getMaxAvailableOptionPos()] == -1) { + maxUnlockedTracksPerLevel[levelSetting->getMaxAvailableOptionPos()] = 0; } } - int var3 = getCountOfRecordStoresWithPrefix(settingStringLevel->getCurrentOptionPos()); - addTextRender(gameMenuFinished, var3 + " of " + std::to_string(levelNames[settingStringLevel->getCurrentOptionPos()].size()) + " tracks in " + field_361[settingStringLevel->getCurrentOptionPos()] + " completed."); - if (!field_356) { - field_333->setText("Restart: " + micro->levelLoader->getName(settingStringLevel->getCurrentOptionPos(), settingsStringTrack->getCurrentOptionPos())); - field_334->setText("Next: " + micro->levelLoader->getName(field_354, field_355 + 1)); + int completedTrackCount = countRecordStoresForLevel(levelSetting->getCurrentOptionPos()); + addMultilineTextToMenu(finishedTrackMenu, completedTrackCount + " of " + std::to_string(trackNamesByLevel[levelSetting->getCurrentOptionPos()].size()) + " tracks in " + levelNames[levelSetting->getCurrentOptionPos()] + " completed."); + if (!isAllTracksCompletedAtLevel) { + restartTrackSetting->setText("Restart: " + micro->levelLoader->getName(levelSetting->getCurrentOptionPos(), trackSetting->getCurrentOptionPos())); + nextTrackSetting->setText("Next: " + micro->levelLoader->getName(savedLevelBeforeMenu, savedTrackBeforeMenu + 1)); } else { - if (settingStringLevel->getCurrentOptionPos() < settingStringLevel->getMaxOptionPos()) { - settingStringLevel->setCurentOptionPos(settingStringLevel->getCurrentOptionPos() + 1); - settingsStringTrack->setCurentOptionPos(0); - settingsStringTrack->setAvailableOptions(field_342[settingStringLevel->getCurrentOptionPos()]); + if (levelSetting->getCurrentOptionPos() < levelSetting->getMaxOptionPos()) { + levelSetting->setCurentOptionPos(levelSetting->getCurrentOptionPos() + 1); + trackSetting->setCurentOptionPos(0); + trackSetting->setAvailableOptions(maxUnlockedTracksPerLevel[levelSetting->getCurrentOptionPos()]); } - if (availableLeagues != -1) { - addTextRender(gameMenuFinished, "Congratultions! You have successfully unlocked a new league: " + leagueNames[availableLeagues]); - if (availableLeagues == 3) { - gameMenuFinished->addMenuElement(new TextRender("Enjoy...", micro)); + if (newlyUnlockedLeague != -1) { + addMultilineTextToMenu(finishedTrackMenu, "Congratultions! You have successfully unlocked a new league: " + leagueNames[newlyUnlockedLeague]); + if (newlyUnlockedLeague == 3) { + finishedTrackMenu->addMenuElement(new TextRender("Enjoy...", micro)); } - showAlert("League unlocked", "You have successfully unlocked a new league: " + leagueNames[availableLeagues], nullptr); + showAlert("League unlocked", "You have successfully unlocked a new league: " + leagueNames[newlyUnlockedLeague], nullptr); } else { - bool var4 = true; + bool allTracksCompleted = true; - for (int var5 = 0; var5 < 3; ++var5) { - if (field_342[var5] != static_cast(micro->levelLoader->levelNames[var5].size() - 1)) { - var4 = false; + for (int levelIndex = 0; levelIndex < 3; ++levelIndex) { + if (maxUnlockedTracksPerLevel[levelIndex] != static_cast(micro->levelLoader->trackNames[levelIndex].size() - 1)) { + allTracksCompleted = false; } } - if (!var4) { - addTextRender(gameMenuFinished, "You have completed all tracks at this level."); + if (!allTracksCompleted) { + addMultilineTextToMenu(finishedTrackMenu, "You have completed all tracks at this level."); } } } - if (!field_356) { - gameMenuFinished->addMenuElement(field_334); + if (!isAllTracksCompletedAtLevel) { + finishedTrackMenu->addMenuElement(nextTrackSetting); } - field_333->setText("Restart: " + micro->levelLoader->getName(field_354, field_355)); - gameMenuFinished->addMenuElement(field_333); - gameMenuFinished->addMenuElement(settingStringPlayMenu); - method_1(gameMenuFinished, false); + restartTrackSetting->setText("Restart: " + micro->levelLoader->getName(savedLevelBeforeMenu, savedTrackBeforeMenu)); + finishedTrackMenu->addMenuElement(restartTrackSetting); + finishedTrackMenu->addMenuElement(playMenuSetting); + switchToMenu(finishedTrackMenu, false); } -void MenuManager::repaint() +void MenuManager::requestRepaint() { micro->gameCanvas->repaint(); } @@ -456,151 +460,145 @@ int MenuManager::getCanvasWidth() return micro->gameCanvas->getWidth(); } -void MenuManager::method_201(int var1) +void MenuManager::runMenuLoop(int menuType) { - field_377 = false; - switch (var1) { + isMenuRenderingBlocked = false; + switch (menuType) { + // main menu case 0: - method_1(gameMenuMain, false); + switchToMenu(mainMenu, false); micro->gamePhysics->enableGenerateInputAI(); - field_357 = true; + shouldStartRaceImmediately = true; break; + // in-game menu case 1: - field_354 = settingStringLevel->getCurrentOptionPos(); - field_355 = settingsStringTrack->getCurrentOptionPos(); - field_333->setText("Restart: " + micro->levelLoader->getName(field_354, field_355)); - field_357 = false; - method_1(gameMenuIngame, false); + savedLevelBeforeMenu = levelSetting->getCurrentOptionPos(); + savedTrackBeforeMenu = trackSetting->getCurrentOptionPos(); + restartTrackSetting->setText("Restart: " + micro->levelLoader->getName(savedLevelBeforeMenu, savedTrackBeforeMenu)); + shouldStartRaceImmediately = false; + switchToMenu(ingameMenu, false); break; + // finished track menu case 2: { - field_362 = Time::currentTimeMillis(); - gameMenuFinished->clearVector(); - field_354 = settingStringLevel->getCurrentOptionPos(); - field_355 = settingsStringTrack->getCurrentOptionPos(); - recordManager->method_8(settingStringLevel->getCurrentOptionPos(), settingsStringTrack->getCurrentOptionPos()); - int var2 = recordManager->getPosOfNewRecord(settingsStringLeague->getCurrentOptionPos(), field_337); - field_340 = timeToString(field_337); - if (var2 >= 0 && var2 <= 2) { - TextRender* var3 = new TextRender("", micro); - var3->setDx(GameCanvas::spriteSizeX[5] + 1); - switch (var2) { + menuLoopStartTime = Time::currentTimeMillis(); + finishedTrackMenu->clearVector(); + savedLevelBeforeMenu = levelSetting->getCurrentOptionPos(); + savedTrackBeforeMenu = trackSetting->getCurrentOptionPos(); + recordManager->openRecordStore(levelSetting->getCurrentOptionPos(), trackSetting->getCurrentOptionPos()); + int newRecordPosition = recordManager->getPosOfNewRecord(leagueSetting->getCurrentOptionPos(), finishTime); + finishTimeFormatted = formatTime(finishTime); + if (newRecordPosition >= 0 && newRecordPosition <= 2) { + // New record is in top 3 - show name entry + TextRender* firstPlaceText = new TextRender("", micro); + firstPlaceText->setDx(GameCanvas::spriteSizeX[5] + 1); + switch (newRecordPosition) { case 0: - var3->setText("First place!"); - var3->setDrawSprite(true, 5); + firstPlaceText->setText("First place!"); + firstPlaceText->setDrawSprite(true, 5); break; case 1: - var3->setText("Second place!"); - var3->setDrawSprite(true, 6); + firstPlaceText->setText("Second place!"); + firstPlaceText->setDrawSprite(true, 6); break; case 2: - var3->setText("Third place!"); - var3->setDrawSprite(true, 7); + firstPlaceText->setText("Third place!"); + firstPlaceText->setDrawSprite(true, 7); } - gameMenuFinished->addMenuElement(var3); - TextRender* var4 = new TextRender("" + field_340, micro); - var4->setDx(GameCanvas::spriteSizeX[5] + 1); - gameMenuFinished->addMenuElement(var4); - gameMenuFinished->addMenuElement(field_335); - gameMenuFinished->addMenuElement(field_336); - method_1(gameMenuFinished, false); - field_377 = false; + finishedTrackMenu->addMenuElement(firstPlaceText); + TextRender* timeText = new TextRender("" + finishTimeFormatted, micro); + timeText->setDx(GameCanvas::spriteSizeX[5] + 1); + finishedTrackMenu->addMenuElement(timeText); + finishedTrackMenu->addMenuElement(okSetting); + finishedTrackMenu->addMenuElement(nameEntrySetting); + switchToMenu(finishedTrackMenu, false); + isMenuRenderingBlocked = false; } else { - method_197(); + saveHighscoreAndShowResults(); } } break; default: - method_1(gameMenuMain, false); + switchToMenu(mainMenu, false); break; } - int64_t currentTimeMillis = Time::currentTimeMillis(); + int64_t loopStartTime = Time::currentTimeMillis(); micro->gameCanvas->isDrawingTime = false; - int64_t var6 = 0L; - int8_t var8 = 50; - micro->gamePhysics->method_53(); + int64_t lastFrameTime = 0L; + int8_t targetFrameTimeMs = 50; // 20 FPS target + micro->gamePhysics->captureRenderSnapshot(); micro->gameToMenu(); - while (Micro::isInGameMenu && Micro::field_249 && currentGameMenu != nullptr) { - int64_t var20; + while (Micro::isInGameMenu && Micro::isGameLoopRunning && currentGameMenu != nullptr) { + int64_t currentTime; if (micro->gamePhysics->isGenerateInputAI) { - int var9; - if ((var9 = micro->gamePhysics->updatePhysics()) != 0 && var9 != 4) { - micro->gamePhysics->resetSmth(true); + int physicsResult; + if ((physicsResult = micro->gamePhysics->updatePhysics()) != 0 && physicsResult != 4) { + micro->gamePhysics->resetPhysicsState(true); } - micro->gamePhysics->method_53(); - repaint(); - if ((var20 = Time::currentTimeMillis()) - var6 < (int64_t)var8) { - // try { - // synchronized (field_359) { - // field_359.wait((int64_t) var8 - (var20 - var6) < 1L ? 1L : (int64_t) var8 - (var20 - var6)); - // } - // } catch (InterruptedException var16) { - // } - Time::sleep((int64_t)var8 - (var20 - var6) < 1L ? 1L : (int64_t)var8 - (var20 - var6)); - - var6 = Time::currentTimeMillis(); + micro->gamePhysics->captureRenderSnapshot(); + requestRepaint(); + if ((currentTime = Time::currentTimeMillis()) - lastFrameTime < (int64_t)targetFrameTimeMs) { + // Frame limiting - sleep to maintain 20 FPS + Time::sleep((int64_t)targetFrameTimeMs - (currentTime - lastFrameTime) < 1L ? 1L : (int64_t)targetFrameTimeMs - (currentTime - lastFrameTime)); + + lastFrameTime = Time::currentTimeMillis(); } else { - var6 = var20; + lastFrameTime = currentTime; } } else { - var8 = 50; - if ((var20 = Time::currentTimeMillis()) - var6 < (int64_t)var8) { - // try { - // Object var21; - // synchronized (var21 = new Object()) { - // var21.wait((int64_t) var8 - (var20 - var6) < 1L ? 1L : (int64_t) var8 - (var20 - var6)); - // } - // } catch (InterruptedException var14) { - // } - Time::sleep((int64_t)var8 - (var20 - var6) < 1L ? 1L : (int64_t)var8 - (var20 - var6)); - - var6 = Time::currentTimeMillis(); + targetFrameTimeMs = 50; + if ((currentTime = Time::currentTimeMillis()) - lastFrameTime < (int64_t)targetFrameTimeMs) { + Time::sleep((int64_t)targetFrameTimeMs - (currentTime - lastFrameTime) < 1L ? 1L : (int64_t)targetFrameTimeMs - (currentTime - lastFrameTime)); + + lastFrameTime = Time::currentTimeMillis(); } else { - var6 = var20; + lastFrameTime = currentTime; } if (Micro::isInGameMenu) { - repaint(); + requestRepaint(); } } } - micro->timeMs += Time::currentTimeMillis() - currentTimeMillis; + micro->timeMs += Time::currentTimeMillis() - loopStartTime; micro->gameCanvas->isDrawingTime = true; if (currentGameMenu == nullptr) { - Micro::field_249 = false; + Micro::isGameLoopRunning = false; } } -void MenuManager::method_202(Graphics* var1) +/* synchronized */ void MenuManager::renderMenuOverGame(Graphics* graphics) { - if (currentGameMenu != nullptr && !field_377) { - micro->gameCanvas->drawGame(var1); - fillCanvasWithImage(var1); - currentGameMenu->render_76(var1); + // Render menu on top of game (for pause menu) + if (currentGameMenu != nullptr && !isMenuRenderingBlocked) { + micro->gameCanvas->drawGame(graphics); + tileBackgroundImage(graphics); + currentGameMenu->render(graphics); } } -void MenuManager::fillCanvasWithImage(Graphics* graphics) +void MenuManager::tileBackgroundImage(Graphics* graphics) { - for (int y = 0; y < getCanvasHeight(); y += rasterImage->getHeight()) { - for (int x = 0; x < getCanvasWidth(); x += rasterImage->getWidth()) { - graphics->drawImage(rasterImage.get(), x, y, 20); + // Tile the raster background image across the entire canvas + for (int y = 0; y < getCanvasHeight(); y += menuBackgroundImage->getHeight()) { + for (int x = 0; x < getCanvasWidth(); x += menuBackgroundImage->getWidth()) { + graphics->drawImage(menuBackgroundImage.get(), x, y, 20); } } } -void MenuManager::processNonFireKeyCode(int keyCode) +void MenuManager::processNonFireKey(int keyCode) { if (micro->gameCanvas->getGameAction(keyCode) != 8) { // if not fire - processKeyCode(keyCode); + processKey(keyCode); } } -void MenuManager::processKeyCode(int keyCode) +void MenuManager::processKey(int keyCode) { if (currentGameMenu != nullptr) { switch (micro->gameCanvas->getGameAction(keyCode)) { @@ -609,13 +607,13 @@ void MenuManager::processKeyCode(int keyCode) return; case 2: // LEFT currentGameMenu->processGameActionUpd(3); - if (currentGameMenu == gameMenuHighscore) { - --field_360; - if (field_360 < 0) { - field_360 = 0; + if (currentGameMenu == highscoreMenu) { + --highscoreLeagueViewIndex; + if (highscoreLeagueViewIndex < 0) { + highscoreLeagueViewIndex = 0; } - method_207(field_360); + refreshHighscoreDisplay(highscoreLeagueViewIndex); } case 3: case 4: @@ -624,13 +622,13 @@ void MenuManager::processKeyCode(int keyCode) break; case 5: // RIGHT currentGameMenu->processGameActionUpd(2); - if (currentGameMenu == gameMenuHighscore) { - ++field_360; - if (field_360 > settingsStringLeague->getMaxAvailableOptionPos()) { - field_360 = settingsStringLeague->getMaxAvailableOptionPos(); + if (currentGameMenu == highscoreMenu) { + ++highscoreLeagueViewIndex; + if (highscoreLeagueViewIndex > leagueSetting->getMaxAvailableOptionPos()) { + highscoreLeagueViewIndex = leagueSetting->getMaxAvailableOptionPos(); } - method_207(field_360); + refreshHighscoreDisplay(highscoreLeagueViewIndex); return; } break; @@ -644,490 +642,498 @@ void MenuManager::processKeyCode(int keyCode) } } -void MenuManager::method_206(Command* var1, Displayable* var2) +void MenuManager::handleCommand(Command* command, Displayable* displayable) { - (void)var2; - if (var1 == commandOk) { + (void)displayable; + if (command == okCommand) { if (currentGameMenu != nullptr) { currentGameMenu->processGameActionUpd(1); return; } - } else if (var1 == commandBack && currentGameMenu != nullptr) { - if (currentGameMenu == gameMenuIngame) { + } else if (command == backCommand && currentGameMenu != nullptr) { + if (currentGameMenu == ingameMenu) { micro->menuToGame(); return; } - method_1(currentGameMenu->getGameMenu(), true); + switchToMenu(currentGameMenu->getGameMenu(), true); } } -GameMenu* MenuManager::getGameMenu() +GameMenu* MenuManager::getCurrentMenu() { return currentGameMenu; } -void MenuManager::method_1(GameMenu* gm, bool var2) +void MenuManager::switchToMenu(GameMenu* menu, bool skipSelectionReset) { - micro->gameCanvas->removeCommand(commandBack); - if (gm != gameMenuMain && gm != gameMenuFinished && gm != nullptr) { - micro->gameCanvas->addCommand(commandBack); + micro->gameCanvas->removeCommand(backCommand); + if (menu != mainMenu && menu != finishedTrackMenu && menu != nullptr) { + micro->gameCanvas->addCommand(backCommand); } - if (gm == gameMenuHighscore) { - field_360 = settingsStringLeague->getCurrentOptionPos(); - method_207(field_360); - } else if (gm == gameMenuFinished) { - field_341 = gameMenuEnterName->getStrArr(); - field_336->setText("Name - " + std::string(field_341)); - } else if (gm == gameMenuPlay) { - settingsStringTrack->setOptionsList(micro->levelLoader->levelNames[settingStringLevel->getCurrentOptionPos()]); - if (currentGameMenu == field_299) { - field_345[settingStringLevel->getCurrentOptionPos()] = settingsStringTrack->getCurrentOptionPos(); + if (menu == highscoreMenu) { + highscoreLeagueViewIndex = leagueSetting->getCurrentOptionPos(); + refreshHighscoreDisplay(highscoreLeagueViewIndex); + } else if (menu == finishedTrackMenu) { + playerName = enterNameMenu->getStrArr(); + nameEntrySetting->setText("Name - " + std::string(playerName)); + } else if (menu == playMenu) { + trackSetting->setOptionsList(micro->levelLoader->trackNames[levelSetting->getCurrentOptionPos()]); + if (currentGameMenu == trackSelectionParentMenu) { + lastSelectedTrackPerLevel[levelSetting->getCurrentOptionPos()] = trackSetting->getCurrentOptionPos(); } - settingsStringTrack->setAvailableOptions(field_342[settingStringLevel->getCurrentOptionPos()]); - settingsStringTrack->setCurentOptionPos(field_345[settingStringLevel->getCurrentOptionPos()]); + trackSetting->setAvailableOptions(maxUnlockedTracksPerLevel[levelSetting->getCurrentOptionPos()]); + trackSetting->setCurentOptionPos(lastSelectedTrackPerLevel[levelSetting->getCurrentOptionPos()]); } - if (gm == gameMenuMain || gm == gameMenuPlay) { + if (menu == mainMenu || menu == playMenu) { micro->gamePhysics->enableGenerateInputAI(); } - currentGameMenu = gm; - if (currentGameMenu != nullptr && !var2) { - currentGameMenu->method_70(); + currentGameMenu = menu; + if (currentGameMenu != nullptr && !skipSelectionReset) { + currentGameMenu->resetToFirstItem(); } - field_377 = false; + isMenuRenderingBlocked = false; } -void MenuManager::method_207(int var1) +void MenuManager::refreshHighscoreDisplay(int leagueIndex) { - gameMenuHighscore->clearVector(); - recordManager->method_8(settingStringLevel->getCurrentOptionPos(), settingsStringTrack->getCurrentOptionPos()); - gameMenuHighscore->addMenuElement(new TextRender(micro->levelLoader->getName(settingStringLevel->getCurrentOptionPos(), settingsStringTrack->getCurrentOptionPos()), micro)); - gameMenuHighscore->addMenuElement(new TextRender("LEAGUE: " + settingsStringLeague->getOptionsList()[var1], micro)); - std::vector var2 = recordManager->getRecordDescription(var1); - - for (std::size_t var3 = 0; var3 < var2.size(); ++var3) { - if (var2[var3] != "") { - TextRender* var4 = new TextRender(std::to_string(var3 + 1) + "." + var2[var3], micro); - var4->setDx(GameCanvas::spriteSizeX[5] + 1); - if (var3 == 0) { - var4->setDrawSprite(true, 5); - } else if (var3 == 1) { - var4->setDrawSprite(true, 6); - } else if (var3 == 2) { - var4->setDrawSprite(true, 7); + highscoreMenu->clearVector(); + recordManager->openRecordStore(levelSetting->getCurrentOptionPos(), trackSetting->getCurrentOptionPos()); + highscoreMenu->addMenuElement(new TextRender(micro->levelLoader->getName(levelSetting->getCurrentOptionPos(), trackSetting->getCurrentOptionPos()), micro)); + highscoreMenu->addMenuElement(new TextRender("LEAGUE: " + leagueSetting->getOptionsList()[leagueIndex], micro)); + std::vector recordDescriptions = recordManager->getRecordDescription(leagueIndex); + + for (std::size_t i = 0; i < recordDescriptions.size(); ++i) { + if (recordDescriptions[i] != "") { + TextRender* recordText = new TextRender(std::to_string(i + 1) + "." + recordDescriptions[i], micro); + recordText->setDx(GameCanvas::spriteSizeX[5] + 1); + if (i == 0) { + recordText->setDrawSprite(true, 5); + } else if (i == 1) { + recordText->setDrawSprite(true, 6); + } else if (i == 2) { + recordText->setDrawSprite(true, 7); } - gameMenuHighscore->addMenuElement(var4); + highscoreMenu->addMenuElement(recordText); } } recordManager->closeRecordStore(); - if (var2[0] == "") { - gameMenuHighscore->addMenuElement(new TextRender("No Highscores", micro)); + if (recordDescriptions[0] == "") { + highscoreMenu->addMenuElement(new TextRender("No Highscores", micro)); } - gameMenuHighscore->addMenuElement(settingStringBack); + highscoreMenu->addMenuElement(backSetting); } -void MenuManager::saveSmthToRecordStoreAndCloseIt() +/* synchronized */ void MenuManager::saveStateAndCloseRecordStore() { if (isRecordStoreOpened) { - method_208(); + saveSettingsToBuffer(); try { recordStore->closeRecordStore(); isRecordStoreOpened = false; - } catch (RecordStoreException& var1) { + } catch (RecordStoreException& e) { } } currentGameMenu = nullptr; } -void MenuManager::method_208() +void MenuManager::saveSettingsToBuffer() { - copyThreeBytesFromArr(16, field_341); - - setValue(0, (int8_t)perspectiveSetting->getCurrentOptionPos()); - setValue(1, (int8_t)shadowsSetting->getCurrentOptionPos()); - setValue(2, (int8_t)driverSpriteSetting->getCurrentOptionPos()); - setValue(3, (int8_t)bikeSpriteSetting->getCurrentOptionPos()); - setValue(14, (int8_t)inputSetting->getCurrentOptionPos()); - setValue(4, (int8_t)lookAheadSetting->getCurrentOptionPos()); - setValue(5, (int8_t)settingsStringLeague->getMaxAvailableOptionPos()); - setValue(6, (int8_t)settingStringLevel->getMaxAvailableOptionPos()); - setValue(10, (int8_t)settingStringLevel->getCurrentOptionPos()); - setValue(11, (int8_t)settingsStringTrack->getCurrentOptionPos()); - setValue(12, (int8_t)settingsStringLeague->getCurrentOptionPos()); + // Copy player name to buffer (indices 16-18) + savePlayerNameToBuffer(16, playerName); + + // Save all settings to the 19-byte buffer + setSavedStateValue(0, (int8_t)perspectiveSetting->getCurrentOptionPos()); + setSavedStateValue(1, (int8_t)shadowsSetting->getCurrentOptionPos()); + setSavedStateValue(2, (int8_t)driverSpriteSetting->getCurrentOptionPos()); + setSavedStateValue(3, (int8_t)bikeSpriteSetting->getCurrentOptionPos()); + setSavedStateValue(14, (int8_t)inputSetting->getCurrentOptionPos()); + setSavedStateValue(4, (int8_t)lookAheadSetting->getCurrentOptionPos()); + setSavedStateValue(5, (int8_t)leagueSetting->getMaxAvailableOptionPos()); + setSavedStateValue(6, (int8_t)levelSetting->getMaxAvailableOptionPos()); + setSavedStateValue(10, (int8_t)levelSetting->getCurrentOptionPos()); + setSavedStateValue(11, (int8_t)trackSetting->getCurrentOptionPos()); + setSavedStateValue(12, (int8_t)leagueSetting->getCurrentOptionPos()); for (int i = 0; i < 3; ++i) { - setValue(7 + i, field_342[i]); + setSavedStateValue(7 + i, maxUnlockedTracksPerLevel[i]); } - if (recorcStoreRecordId == -1) { + if (savedStateRecordId == -1) { try { - recorcStoreRecordId = recordStore->addRecord(field_278, 0, 19); - } catch (RecordStoreNotOpenException& var2) { - } catch (RecordStoreException& var3) { + savedStateRecordId = recordStore->addRecord(savedStateBuffer, 0, 19); + } catch (RecordStoreNotOpenException& e) { + } catch (RecordStoreException& e) { } } else { try { - recordStore->setRecord(recorcStoreRecordId, field_278, 0, 19); - } catch (RecordStoreNotOpenException& var4) { - } catch (RecordStoreException& var5) { + recordStore->setRecord(savedStateRecordId, savedStateBuffer, 0, 19); + } catch (RecordStoreNotOpenException& e) { + } catch (RecordStoreException& e) { } } } -void MenuManager::run() +void MenuManager::runAlertThread() { - // TODO + // TODO: Thread entry point for alert display // if (alert != nullptr) { // Display.getDisplay(micro).setCurrent(alert); // } } -void MenuManager::showAlert(std::string title, std::string alertText, Image* image) +void MenuManager::showAlert(const std::string& title, const std::string& message, Image* image) { (void)title; - (void)alertText; + (void)message; (void)image; - // TODO - // alert = new Alert(title, alertText, image, AlertType.INFO); + // TODO: Display alert dialog + // alert = new Alert(title, message, image, AlertType.INFO); // alert.setTimeout(4000); // (new Thread(this)).start(); } -void MenuManager::processMenu(IGameMenuElement* menuElement) +void MenuManager::handleMenuSelection(IGameMenuElement* element) { - if (menuElement == taskStart) { - if (settingStringLevel->getCurrentOptionPos() <= settingStringLevel->getMaxAvailableOptionPos() && settingsStringTrack->getCurrentOptionPos() <= settingsStringTrack->getMaxAvailableOptionPos() && settingsStringLeague->getCurrentOptionPos() <= settingsStringLeague->getMaxAvailableOptionPos()) { + if (element == startTask) { + if (levelSetting->getCurrentOptionPos() <= levelSetting->getMaxAvailableOptionPos() && trackSetting->getCurrentOptionPos() <= trackSetting->getMaxAvailableOptionPos() && leagueSetting->getCurrentOptionPos() <= leagueSetting->getMaxAvailableOptionPos()) { micro->gamePhysics->disableGenerateInputAI(); - micro->levelLoader->method_88(settingStringLevel->getCurrentOptionPos(), settingsStringTrack->getCurrentOptionPos()); - micro->gamePhysics->setMotoLeague(settingsStringLeague->getCurrentOptionPos()); - field_357 = true; + micro->levelLoader->loadTrack(levelSetting->getCurrentOptionPos(), trackSetting->getCurrentOptionPos()); + micro->gamePhysics->setMotoLeague(leagueSetting->getCurrentOptionPos()); + shouldStartRaceImmediately = true; micro->menuToGame(); } else { showAlert("GDTR", "Complete more tracks to unlock this track/league combo.", nullptr); } - } else if (menuElement == perspectiveSetting) { - micro->gamePhysics->method_26(perspectiveSetting->getCurrentOptionPos() == 0); + } else if (element == perspectiveSetting) { + micro->gamePhysics->invertYPositions(perspectiveSetting->getCurrentOptionPos() == 0); LevelLoader::isEnabledPerspective = perspectiveSetting->getCurrentOptionPos() == 0; - } else if (menuElement == shadowsSetting) { + } else if (element == shadowsSetting) { LevelLoader::isEnabledShadows = shadowsSetting->getCurrentOptionPos() == 0; } else { - if (menuElement == driverSpriteSetting) { - if (driverSpriteSetting->method_114()) { + if (element == driverSpriteSetting) { + if (driverSpriteSetting->checkAndClearSelectionFlag()) { driverSpriteSetting->setCurentOptionPos(driverSpriteSetting->getCurrentOptionPos() + 1); return; } - } else if (menuElement == bikeSpriteSetting) { - if (bikeSpriteSetting->method_114()) { + } else if (element == bikeSpriteSetting) { + if (bikeSpriteSetting->checkAndClearSelectionFlag()) { bikeSpriteSetting->setCurentOptionPos(bikeSpriteSetting->getCurrentOptionPos() + 1); return; } } else { - if (menuElement == inputSetting) { - if (inputSetting->method_114()) { + if (element == inputSetting) { + if (inputSetting->checkAndClearSelectionFlag()) { inputSetting->setCurentOptionPos(inputSetting->getCurrentOptionPos() + 1); } - micro->gameCanvas->method_163(inputSetting->getCurrentOptionPos()); + micro->gameCanvas->setInputConfigIndex(inputSetting->getCurrentOptionPos()); return; } - if (menuElement == lookAheadSetting) { + if (element == lookAheadSetting) { micro->gamePhysics->setEnableLookAhead(lookAheadSetting->getCurrentOptionPos() == 0); return; } - if (menuElement == field_314) { - if (currentGameMenu == gameMenuConfirmClear) { + if (element == confirmYesSetting) { + if (currentGameMenu == confirmClearHighscoresMenu) { recordManager->deleteRecordStores(); showAlert("Cleared", "Highscores have been cleared", nullptr); - } else if (currentGameMenu == gameMenuConfirmReset) { - exit(); + } else if (currentGameMenu == confirmFullResetMenu) { + performFullReset(); showAlert("Reset", "Master reset. Application will be closed.", nullptr); } - method_1(currentGameMenu->getGameMenu(), false); + switchToMenu(currentGameMenu->getGameMenu(), false); return; } - if (menuElement == field_315) { - method_1(currentGameMenu->getGameMenu(), false); + if (element == confirmNoSetting) { + switchToMenu(currentGameMenu->getGameMenu(), false); return; } - if (menuElement == settingStringBack) { - method_1(currentGameMenu->getGameMenu(), true); + if (element == backSetting) { + switchToMenu(currentGameMenu->getGameMenu(), true); return; } - if (menuElement == settingStringPlayMenu) { - settingStringLevel->setCurentOptionPos(field_354); - settingsStringTrack->setAvailableOptions(field_342[field_354]); - settingsStringTrack->setCurentOptionPos(field_355); - method_1(currentGameMenu->getGameMenu(), false); + if (element == playMenuSetting) { + levelSetting->setCurentOptionPos(savedLevelBeforeMenu); + trackSetting->setAvailableOptions(maxUnlockedTracksPerLevel[savedLevelBeforeMenu]); + trackSetting->setCurentOptionPos(savedTrackBeforeMenu); + switchToMenu(currentGameMenu->getGameMenu(), false); return; } - if (menuElement == settingStringGoToMain) { - method_1(gameMenuMain, false); + if (element == goToMainSetting) { + switchToMenu(mainMenu, false); return; } - if (menuElement == settingStringExitGame) { - method_1(currentGameMenu->getGameMenu(), false); + if (element == exitGameSetting) { + switchToMenu(currentGameMenu->getGameMenu(), false); return; } - if (menuElement == field_333) { - if (settingsStringLeague->getCurrentOptionPos() <= settingsStringLeague->getMaxAvailableOptionPos()) { - settingStringLevel->setCurentOptionPos(field_354); - settingsStringTrack->setAvailableOptions(field_342[field_354]); - settingsStringTrack->setCurentOptionPos(field_355); - micro->gamePhysics->setMotoLeague(settingsStringLeague->getCurrentOptionPos()); - field_357 = true; + if (element == restartTrackSetting) { + if (leagueSetting->getCurrentOptionPos() <= leagueSetting->getMaxAvailableOptionPos()) { + levelSetting->setCurentOptionPos(savedLevelBeforeMenu); + trackSetting->setAvailableOptions(maxUnlockedTracksPerLevel[savedLevelBeforeMenu]); + trackSetting->setCurentOptionPos(savedTrackBeforeMenu); + micro->gamePhysics->setMotoLeague(leagueSetting->getCurrentOptionPos()); + shouldStartRaceImmediately = true; micro->menuToGame(); return; } } else { - if (menuElement == field_334) { - if (!field_356) { - settingsStringTrack->menuElemMethod(2); + if (element == nextTrackSetting) { + if (!isAllTracksCompletedAtLevel) { + trackSetting->menuElemMethod(2); } - micro->levelLoader->method_88(settingStringLevel->getCurrentOptionPos(), settingsStringTrack->getCurrentOptionPos()); - micro->gamePhysics->setMotoLeague(settingsStringLeague->getCurrentOptionPos()); - method_208(); - field_357 = true; + micro->levelLoader->loadTrack(levelSetting->getCurrentOptionPos(), trackSetting->getCurrentOptionPos()); + micro->gamePhysics->setMotoLeague(leagueSetting->getCurrentOptionPos()); + saveSettingsToBuffer(); + shouldStartRaceImmediately = true; micro->menuToGame(); return; } - if (menuElement == settingStringContinue) { - repaint(); + if (element == continueSetting) { + requestRepaint(); micro->menuToGame(); return; } - if (menuElement == field_336) { - gameMenuEnterName->method_70(); - method_1(gameMenuEnterName, false); + if (element == nameEntrySetting) { + enterNameMenu->resetToFirstItem(); + switchToMenu(enterNameMenu, false); return; } - if (menuElement == field_335) { - method_197(); + if (element == okSetting) { + saveHighscoreAndShowResults(); return; } - if (menuElement == settingsStringTrack) { - if (settingsStringTrack->method_114()) { - settingsStringTrack->setAvailableOptions(field_342[settingStringLevel->getCurrentOptionPos()]); - settingsStringTrack->init(); - field_299 = settingsStringTrack->getGameMenu(); - method_1(field_299, false); - field_299->method_83(settingsStringTrack->getCurrentOptionPos()); + if (element == trackSetting) { + if (trackSetting->checkAndClearSelectionFlag()) { + trackSetting->setAvailableOptions(maxUnlockedTracksPerLevel[levelSetting->getCurrentOptionPos()]); + trackSetting->init(); + trackSelectionParentMenu = trackSetting->getParentGameMenu(); + switchToMenu(trackSelectionParentMenu, false); + trackSelectionParentMenu->navigateToItem(trackSetting->getCurrentOptionPos()); } - field_345[settingStringLevel->getCurrentOptionPos()] = settingsStringTrack->getCurrentOptionPos(); + lastSelectedTrackPerLevel[levelSetting->getCurrentOptionPos()] = trackSetting->getCurrentOptionPos(); return; } - if (menuElement == settingStringLevel) { - if (settingStringLevel->method_114()) { - gameMenuStringLevel = settingStringLevel->getGameMenu(); - method_1(gameMenuStringLevel, false); - gameMenuStringLevel->method_83(settingStringLevel->getCurrentOptionPos()); + if (element == levelSetting) { + if (levelSetting->checkAndClearSelectionFlag()) { + levelSelectionMenu = levelSetting->getParentGameMenu(); + switchToMenu(levelSelectionMenu, false); + levelSelectionMenu->navigateToItem(levelSetting->getCurrentOptionPos()); } - settingsStringTrack->setOptionsList(micro->levelLoader->levelNames[settingStringLevel->getCurrentOptionPos()]); - settingsStringTrack->setAvailableOptions(field_342[settingStringLevel->getCurrentOptionPos()]); - settingsStringTrack->setCurentOptionPos(field_345[settingStringLevel->getCurrentOptionPos()]); - settingsStringTrack->init(); + trackSetting->setOptionsList(micro->levelLoader->trackNames[levelSetting->getCurrentOptionPos()]); + trackSetting->setAvailableOptions(maxUnlockedTracksPerLevel[levelSetting->getCurrentOptionPos()]); + trackSetting->setCurentOptionPos(lastSelectedTrackPerLevel[levelSetting->getCurrentOptionPos()]); + trackSetting->init(); return; } - if (menuElement == settingsStringLeague && settingsStringLeague->method_114()) { - gameMenuLeague = settingsStringLeague->getGameMenu(); - settingsStringLeague->setParentGameMenu(currentGameMenu); - method_1(gameMenuLeague, false); - gameMenuLeague->method_83(settingsStringLeague->getCurrentOptionPos()); + if (element == leagueSetting && leagueSetting->checkAndClearSelectionFlag()) { + leagueSelectionMenu = leagueSetting->getParentGameMenu(); + leagueSetting->setParentGameMenu(currentGameMenu); + switchToMenu(leagueSelectionMenu, false); + leagueSelectionMenu->navigateToItem(leagueSetting->getCurrentOptionPos()); } } } } } -int MenuManager::method_210() +int MenuManager::getGraphicsFlags() { - int var1 = 0; + // Returns bitfield: bit 0 = bike sprite enabled, bit 1 = driver sprite enabled + int flags = 0; if (driverSpriteSetting->getCurrentOptionPos() == 0) { - var1 |= 2; + flags |= 2; } if (bikeSpriteSetting->getCurrentOptionPos() == 0) { - var1 |= 1; + flags |= 1; } - return var1; + return flags; } -void MenuManager::method_211(int var1) +void MenuManager::applyGraphicsFlags(int flags) { bikeSpriteSetting->setCurentOptionPos(1); driverSpriteSetting->setCurentOptionPos(1); - if ((var1 & 1) > 0) { + if ((flags & 1) > 0) { bikeSpriteSetting->setCurentOptionPos(0); } - if ((var1 & 2) > 0) { + if ((flags & 2) > 0) { driverSpriteSetting->setCurentOptionPos(0); } } -int MenuManager::method_212() +int MenuManager::getSelectedLevel() { - return settingStringLevel->getCurrentOptionPos(); + return levelSetting->getCurrentOptionPos(); } -int MenuManager::method_213() +int MenuManager::getSelectedTrack() { - return settingsStringTrack->getCurrentOptionPos(); + return trackSetting->getCurrentOptionPos(); } -int MenuManager::method_214() +int MenuManager::getSelectedLeague() { - return settingsStringLeague->getCurrentOptionPos(); + return leagueSetting->getCurrentOptionPos(); } -void MenuManager::method_215(int64_t var1) +void MenuManager::setFinishTime(int64_t timeMs) { - field_337 = var1; + finishTime = timeMs; } -std::vector MenuManager::method_216(int var1, int8_t var2) +std::vector MenuManager::loadPlayerName(int index, int8_t defaultValue) { - switch (var1) { + // Load 3 bytes from saved state buffer starting at index + switch (index) { case 16: { - std::vector var3 = std::vector(3); + std::vector result = std::vector(3); - for (int var4 = 0; var4 < 3; ++var4) { - var3[var4] = field_278[16 + var4]; + for (int i = 0; i < 3; ++i) { + result[i] = savedStateBuffer[16 + i]; } - if (var3[0] == -127) { - var3[0] = var2; + if (result[0] == -127) { + result[0] = defaultValue; } - return var3; + return result; } default: return std::vector(); } } -int8_t MenuManager::method_217(int var1, int8_t var2) +int8_t MenuManager::loadSavedStateValue(int index, int8_t defaultValue) { - return field_278[var1] == -127 ? var2 : field_278[var1]; + // Load single byte from saved state, return default if uninitialized (-127) + return savedStateBuffer[index] == -127 ? defaultValue : savedStateBuffer[index]; } -void MenuManager::copyThreeBytesFromArr(int var1, char* var2) +void MenuManager::savePlayerNameToBuffer(int index, char* name) { - if (isRecordStoreOpened && var1 == 16) { + // Copy 3-character player name to buffer at specified index + if (isRecordStoreOpened && index == 16) { for (int i = 0; i < 3; ++i) { - field_278[16 + i] = var2[i]; + savedStateBuffer[16 + i] = name[i]; } } } -std::string MenuManager::timeToString(int64_t time) +std::string MenuManager::formatTime(int64_t timeMs) { - field_338 = (int)(time / 100L); - field_339 = (int)(time % 100L); - std::string timeStr; - if (field_338 / 60 < 10) { - timeStr = " 0" + std::to_string(field_338 / 60); + // Format milliseconds to MM:SS.cc string + finishTimeSeconds = (int)(timeMs / 100L); + finishTimeCentiseconds = (int)(timeMs % 100L); + std::string formattedTime; + if (finishTimeSeconds / 60 < 10) { + formattedTime = " 0" + std::to_string(finishTimeSeconds / 60); } else { - timeStr = " " + std::to_string(field_338 / 60); + formattedTime = " " + std::to_string(finishTimeSeconds / 60); } - if (field_338 % 60 < 10) { - timeStr = timeStr + ":0" + std::to_string(field_338 % 60); + if (finishTimeSeconds % 60 < 10) { + formattedTime = formattedTime + ":0" + std::to_string(finishTimeSeconds % 60); } else { - timeStr = timeStr + ":" + std::to_string(field_338 % 60); + formattedTime = formattedTime + ":" + std::to_string(finishTimeSeconds % 60); } - if (field_339 < 10) { - timeStr = timeStr + ".0" + std::to_string(field_339); + if (finishTimeCentiseconds < 10) { + formattedTime = formattedTime + ".0" + std::to_string(finishTimeCentiseconds); } else { - timeStr = timeStr + "." + std::to_string(field_339); + formattedTime = formattedTime + "." + std::to_string(finishTimeCentiseconds); } - return timeStr; + return formattedTime; } -void MenuManager::setValue(int pos, int8_t value) +void MenuManager::setSavedStateValue(int index, int8_t value) { if (isRecordStoreOpened) { - field_278[pos] = value; + savedStateBuffer[index] = value; } } -void MenuManager::exit() +void MenuManager::performFullReset() { + // Reset all settings to default values perspectiveSetting->setCurentOptionPos(0); shadowsSetting->setCurentOptionPos(0); driverSpriteSetting->setCurentOptionPos(0); bikeSpriteSetting->setCurentOptionPos(0); lookAheadSetting->setCurentOptionPos(0); - settingsStringLeague->setCurentOptionPos(0); - settingsStringLeague->setAvailableOptions(0); - settingStringLevel->setCurentOptionPos(0); - settingStringLevel->setAvailableOptions(1); - settingsStringTrack->setCurentOptionPos(0); - - field_341[0] = 65; - field_341[1] = 65; - field_341[2] = 65; + leagueSetting->setCurentOptionPos(0); + leagueSetting->setAvailableOptions(0); + levelSetting->setCurentOptionPos(0); + levelSetting->setAvailableOptions(1); + trackSetting->setCurentOptionPos(0); + + playerName[0] = 65; // 'A' + playerName[1] = 65; + playerName[2] = 65; inputSetting->setCurentOptionPos(0); - field_342[0] = 0; - field_342[1] = 0; - field_342[2] = -1; - availableLeagues = 0; - method_208(); + maxUnlockedTracksPerLevel[0] = 0; + maxUnlockedTracksPerLevel[1] = 0; + maxUnlockedTracksPerLevel[2] = -1; + maxAvailableLeagues = 0; + saveSettingsToBuffer(); recordManager->deleteRecordStores(); } -void MenuManager::removeOkAndBackCommands() +void MenuManager::removeCommands() { - micro->gameCanvas->removeCommand(commandOk); - micro->gameCanvas->removeCommand(commandBack); + micro->gameCanvas->removeCommand(okCommand); + micro->gameCanvas->removeCommand(backCommand); } -void MenuManager::addOkAndBackCommands() +void MenuManager::addCommands() { - if (currentGameMenu != gameMenuMain && currentGameMenu != gameMenuFinished && currentGameMenu != nullptr) { - micro->gameCanvas->addCommand(commandBack); + if (currentGameMenu != mainMenu && currentGameMenu != finishedTrackMenu && currentGameMenu != nullptr) { + micro->gameCanvas->addCommand(backCommand); } - micro->gameCanvas->addCommand(commandOk); + micro->gameCanvas->addCommand(okCommand); } -int MenuManager::getCountOfRecordStoresWithPrefix(int prefixNumber) +int MenuManager::countRecordStoresForLevel(int levelIndex) { std::vector storeNames = RecordStore::listRecordStores(); if (recordManager != nullptr && !storeNames.empty()) { int count = 0; for (std::size_t i = 0; i < storeNames.size(); ++i) { - if (storeNames[i].find(std::to_string(prefixNumber), 0) == 0) { + if (storeNames[i].find(std::to_string(levelIndex), 0) == 0) { ++count; } } diff --git a/src/MenuManager.h b/src/MenuManager.h index 45fae25..ad166d3 100644 --- a/src/MenuManager.h +++ b/src/MenuManager.h @@ -10,7 +10,7 @@ class Micro; class RecordManager; class Command; class GameMenu; -class TimerOrMotoPartOrMenuElem; +class PhysicsElemOrMenuItem; class SettingsStringRender; class RecordStore; class Image; @@ -21,146 +21,177 @@ class IGameMenuElement; class MenuManager : public IMenuManager { private: - std::vector field_278; + // Saved state buffer (19 bytes) - see initPart case 2 for layout + std::vector savedStateBuffer; Micro* micro; RecordManager* recordManager; - Command* commandOk; - Command* commandBack; - GameMenu* gameMenuMain; - GameMenu* gameMenuPlay; - GameMenu* gameMenuOptions; - GameMenu* gameMenuAbout; - GameMenu* gameMenuHelp; - GameMenu* gameMenuConfirmClear; - GameMenu* gameMenuConfirmReset; - GameMenu* gameMenuFinished; - GameMenu* gameMenuIngame; - TimerOrMotoPartOrMenuElem* taskPlayMenu; - TimerOrMotoPartOrMenuElem* taskOptions; - TimerOrMotoPartOrMenuElem* taskHelp; - SettingsStringRender* settingStringLevel; - GameMenu* gameMenuStringLevel; - SettingsStringRender* settingsStringTrack; - GameMenu* field_299; - SettingsStringRender* settingsStringLeague; - GameMenu* gameMenuLeague; - GameMenu* gameMenuHighscore; - TimerOrMotoPartOrMenuElem* gameTimerTaskHighscore; - SettingsStringRender* taskStart; + Command* okCommand; + Command* backCommand; + // Main menu screens + GameMenu* mainMenu; + GameMenu* playMenu; + GameMenu* optionsMenu; + GameMenu* aboutMenu; + GameMenu* helpMenu; + GameMenu* confirmClearHighscoresMenu; + GameMenu* confirmFullResetMenu; + GameMenu* finishedTrackMenu; + GameMenu* ingameMenu; + // Menu navigation elements + PhysicsElemOrMenuItem* playMenuTask; + PhysicsElemOrMenuItem* optionsTask; + PhysicsElemOrMenuItem* helpTask; + SettingsStringRender* levelSetting; + GameMenu* levelSelectionMenu; + SettingsStringRender* trackSetting; + // Parent menu when track selection is active + GameMenu* trackSelectionParentMenu; + SettingsStringRender* leagueSetting; + GameMenu* leagueSelectionMenu; + GameMenu* highscoreMenu; + PhysicsElemOrMenuItem* highscoreTask; + SettingsStringRender* startTask; SettingsStringRender* perspectiveSetting; SettingsStringRender* shadowsSetting; SettingsStringRender* driverSpriteSetting; SettingsStringRender* bikeSpriteSetting; SettingsStringRender* inputSetting; SettingsStringRender* lookAheadSetting; - TimerOrMotoPartOrMenuElem* clearHighscoreSetting; - TimerOrMotoPartOrMenuElem* field_313; - SettingsStringRender* field_314; - SettingsStringRender* field_315; - TimerOrMotoPartOrMenuElem* taskAbout; - GameMenu* field_317; - TimerOrMotoPartOrMenuElem* field_318; - GameMenu* field_319; - TimerOrMotoPartOrMenuElem* field_320; - GameMenu* field_321; - TimerOrMotoPartOrMenuElem* field_322; - GameMenu* gameMenuOptionsHighscoreDescription; - TimerOrMotoPartOrMenuElem* taskHighscore; - GameMenu* gameMenuOptions2; - TimerOrMotoPartOrMenuElem* field_326; - GameMenu* gameMenuEnterName; - SettingsStringRender* settingStringBack; - SettingsStringRender* settingStringPlayMenu; - SettingsStringRender* settingStringContinue; - SettingsStringRender* settingStringGoToMain; - SettingsStringRender* settingStringExitGame; - SettingsStringRender* field_333; - SettingsStringRender* field_334; - SettingsStringRender* field_335; - SettingsStringRender* field_336; - int64_t field_337; - int field_338; - int field_339; - std::string field_340; - char* field_341; - char field_342[4]; - char defaultInputString[4] = "AAA"; - int8_t availableLeagues = 0; - int8_t field_344 = 0; - std::vector field_345 = { 0, 0, 0 }; - std::vector> levelNames; + PhysicsElemOrMenuItem* clearHighscoreTask; + PhysicsElemOrMenuItem* fullResetTask; + SettingsStringRender* confirmYesSetting; + SettingsStringRender* confirmNoSetting; + PhysicsElemOrMenuItem* aboutTask; + GameMenu* helpObjectiveMenu; + PhysicsElemOrMenuItem* helpObjectiveTask; + GameMenu* helpKeysMenu; + PhysicsElemOrMenuItem* helpKeysTask; + GameMenu* helpUnlockingMenu; + PhysicsElemOrMenuItem* helpUnlockingTask; + GameMenu* helpHighscoreDescriptionMenu; + PhysicsElemOrMenuItem* helpHighscoreTask; + GameMenu* helpOptionsDescriptionMenu; + PhysicsElemOrMenuItem* helpOptionsTask; + GameMenu* enterNameMenu; + SettingsStringRender* backSetting; + SettingsStringRender* playMenuSetting; + SettingsStringRender* continueSetting; + SettingsStringRender* goToMainSetting; + SettingsStringRender* exitGameSetting; + // "Restart: [track name]" + SettingsStringRender* restartTrackSetting; + // "Next: [track name]" + SettingsStringRender* nextTrackSetting; + // "Ok" for highscore entry + SettingsStringRender* okSetting; + // "Name - [player name]" + SettingsStringRender* nameEntrySetting; + int64_t finishTime; + // For display formatting + int finishTimeSeconds; + // For display formatting + int finishTimeCentiseconds; + // Formatted time string (MM:SS.cc) + std::string finishTimeFormatted; + // 3-character player name for highscores + char* playerName; + // Array[3]: max unlocked track per level + char maxUnlockedTracksPerLevel[4]; + char defaultPlayerName[4] = "AAA"; + // Number of unlocked leagues (0-3) + int8_t maxAvailableLeagues = 0; + // Number of unlocked levels (1-3) + int8_t maxAvailableLevels = 0; + // Last selected track for each level + std::vector lastSelectedTrackPerLevel = { 0, 0, 0 }; + // Track names by level + std::vector> trackNamesByLevel; + // Current league names std::vector leagueNames = std::vector(3); - std::vector leagueNamesAll4; + // All 4 league names (including 325cc) + std::vector allLeagueNames; RecordStore* recordStore; - int recorcStoreRecordId = -1; + // Record ID for saved state (typo in original: "recorc") + int savedStateRecordId = -1; bool isRecordStoreOpened; - std::unique_ptr rasterImage; - TextRender* textRenderCodeBrewLink; - int field_354 = 0; - int field_355 = 0; - bool field_356 = false; - bool field_357 = false; - std::vector field_361 = { "Easy", "Medium", "Pro" }; - int64_t field_362 = 0L; - int8_t isDisablePerspective = 0; - int8_t isDisabledShadows = 0; - int8_t isDisabledDriverSprite = 0; - int8_t isDisabledBikeSprite = 0; - int8_t field_367 = 0; - int8_t isDisableLookAhead = 0; - int8_t field_369 = 0; - int8_t field_370 = 0; - int8_t field_371 = 0; - int8_t field_372 = 0; - int8_t field_373 = 0; - std::vector field_374; - std::vector field_375; - std::unique_ptr field_376; + // raster.png + std::unique_ptr menuBackgroundImage; + // Clickable link to codebrew.se + TextRender* codebrewLinkText; + int savedLevelBeforeMenu = 0; + int savedTrackBeforeMenu = 0; + bool isAllTracksCompletedAtLevel = false; + // Flag to start race without menu interaction + bool shouldStartRaceImmediately = false; + // Level display names + std::vector levelNames = { "Easy", "Medium", "Pro" }; + int64_t menuLoopStartTime = 0L; + int8_t perspectiveDisabled = 0; + int8_t shadowsDisabled = 0; + int8_t driverSpriteDisabled = 0; + int8_t bikeSpriteDisabled = 0; + // Selected input method (0-2) + int8_t inputMethod = 0; + int8_t lookAheadDisabled = 0; + int8_t lastSelectedTrack = 0; + int8_t lastSelectedLevel = 0; + int8_t lastSelectedLeague = 0; + int8_t unknownGraphicsSetting = 0; // Graphics setting passed to setInputConfigEnabled + int8_t unknownSetting15 = 0; // Unknown setting from save index 15 + std::vector onOffOptions; // {"On", "Off"} + // {"Keyset 1", "Keyset 2", "Keyset 3"} + std::vector inputMethodNames; + // Empty text for help menu separators + std::unique_ptr helpSeparatorText; // Alert alert = nullptr; // TODO - void addTextRender(GameMenu* gameMenu, std::string text); - void method_197(); - void fillCanvasWithImage(Graphics* graphics); - void processNonFireKeyCode(int keyCode); - std::vector method_216(int var1, int8_t var2); - int8_t method_217(int var1, int8_t var2); - void copyThreeBytesFromArr(int var1, char* var2); - std::string timeToString(int64_t time); - void setValue(int pos, int8_t value); - void exit(); - int getCountOfRecordStoresWithPrefix(int prefixNumber); + void addMultilineTextToMenu(GameMenu* menu, const std::string& text); + void saveHighscoreAndShowResults(); + void tileBackgroundImage(Graphics* graphics); + void processNonFireKey(int keyCode); + std::vector loadPlayerName(int index, int8_t defaultValue); + int8_t loadSavedStateValue(int index, int8_t defaultValue); + void savePlayerNameToBuffer(int index, char* name); + std::string formatTime(int64_t timeMs); + void setSavedStateValue(int index, int8_t value); + void performFullReset(); + int countRecordStoresForLevel(int levelIndex); public: GameMenu* currentGameMenu; - int field_360 = 0; - bool field_377 = false; + // Currently viewed league in highscore screen + int highscoreLeagueViewIndex = 0; + // Prevents menu rendering over game + bool isMenuRenderingBlocked = false; - MenuManager(Micro* var1); - void initPart(int var1); - int getCurrentLevel(); - int getCurrentTrack(); - bool method_196(); - void repaint(); + MenuManager(Micro* micro); + // Initialize in phases 1-7 + void initializePhase(int phase); + // Returns and clears shouldStartRaceImmediately + bool consumeShouldStartRaceFlag(); + void requestRepaint(); int getCanvasHeight(); int getCanvasWidth(); - void method_201(int var1); - void method_206(Command* var1, Displayable* var2); - GameMenu* getGameMenu(); - void method_1(GameMenu* gm, bool var2); - void method_207(int var1); - /* synchronized */ void saveSmthToRecordStoreAndCloseIt(); - void method_208(); - void run(); - void showAlert(std::string title, std::string alertText, Image* image); - void processMenu(IGameMenuElement* menuElement); - int method_210(); - void method_211(int var1); - int method_212(); - int method_213(); - int method_214(); - void method_215(int64_t var1); - void removeOkAndBackCommands(); - void addOkAndBackCommands(); - /* synchronized */ void method_202(Graphics* var1); - void processKeyCode(int keyCode); + // 0=main, 1=ingame, 2=finished + void runMenuLoop(int menuType); + void handleCommand(Command* command, Displayable* displayable); + GameMenu* getCurrentMenu(); + void switchToMenu(GameMenu* menu, bool skipSelectionReset); + void refreshHighscoreDisplay(int leagueIndex); + /* synchronized */ void saveStateAndCloseRecordStore(); + void saveSettingsToBuffer(); + void runAlertThread(); // TODO: Thread entry point for alert display + void showAlert(const std::string& title, const std::string& message, Image* image); + void handleMenuSelection(IGameMenuElement* element); + // Bitfield: bit 0=bike sprite, bit 1=driver sprite + int getGraphicsFlags(); + void applyGraphicsFlags(int flags); + int getSelectedLevel(); + int getSelectedTrack(); + int getSelectedLeague(); + void setFinishTime(int64_t timeMs); + void removeCommands(); + void addCommands(); + /* synchronized */ void renderMenuOverGame(Graphics* graphics); + void processKey(int keyCode); }; diff --git a/src/Micro.cpp b/src/Micro.cpp index 13f1bee..7ce2e4a 100644 --- a/src/Micro.cpp +++ b/src/Micro.cpp @@ -8,7 +8,7 @@ #include "lcdui/CanvasImpl.h" #include "rms/RecordStore.h" -bool Micro::field_249 = false; +bool Micro::isGameLoopRunning = false; int Micro::gameLoadingStateStage = 0; Micro::Micro() @@ -28,12 +28,12 @@ void Micro::gameToMenu() { gameCanvas->removeMenuCommand(); isInGameMenu = true; - menuManager->addOkAndBackCommands(); + menuManager->addCommands(); } void Micro::menuToGame() { - menuManager->removeOkAndBackCommands(); + menuManager->removeCommands(); isInGameMenu = false; gameCanvas->addMenuCommand(); } @@ -53,25 +53,25 @@ int64_t Micro::goLoadingStep() break; case 3: menuManager = new MenuManager(this); - menuManager->initPart(1); + menuManager->initializePhase(1); break; case 4: - menuManager->initPart(2); + menuManager->initializePhase(2); break; case 5: - menuManager->initPart(3); + menuManager->initializePhase(3); break; case 6: - menuManager->initPart(4); + menuManager->initializePhase(4); break; case 7: - menuManager->initPart(5); + menuManager->initializePhase(5); break; case 8: - menuManager->initPart(6); + menuManager->initializePhase(6); break; case 9: - menuManager->initPart(7); + menuManager->initializePhase(7); break; case 10: gameCanvas->setMenuManager(menuManager); @@ -83,7 +83,7 @@ int64_t Micro::goLoadingStep() // try { // Thread.sleep(100L); - // } catch (InterruptedException var3) { + // } catch (InterruptedException e) { // } Time::sleep(100LL); } @@ -122,25 +122,25 @@ void Micro::init() isInited = true; } -void Micro::restart(bool var1) +void Micro::restart(bool scheduleTimerTask) { - gamePhysics->resetSmth(true); + gamePhysics->resetPhysicsState(true); timeMs = 0; gameTimeMs = 0; - field_246 = 0; - if (var1) { - gameCanvas->scheduleGameTimerTask(levelLoader->getName(menuManager->getCurrentLevel(), menuManager->getCurrentTrack()), 3000); + crashRestartTimeMs = 0; + if (scheduleTimerTask) { + gameCanvas->scheduleGameTimerTask(levelLoader->getName(menuManager->getSelectedLevel(), menuManager->getSelectedTrack()), 3000); } - gameCanvas->method_129(); + gameCanvas->reset(); } -void Micro::destroyApp(bool var1) +void Micro::destroyApp(bool var1) // TODO: unused parameter { (void)var1; - field_249 = false; - field_242 = true; - menuManager->saveSmthToRecordStoreAndCloseIt(); + isGameLoopRunning = false; + isAboutToExit = true; + menuManager->saveStateAndCloseRecordStore(); } void Micro::startApp(int argc, char** argv) @@ -158,7 +158,7 @@ void Micro::startApp(int argc, char** argv) RecordStore::setRecordStoreDir(argv[0]); - field_249 = true; + isGameLoopRunning = true; // if (thread == null) { // thread = new Thread(this); // thread.start(); @@ -175,32 +175,32 @@ void Micro::run() gameCanvas->setCommandListener(gameCanvas); restart(false); - menuManager->method_201(0); - if (menuManager->method_196()) { + menuManager->runMenuLoop(0); + if (menuManager->consumeShouldStartRaceFlag()) { restart(true); } - int64_t var3 = 0L; + int64_t lastMillis = 0L; - while (field_249) { - int var5; - if (gamePhysics->method_21() != menuManager->method_210()) { - var5 = gameCanvas->loadSprites(menuManager->method_210()); - gamePhysics->method_22(var5); - menuManager->method_211(var5); + while (isGameLoopRunning) { + int physicsState; + if (gamePhysics->getRenderModeIndex() != menuManager->getGraphicsFlags()) { + physicsState = gameCanvas->loadSprites(menuManager->getGraphicsFlags()); + gamePhysics->setRenderFlags(physicsState); + menuManager->applyGraphicsFlags(physicsState); } - bool var10000; + bool shouldContinueLoop; try { if (isInGameMenu) { - menuManager->method_201(1); - if (menuManager->method_196()) { + menuManager->runMenuLoop(1); + if (menuManager->consumeShouldStartRaceFlag()) { restart(true); } } for (int i = numPhysicsLoops; i > 0; --i) { - if (field_248) { + if (advanceGameTime) { gameTimeMs += 20L; } @@ -208,94 +208,94 @@ void Micro::run() timeMs = Time::currentTimeMillis(); } - if ((var5 = gamePhysics->updatePhysics()) == 3 && field_246 == 0L) { - field_246 = Time::currentTimeMillis() + 3000L; + if ((physicsState = gamePhysics->updatePhysics()) == 3 && crashRestartTimeMs == 0L) { + crashRestartTimeMs = Time::currentTimeMillis() + 3000L; gameCanvas->scheduleGameTimerTask("Crashed", 3000); gameCanvas->repaint(); gameCanvas->serviceRepaints(); } - if (field_246 != 0L && field_246 < Time::currentTimeMillis()) { + if (crashRestartTimeMs != 0L && crashRestartTimeMs < Time::currentTimeMillis()) { restart(true); } - if (var5 == 5) { + if (physicsState == 5) { gameCanvas->scheduleGameTimerTask("Crashed", 3000); gameCanvas->repaint(); gameCanvas->serviceRepaints(); // try { // long var7 = 1000L; - // if (this.field_246 > 0L) { - // var7 = Math.min(this.field_246 - System.currentTimeMillis(), 1000L); + // if (this.crashRestartTimeMs > 0L) { + // var7 = Math.min(this.crashRestartTimeMs - System.currentTimeMillis(), 1000L); // } // if (var7 > 0L) { // Thread.sleep(var7); // } - // } catch (InterruptedException var12) { + // } catch (InterruptedException e) { // } - int64_t var7 = 1000L; - if (field_246 > 0L) { - var7 = std::min(field_246 - Time::currentTimeMillis(), static_cast(1000)); + int64_t crashRestartDelay = 1000L; + if (crashRestartTimeMs > 0L) { + crashRestartDelay = std::min(crashRestartTimeMs - Time::currentTimeMillis(), static_cast(1000)); } - if (var7 > 0L) { - Time::sleep(var7); + if (crashRestartDelay > 0L) { + Time::sleep(crashRestartDelay); } restart(true); - } else if (var5 == 4) { + } else if (physicsState == 4) { timeMs = 0L; gameTimeMs = 0L; - } else if (var5 == 1 || var5 == 2) { - if (var5 == 2) { + } else if (physicsState == 1 || physicsState == 2) { + if (physicsState == 2) { gameTimeMs -= 10L; } goalLoop(); - menuManager->method_215(gameTimeMs / 10L); - menuManager->method_201(2); - if (menuManager->method_196()) { + menuManager->setFinishTime(gameTimeMs / 10L); + menuManager->runMenuLoop(2); + if (menuManager->consumeShouldStartRaceFlag()) { restart(true); } - if (!field_249) { + if (!isGameLoopRunning) { break; } } - field_248 = var5 != 4; + advanceGameTime = physicsState != 4; } - var10000 = field_249; - } catch (std::exception& var15) { + shouldContinueLoop = isGameLoopRunning; + } catch (std::exception& e) { continue; } - if (!var10000) { + if (!shouldContinueLoop) { break; } try { - gamePhysics->method_53(); - int64_t var1; - if ((var1 = Time::currentTimeMillis()) - var3 < 30L) { + gamePhysics->captureRenderSnapshot(); + int64_t curMillis; + if ((curMillis = Time::currentTimeMillis()) - lastMillis < 30L) { // try { // synchronized (this) { // wait(Math.max(30L - (var1 - var3), 1L)); // } - // } catch (InterruptedException var11) { + // } catch (InterruptedException e) { // } - Time::sleep(std::max(30LL - (var1 - var3), 1LL)); + Time::sleep(std::max(30LL - (curMillis - lastMillis), 1LL)); - var3 = Time::currentTimeMillis(); + lastMillis = Time::currentTimeMillis(); } else { - var3 = var1; + lastMillis = curMillis; } gameCanvas->repaint(); - } catch (std::exception& var14) { + } catch (std::exception& e) { } } @@ -304,8 +304,8 @@ void Micro::run() void Micro::goalLoop() { - int64_t var4 = 0L; - if (!gamePhysics->field_69) { + int64_t lastFrameTime = 0L; + if (!gamePhysics->frontWheelContactLatch) { gameCanvas->scheduleGameTimerTask("Wheelie!", 1000); } else { gameCanvas->scheduleGameTimerTask("Finished", 1000); @@ -326,7 +326,7 @@ void Micro::goalLoop() // } // return; - // } catch (InterruptedException var12) { + // } catch (InterruptedException e) { // return; // } int64_t deltaTime; @@ -338,20 +338,20 @@ void Micro::goalLoop() } } - gamePhysics->method_53(); - int64_t var2; - if ((var2 = Time::currentTimeMillis()) - var4 < 30L) { + gamePhysics->captureRenderSnapshot(); + int64_t currentTime; + if ((currentTime = Time::currentTimeMillis()) - lastFrameTime < 30L) { // try { // synchronized (this) { - // wait(Math.max(30L - (var2 - var4), 1L)); + // wait(Math.max(30L - (currentTime - lastFrameTime), 1L)); // } - // } catch (InterruptedException var14) { + // } catch (InterruptedException e) { // } - Time::sleep(std::max(30LL - (var2 - var4), 1LL)); + Time::sleep(std::max(30LL - (currentTime - lastFrameTime), 1LL)); - var4 = Time::currentTimeMillis(); + lastFrameTime = Time::currentTimeMillis(); } else { - var4 = var2; + lastFrameTime = currentTime; } } } diff --git a/src/Micro.h b/src/Micro.h index 073617c..76a25d1 100644 --- a/src/Micro.h +++ b/src/Micro.h @@ -11,7 +11,7 @@ class LevelLoader; class Micro { private: int64_t goLoadingStep(); - void destroyApp(bool var1); + void destroyApp(bool var1); // TODO: unused parameter std::filesystem::path mrgFilePath; public: @@ -19,14 +19,14 @@ class Micro { LevelLoader* levelLoader; GamePhysics* gamePhysics; MenuManager* menuManager; - bool field_242 = false; + bool isAboutToExit = false; int numPhysicsLoops = 2; int64_t timeMs = 0; int64_t gameTimeMs = 0; - int64_t field_246 = 0; + int64_t crashRestartTimeMs = 0; bool isInited = false; - bool field_248 = false; - static bool field_249; + bool advanceGameTime = false; + static bool isGameLoopRunning; inline static bool isInGameMenu; static int gameLoadingStateStage; @@ -38,7 +38,7 @@ class Micro { void gameToMenu(); void menuToGame(); void init(); - void restart(bool var1); + void restart(bool scheduleTimerTask); void run(); void goalLoop(); void setNumPhysicsLoops(int value); diff --git a/src/MotoComponent.cpp b/src/MotoComponent.cpp new file mode 100644 index 0000000..07f2b70 --- /dev/null +++ b/src/MotoComponent.cpp @@ -0,0 +1,22 @@ +#include "MotoComponent.h" + +MotoComponent::MotoComponent() + : radiusF16(0) + , radiusIndex(0) + , inverseMassF16(0) + , leanInfluenceF16(0) +{ + for (int i = 0; i < 6; ++i) { + stateBuffers[i] = std::make_unique(); + } + + reset(); +} + +void MotoComponent::reset() +{ + radiusF16 = inverseMassF16 = leanInfluenceF16 = 0; + for (int i = 0; i < 6; ++i) { + stateBuffers[i]->setToZeros(); + } +} diff --git a/src/MotoComponent.h b/src/MotoComponent.h new file mode 100644 index 0000000..bdc6143 --- /dev/null +++ b/src/MotoComponent.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "PhysicsElemOrMenuItem.h" + +// Moto component physics data (bike parts: chassis, wheels, rider, etc.) +class MotoComponent { +public: + // Component radius - used in collision detection + int radiusF16; + // Component type / radius index (0, 1, or 2 maps to different radii) + int radiusIndex; + // Component mass (inverse) - used in force calculations: force / mass = acceleration + int inverseMassF16; + // Component leanF16 influence - only set for back wheel, used in angular velocity calc + int leanInfluenceF16; + /** + * Buffered physics states for multi-stage integration + * + * 0 - Current state (read buffer) + * 1 - Next state (write buffer) + * 2 - Integration work A (predictor) + * 3 - Integration work B (corrector) + * 4 - Midpoint state (predictor-corrector) + * 5 - Render snapshot (copied to GamePhysics::renderCache) + */ + std::vector> stateBuffers = std::vector>(6); + + MotoComponent(); + ~MotoComponent() = default; + void reset(); +}; diff --git a/src/PhysicsElemOrMenuItem.cpp b/src/PhysicsElemOrMenuItem.cpp new file mode 100644 index 0000000..38b8677 --- /dev/null +++ b/src/PhysicsElemOrMenuItem.cpp @@ -0,0 +1,69 @@ +#include "PhysicsElemOrMenuItem.h" + +#include "IMenuManager.h" +#include "GameMenu.h" +#include "lcdui/Graphics.h" + +PhysicsElemOrMenuItem::PhysicsElemOrMenuItem() +{ + setToZeros(); +} + +PhysicsElemOrMenuItem::PhysicsElemOrMenuItem(int timerNo, Micro* micro) +{ + this->micro = micro; + this->timerNo = timerNo; +} + +PhysicsElemOrMenuItem::PhysicsElemOrMenuItem(std::string text, GameMenu* gameMenu, IMenuManager* menuManager) +{ + this->text = text + ">"; + this->gameMenu = gameMenu; + this->menuManager = menuManager; +} + +void PhysicsElemOrMenuItem::setToZeros() +{ + xF16 = yF16 = angleF16 = 0; + velocityXF16 = velocityYF16 = angularVelocityF16 = 0; + forceAccumXF16 = forceAccumYF16 = torqueF16 = 0; +} + +void PhysicsElemOrMenuItem::setText(std::string text) +{ + this->text = text + ">"; +} + +std::string PhysicsElemOrMenuItem::getText() +{ + return text; +} + +bool PhysicsElemOrMenuItem::isNotTextRender() +{ + return true; +} + +void PhysicsElemOrMenuItem::menuElemMethod(int action) +{ + switch (action) { + case 1: + case 2: + menuManager->handleMenuSelection(this); + gameMenu->setGameMenu(menuManager->getCurrentMenu()); + menuManager->switchToMenu(gameMenu, false); + case 3: + default: + break; + } +} + +void PhysicsElemOrMenuItem::setGameMenu(GameMenu* gameMenu) +{ + this->gameMenu = gameMenu; +} + +void PhysicsElemOrMenuItem::render(Graphics* graphics, int y, int x) +{ + graphics->drawString(text, x, y, 20); +} \ No newline at end of file diff --git a/src/PhysicsElemOrMenuItem.h b/src/PhysicsElemOrMenuItem.h new file mode 100644 index 0000000..5fb9b11 --- /dev/null +++ b/src/PhysicsElemOrMenuItem.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include "Micro.h" +#include "IGameMenuElement.h" + +class Graphics; +class GameMenu; +class IMenuManager; + +// FIXME: should be split into separate classes PhysicsState / MenuItem +class PhysicsElemOrMenuItem : public IGameMenuElement { +private: + std::string text; + GameMenu* gameMenu; + IMenuManager* menuManager; + +public: + int xF16; + int yF16; + int angleF16; + int velocityXF16; + int velocityYF16; + // Angular velocity + int angularVelocityF16; + // Force accumulator X + int forceAccumXF16; + // Force accumulator Y + int forceAccumYF16; + // Torque + int torqueF16; + int timerNo; // UNUSED + Micro* micro; + + PhysicsElemOrMenuItem(); + PhysicsElemOrMenuItem(int timerNo, Micro* micro); + PhysicsElemOrMenuItem(std::string text, GameMenu* gameMenu, IMenuManager* menuManager); + void setToZeros(); + void setText(std::string text); + std::string getText(); + bool isNotTextRender(); + void menuElemMethod(int action); + void setGameMenu(GameMenu* gameMenu); + void render(Graphics* graphics, int y, int x); +}; diff --git a/src/RecordManager.cpp b/src/RecordManager.cpp index 3a4f57d..69277a5 100644 --- a/src/RecordManager.cpp +++ b/src/RecordManager.cpp @@ -5,14 +5,14 @@ #include "rms/RecordStoreNotOpenException.h" #include "rms/InvalidRecordIDException.h" -void RecordManager::method_8(int var1, int var2) +void RecordManager::openRecordStore(int playerId, int levelId) { resetRecordsTime(); try { - str = std::to_string(var1) + std::to_string(var2); + str = std::to_string(playerId) + std::to_string(levelId); recordStore = RecordStore::openRecordStore(str, true); - } catch (RecordStoreException& var9) { + } catch (RecordStoreException& e) { return; } @@ -21,52 +21,52 @@ void RecordManager::method_8(int var1, int var2) RecordEnumeration* recordEnum; try { recordEnum = recordStore->enumerateRecords(nullptr, nullptr, false); - } catch (RecordStoreNotOpenException& var8) { + } catch (RecordStoreNotOpenException& e) { return; } if (recordEnum->numRecords() > 0) { - std::vector var4; + std::vector recordData; try { - var4 = recordEnum->nextRecord(); + recordData = recordEnum->nextRecord(); recordEnum->reset(); packedRecordInfoRecordId = recordEnum->nextRecordId(); - } catch (RecordStoreNotOpenException& var5) { + } catch (RecordStoreNotOpenException& e) { return; - } catch (InvalidRecordIDException& var6) { + } catch (InvalidRecordIDException& e) { return; - } catch (RecordStoreException& var7) { + } catch (RecordStoreException& e) { return; } - loadRecordInfo(var4); + loadRecordInfo(recordData); recordEnum->destroy(); } } -int64_t RecordManager::load5BytesAsLong(std::vector var1, int offset) +int64_t RecordManager::load5BytesAsLong(std::vector buffer, int offset) { int64_t result = 0L; int64_t mult = 1L; - for (int var7 = offset; var7 < 5 + offset; ++var7) { - int var8 = (var1[var7] + 256) % 256; - result += mult * (int64_t)var8; + for (int i = offset; i < 5 + offset; ++i) { + int byteVal = (buffer[i] + 256) % 256; + result += mult * (int64_t)byteVal; mult *= 256L; } return result; } -void RecordManager::pushLongAs5Bytes(std::vector var1, int var2, int64_t var3) +void RecordManager::pushLongAs5Bytes(std::vector buffer, int offset, int64_t value) { - for (int var5 = var2; var5 < 5 + var2; ++var5) { - var1[var5] = (int8_t)((int)(var3 % 256L)); - var3 /= 256L; + for (int i = offset; i < 5 + offset; ++i) { + buffer[i] = (int8_t)((int)(value % 256L)); + value /= 256L; } } -void RecordManager::loadRecordInfo(std::vector var1) +void RecordManager::loadRecordInfo(std::vector buffer) { int offset = 0; @@ -74,7 +74,7 @@ void RecordManager::loadRecordInfo(std::vector var1) int pos; for (league = 0; league < 4; ++league) { for (pos = 0; pos < 3; ++pos) { - recordTimeMs[league][pos] = load5BytesAsLong(var1, offset); + recordTimeMs[league][pos] = load5BytesAsLong(buffer, offset); offset += 5; } } @@ -82,13 +82,13 @@ void RecordManager::loadRecordInfo(std::vector var1) for (league = 0; league < LEAGUES_MAX; ++league) { for (pos = 0; pos < RECORD_NO_MAX; ++pos) { for (auto i = 0; i < PLAYER_NAME_MAX; ++i) { - recordName[league][pos][i] = var1[offset++]; + recordName[league][pos][i] = buffer[offset++]; } } } } -void RecordManager::getLevelInfo(std::vector var1) +void RecordManager::getLevelInfo(std::vector buffer) { int shift = 0; @@ -96,7 +96,7 @@ void RecordManager::getLevelInfo(std::vector var1) int recordNo; for (league = 0; league < 4; ++league) { for (recordNo = 0; recordNo < 3; ++recordNo) { - pushLongAs5Bytes(var1, shift, recordTimeMs[league][recordNo]); + pushLongAs5Bytes(buffer, shift, recordTimeMs[league][recordNo]); shift += 5; } } @@ -104,7 +104,7 @@ void RecordManager::getLevelInfo(std::vector var1) for (league = 0; league < LEAGUES_MAX; ++league) { for (recordNo = 0; recordNo < RECORD_NO_MAX; ++recordNo) { for (auto i = 0; i < PLAYER_NAME_MAX; ++i) { - var1[shift++] = recordName[league][recordNo][i]; + buffer[shift++] = recordName[league][recordNo][i]; } } } @@ -119,39 +119,39 @@ void RecordManager::resetRecordsTime() } } -std::vector RecordManager::getRecordDescription(int var1) +std::vector RecordManager::getRecordDescription(int league) { - std::vector var2 = std::vector(3); + std::vector descriptions = std::vector(3); - for (int var3 = 0; var3 < 3; ++var3) { - if (recordTimeMs[var1][var3] != 0L) { - int var4 = (int)recordTimeMs[var1][var3] / 100; - int var5 = (int)recordTimeMs[var1][var3] % 100; - var2[var3] = "" + std::string(recordName[var1][var3]) + " "; + for (int i = 0; i < 3; ++i) { + if (recordTimeMs[league][i] != 0L) { + int seconds = (int)recordTimeMs[league][i] / 100; + int centiseconds = (int)recordTimeMs[league][i] % 100; + descriptions[i] = "" + std::string(recordName[league][i]) + " "; - if (var4 / 60 < 10) { - var2[var3] = var2[var3] + " 0" + std::to_string(var4 / 60); + if (seconds / 60 < 10) { + descriptions[i] = descriptions[i] + " 0" + std::to_string(seconds / 60); } else { - var2[var3] = var2[var3] + " " + std::to_string(var4 / 60); + descriptions[i] = descriptions[i] + " " + std::to_string(seconds / 60); } - if (var4 % 60 < 10) { - var2[var3] = var2[var3] + ":0" + std::to_string(var4 % 60); + if (seconds % 60 < 10) { + descriptions[i] = descriptions[i] + ":0" + std::to_string(seconds % 60); } else { - var2[var3] = var2[var3] + ":" + std::to_string(var4 % 60); + descriptions[i] = descriptions[i] + ":" + std::to_string(seconds % 60); } - if (var5 < 10) { - var2[var3] = var2[var3] + ".0" + std::to_string(var5); + if (centiseconds < 10) { + descriptions[i] = descriptions[i] + ".0" + std::to_string(centiseconds); } else { - var2[var3] = var2[var3] + "." + std::to_string(var5); + descriptions[i] = descriptions[i] + "." + std::to_string(centiseconds); } } else { - var2[var3].clear(); + descriptions[i].clear(); } } - return var2; + return descriptions; } void RecordManager::writeRecordInfo() @@ -160,14 +160,14 @@ void RecordManager::writeRecordInfo() if (packedRecordInfoRecordId == -1) { try { packedRecordInfoRecordId = recordStore->addRecord(packedRecordInfo, 0, 96); - } catch (RecordStoreNotOpenException& var1) { - } catch (RecordStoreException& var2) { + } catch (RecordStoreNotOpenException& e) { + } catch (RecordStoreException& e) { } } else { try { recordStore->setRecord(packedRecordInfoRecordId, packedRecordInfo, 0, 96); - } catch (RecordStoreNotOpenException& var3) { - } catch (RecordStoreException& var4) { + } catch (RecordStoreNotOpenException& e) { + } catch (RecordStoreException& e) { } } } @@ -183,7 +183,7 @@ int RecordManager::getPosOfNewRecord(int league, int64_t timeMs) return 3; } -void RecordManager::method_17(int league, char* values, int64_t timeMs) +void RecordManager::addRecord(int league, char* playerName, int64_t timeMs) { int newRecordPos; if ((newRecordPos = getPosOfNewRecord(league, timeMs)) != 3) { @@ -195,7 +195,7 @@ void RecordManager::method_17(int league, char* values, int64_t timeMs) recordTimeMs[league][newRecordPos] = timeMs; for (auto i = 0; i < PLAYER_NAME_MAX; ++i) { - recordName[league][newRecordPos][i] = values[i]; + recordName[league][newRecordPos][i] = playerName[i]; } } } @@ -217,9 +217,8 @@ void RecordManager::deleteRecordStores() for (std::size_t i = 0; i < names.size(); ++i) { if (names[i] != "GDTRStates") { try { - // RecordStore *var10000 = recordStore; RecordStore::deleteRecordStore(names[i]); - } catch (RecordStoreException& var3) { + } catch (RecordStoreException& e) { } } } @@ -231,7 +230,7 @@ void RecordManager::closeRecordStore() try { recordStore->closeRecordStore(); return; - } catch (RecordStoreException& var1) { + } catch (RecordStoreException& e) { } } } diff --git a/src/RecordManager.h b/src/RecordManager.h index d4cc618..deb2fed 100644 --- a/src/RecordManager.h +++ b/src/RecordManager.h @@ -16,11 +16,12 @@ class RecordManager { inline static const int unused = 3; - void method_8(int var1, int var2); - std::vector getRecordDescription(int var1); + // Opens record store with player/level ID + void openRecordStore(int playerId, int levelId); + std::vector getRecordDescription(int league); void writeRecordInfo(); int getPosOfNewRecord(int league, int64_t timeMs); - void method_17(int league, char* values, int64_t timeMs); + void addRecord(int league, char* playerName, int64_t timeMs); void deleteRecordStores(); void closeRecordStore(); @@ -34,10 +35,10 @@ class RecordManager { std::vector packedRecordInfo = std::vector(96); std::string str; - int64_t load5BytesAsLong(std::vector var1, int offset); - void pushLongAs5Bytes(std::vector var1, int var2, int64_t var3); - void loadRecordInfo(std::vector var1); - void getLevelInfo(std::vector var1); + int64_t load5BytesAsLong(std::vector buffer, int offset); + void pushLongAs5Bytes(std::vector buffer, int offset, int64_t value); + void loadRecordInfo(std::vector buffer); + void getLevelInfo(std::vector buffer); void resetRecordsTime(); void addNewRecord(int gameLevel, int position); }; diff --git a/src/SettingsStringRender.cpp b/src/SettingsStringRender.cpp index 9962e23..2ba0381 100644 --- a/src/SettingsStringRender.cpp +++ b/src/SettingsStringRender.cpp @@ -5,7 +5,7 @@ #include "GameCanvas.h" #include "lcdui/Graphics.h" -SettingsStringRender::SettingsStringRender(std::string text, int isDisabled, IMenuManager* menuManager, std::vector optionsList, bool var5, Micro* micro, GameMenu* gameMenu, bool useColon) +SettingsStringRender::SettingsStringRender(std::string text, int isDisabled, IMenuManager* menuManager, std::vector optionsList, bool isToggle, Micro* micro, GameMenu* gameMenu, bool useColon) { this->micro = micro; if (useColon) { @@ -25,9 +25,9 @@ SettingsStringRender::SettingsStringRender(std::string text, int isDisabled, IMe } maxAvailableOption = optionsList.size() - 1; - field_146 = var5; + isToggleMode = isToggle; setCurentOptionPos(isDisabled); - if (var5) { + if (isToggle) { if (isDisabled == 1) { selectedOptionName = "Off"; } else { @@ -47,9 +47,9 @@ void SettingsStringRender::setFlags(bool hasSprite, bool isDrawSprite8) this->isDrawSprite8 = isDrawSprite8; } -void SettingsStringRender::setOptionsList(std::vector var1) +void SettingsStringRender::setOptionsList(std::vector options) { - optionsList = var1; + optionsList = options; if (currentOptionPos > static_cast(optionsList.size()) - 1) { currentOptionPos = optionsList.size() - 1; } @@ -67,15 +67,15 @@ void SettingsStringRender::init() currentGameMenu = new GameMenu(text, micro, parentGameMenu); settingsStringRenders = std::vector(optionsList.size()); - for (int var1 = 0; var1 < static_cast(settingsStringRenders.size()); ++var1) { - if (var1 > maxAvailableOption) { - settingsStringRenders[var1] = new SettingsStringRender(optionsList[var1], 0, this, std::vector(), false, micro, parentGameMenu, true); - settingsStringRenders[var1]->setFlags(true, true); + for (int i = 0; i < static_cast(settingsStringRenders.size()); ++i) { + if (i > maxAvailableOption) { + settingsStringRenders[i] = new SettingsStringRender(optionsList[i], 0, this, std::vector(), false, micro, parentGameMenu, true); + settingsStringRenders[i]->setFlags(true, true); } else { - settingsStringRenders[var1] = new SettingsStringRender(optionsList[var1], 0, this, std::vector(), false, micro, parentGameMenu, true); + settingsStringRenders[i] = new SettingsStringRender(optionsList[i], 0, this, std::vector(), false, micro, parentGameMenu, true); } - currentGameMenu->addMenuElement(settingsStringRenders[var1]); + currentGameMenu->addMenuElement(settingsStringRenders[i]); } } @@ -98,17 +98,17 @@ bool SettingsStringRender::isNotTextRender() return true; } -void SettingsStringRender::menuElemMethod(int var1) +void SettingsStringRender::menuElemMethod(int action) { if (useColon) { - if (var1 == 1) { - menuManager->processMenu(this); + if (action == 1) { + menuManager->handleMenuSelection(this); return; } } else { - switch (var1) { + switch (action) { case 1: - if (field_146) { + if (isToggleMode) { ++currentOptionPos; if (currentOptionPos > 1) { currentOptionPos = 0; @@ -120,19 +120,19 @@ void SettingsStringRender::menuElemMethod(int var1) selectedOptionName = "On"; } - menuManager->processMenu(this); + menuManager->handleMenuSelection(this); return; } - field_147 = true; - menuManager->processMenu(this); + isSelectionConfirmed = true; + menuManager->handleMenuSelection(this); return; case 2: - if (field_146) { + if (isToggleMode) { if (currentOptionPos == 1) { currentOptionPos = 0; selectedOptionName = "On"; - menuManager->processMenu(this); + menuManager->handleMenuSelection(this); } return; @@ -142,17 +142,17 @@ void SettingsStringRender::menuElemMethod(int var1) if (currentOptionPos > static_cast(optionsList.size()) - 1) { currentOptionPos = optionsList.size() - 1; } else { - menuManager->processMenu(this); + menuManager->handleMenuSelection(this); } selectCurrentOptionName(); return; case 3: - if (field_146) { + if (isToggleMode) { if (currentOptionPos == 0) { currentOptionPos = 1; selectedOptionName = "Off"; - menuManager->processMenu(this); + menuManager->handleMenuSelection(this); } return; @@ -163,7 +163,7 @@ void SettingsStringRender::menuElemMethod(int var1) currentOptionPos = 0; } else { selectCurrentOptionName(); - menuManager->processMenu(this); + menuManager->handleMenuSelection(this); } selectCurrentOptionName(); @@ -192,7 +192,7 @@ void SettingsStringRender::render(Graphics* graphics, int y, int x) } else { graphics->drawString(text, x, y, 20); int shiftedX = x + graphics->getFont()->stringWidth(text); - if (currentOptionPos > maxAvailableOption && !field_146) { + if (currentOptionPos > maxAvailableOption && !isToggleMode) { micro->gameCanvas->drawSprite(graphics, 8, shiftedX + 1, y - GameCanvas::spriteSizeY[8] / 2 + graphics->getFont()->getHeight() / 2); shiftedX += GameCanvas::spriteSizeX[9] + 1; } @@ -254,33 +254,38 @@ int SettingsStringRender::getCurrentOptionPos() return currentOptionPos; } -GameMenu* SettingsStringRender::getGameMenu() +GameMenu* SettingsStringRender::getCurrentMenu() { return currentGameMenu; } -void SettingsStringRender::method_1(GameMenu* var1, bool var2) +GameMenu* SettingsStringRender::getParentGameMenu() { - (void)var1; - (void)var2; + return parentGameMenu; } -void SettingsStringRender::saveSmthToRecordStoreAndCloseIt() +void SettingsStringRender::switchToMenu(GameMenu* menu, bool skipSelectionReset) { + (void)menu; + (void)skipSelectionReset; } -void SettingsStringRender::processMenu(IGameMenuElement* var1) +void SettingsStringRender::saveStateAndCloseRecordStore() { - for (int var2 = 0; var2 < static_cast(settingsStringRenders.size()); ++var2) { - if (var1 == settingsStringRenders[var2]) { - currentOptionPos = var2; +} + +void SettingsStringRender::handleMenuSelection(IGameMenuElement* element) +{ + for (int i = 0; i < static_cast(settingsStringRenders.size()); ++i) { + if (element == settingsStringRenders[i]) { + currentOptionPos = i; selectCurrentOptionName(); break; } } - menuManager->method_1(parentGameMenu, true); - menuManager->processMenu(this); + menuManager->switchToMenu(parentGameMenu, true); + menuManager->handleMenuSelection(this); } std::vector SettingsStringRender::getSettingsStringRenders() @@ -288,12 +293,12 @@ std::vector SettingsStringRender::getSettingsStringRender return settingsStringRenders; } -bool SettingsStringRender::method_114() +bool SettingsStringRender::checkAndClearSelectionFlag() { - if (field_147) { - field_147 = false; + if (isSelectionConfirmed) { + isSelectionConfirmed = false; return true; } else { - return field_147; + return isSelectionConfirmed; } } diff --git a/src/SettingsStringRender.h b/src/SettingsStringRender.h index f7953fd..2f63ee1 100644 --- a/src/SettingsStringRender.h +++ b/src/SettingsStringRender.h @@ -19,8 +19,10 @@ class SettingsStringRender : public IMenuManager, public IGameMenuElement { IMenuManager* menuManager; GameMenu* currentGameMenu = nullptr; GameMenu* parentGameMenu = nullptr; - bool field_146; - bool field_147 = false; + // Is On/Off toggle mode (vs multi-option) + bool isToggleMode; + // Selection confirmed flag + bool isSelectionConfirmed = false; std::string selectedOptionName; Micro* micro = nullptr; std::vector settingsStringRenders; @@ -31,14 +33,14 @@ class SettingsStringRender : public IMenuManager, public IGameMenuElement { void selectCurrentOptionName(); public: - SettingsStringRender(std::string text, int isDisabled, IMenuManager* menuManager, std::vector optionsList, bool var5, Micro* micro, GameMenu* gameMenu, bool useColon); + SettingsStringRender(std::string text, int isDisabled, IMenuManager* menuManager, std::vector optionsList, bool isToggle, Micro* micro, GameMenu* gameMenu, bool useColon); void setFlags(bool hasSprite, bool isDrawSprite8); - void setOptionsList(std::vector var1); + void setOptionsList(std::vector options); void init(); void setParentGameMenu(GameMenu* parentGameMenu); void setText(std::string text); bool isNotTextRender(); - void menuElemMethod(int var1); + void menuElemMethod(int action); void render(Graphics* graphics, int y, int x); void setAvailableOptions(int maxAvailableOption); int getMaxAvailableOptionPos(); @@ -46,10 +48,12 @@ class SettingsStringRender : public IMenuManager, public IGameMenuElement { std::vector getOptionsList(); void setCurentOptionPos(int pos); int getCurrentOptionPos(); - GameMenu* getGameMenu(); - void method_1(GameMenu* var1, bool var2); - void saveSmthToRecordStoreAndCloseIt(); - void processMenu(IGameMenuElement* var1); + GameMenu* getCurrentMenu(); + GameMenu* getParentGameMenu(); + void switchToMenu(GameMenu* menu, bool skipSelectionReset); + void saveStateAndCloseRecordStore(); + void handleMenuSelection(IGameMenuElement* element); std::vector getSettingsStringRenders(); - bool method_114(); + // Returns true if selection was confirmed, then clears flag + bool checkAndClearSelectionFlag(); }; diff --git a/src/TextRender.cpp b/src/TextRender.cpp index a4a8a87..d8f38bf 100644 --- a/src/TextRender.cpp +++ b/src/TextRender.cpp @@ -5,10 +5,10 @@ #include "lcdui/Font.h" #include "lcdui/Graphics.h" -TextRender::TextRender(std::string text, Micro* var2) +TextRender::TextRender(std::string text, Micro* micro) { this->text = text; - micro = var2; + this->micro = micro; isDrawSprite = false; spriteNo = 0; font = nullptr; @@ -45,9 +45,9 @@ bool TextRender::isNotTextRender() return false; } -void TextRender::menuElemMethod(int var1) +void TextRender::menuElemMethod(int index) { - (void)var1; + (void)index; // UNUSED: parameter not used in implementation } void TextRender::render(Graphics* graphics, int y, int x) @@ -70,19 +70,19 @@ std::vector TextRender::makeMultilineTextRenders(std::string text, { std::size_t startPos = 0; std::size_t endPos = 0; - int8_t var4 = 25; + int8_t padding = 25; std::vector vector; for (; endPos < text.length(); startPos = ++endPos - 1) { - std::size_t var6; - if ((var6 = text.find(" ", startPos)) == std::string::npos) { - endPos = var6 = text.length(); + std::size_t spacePos; + if ((spacePos = text.find(" ", startPos)) == std::string::npos) { + endPos = spacePos = text.length(); } - while (endPos < text.length() && defaultFont->substringWidth(text, startPos, var6 - startPos) < fieldMaxWidth - var4) { - endPos = var6 + 1; - if ((var6 = text.find(" ", var6 + 1)) == std::string::npos) { - if (defaultFont->substringWidth(text, startPos, text.length() - 1 - startPos) <= fieldMaxWidth - var4) { + while (endPos < text.length() && defaultFont->substringWidth(text, startPos, spacePos - startPos) < fieldMaxWidth - padding) { + endPos = spacePos + 1; + if ((spacePos = text.find(" ", spacePos + 1)) == std::string::npos) { + if (defaultFont->substringWidth(text, startPos, text.length() - 1 - startPos) <= fieldMaxWidth - padding) { endPos = text.length(); } break; @@ -95,9 +95,9 @@ std::vector TextRender::makeMultilineTextRenders(std::string text, return vector; } -void TextRender::setDx(int var1) +void TextRender::setDx(int dx) { - dx = var1; + this->dx = dx; } void TextRender::setDrawSprite(bool isDrawSprite, int spriteNo) diff --git a/src/TextRender.h b/src/TextRender.h index 7e73cc8..9b678df 100644 --- a/src/TextRender.h +++ b/src/TextRender.h @@ -23,16 +23,16 @@ class TextRender : public IGameMenuElement { Micro* micro; public: - TextRender(std::string text, Micro* var2); + TextRender(std::string text, Micro* micro); static int getBaselinePosition(); void setFont(std::shared_ptr font); static void setDefaultFont(std::shared_ptr font); static void setMaxArea(int w, int h); void setText(std::string text); bool isNotTextRender(); - void menuElemMethod(int var1); + void menuElemMethod(int index); void render(Graphics* graphics, int y, int x); static std::vector makeMultilineTextRenders(std::string text, Micro* micro); - void setDx(int var1); + void setDx(int dx); void setDrawSprite(bool isDrawSprite, int spriteNo); }; diff --git a/src/TimerOrMotoPartOrMenuElem.cpp b/src/TimerOrMotoPartOrMenuElem.cpp deleted file mode 100644 index 48e4c68..0000000 --- a/src/TimerOrMotoPartOrMenuElem.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "TimerOrMotoPartOrMenuElem.h" - -#include "IMenuManager.h" -#include "GameMenu.h" -#include "lcdui/Graphics.h" - -TimerOrMotoPartOrMenuElem::TimerOrMotoPartOrMenuElem() -{ - setToZeros(); -} - -TimerOrMotoPartOrMenuElem::TimerOrMotoPartOrMenuElem(int timerNo, Micro* micro) -{ - this->micro = micro; - this->timerNo = timerNo; -} - -TimerOrMotoPartOrMenuElem::TimerOrMotoPartOrMenuElem(std::string text, GameMenu* gameMenu, IMenuManager* menuManager) -{ - this->text = text + ">"; - this->gameMenu = gameMenu; - this->menuManager = menuManager; -} - -void TimerOrMotoPartOrMenuElem::setToZeros() -{ - xF16 = yF16 = angleF16 = 0; - field_382 = field_383 = field_384 = 0; - field_385 = field_386 = field_387 = 0; -} - -void TimerOrMotoPartOrMenuElem::setText(std::string text) -{ - this->text = text + ">"; -} - -std::string TimerOrMotoPartOrMenuElem::getText() -{ - return text; -} - -bool TimerOrMotoPartOrMenuElem::isNotTextRender() -{ - return true; -} - -void TimerOrMotoPartOrMenuElem::menuElemMethod(int var1) -{ - switch (var1) { - case 1: - case 2: - menuManager->processMenu(this); - gameMenu->setGameMenu(menuManager->getGameMenu()); - menuManager->method_1(gameMenu, false); - case 3: - default: - break; - } -} - -void TimerOrMotoPartOrMenuElem::setGameMenu(GameMenu* gameMenu) -{ - this->gameMenu = gameMenu; -} - -void TimerOrMotoPartOrMenuElem::render(Graphics* graphics, int y, int x) -{ - graphics->drawString(text, x, y, 20); -} \ No newline at end of file diff --git a/src/TimerOrMotoPartOrMenuElem.h b/src/TimerOrMotoPartOrMenuElem.h deleted file mode 100644 index 8e1254d..0000000 --- a/src/TimerOrMotoPartOrMenuElem.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -#include "Micro.h" -#include "IGameMenuElement.h" - -class Graphics; -class GameMenu; -class IMenuManager; - -class TimerOrMotoPartOrMenuElem : public IGameMenuElement { -private: - std::string text; - GameMenu* gameMenu; - IMenuManager* menuManager; - -public: - int xF16; - int yF16; - int angleF16; - int field_382; - int field_383; - int field_384; - int field_385; - int field_386; - int field_387; - int timerNo; - Micro* micro; - - TimerOrMotoPartOrMenuElem(); - TimerOrMotoPartOrMenuElem(int timerNo, Micro* micro); - TimerOrMotoPartOrMenuElem(std::string text, GameMenu* gameMenu, IMenuManager* menuManager); - void setToZeros(); - void setText(std::string text); - std::string getText(); - bool isNotTextRender(); - void menuElemMethod(int var1); - void setGameMenu(GameMenu* gameMenu); - void render(Graphics* graphics, int y, int x); -}; \ No newline at end of file diff --git a/src/class_10.cpp b/src/class_10.cpp deleted file mode 100644 index a454d49..0000000 --- a/src/class_10.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "class_10.h" - -class_10::class_10() -{ - for (int var1 = 0; var1 < 6; ++var1) { - motoComponents[var1] = std::make_unique(); - } - - reset(); -} - -class_10::~class_10() -{ - // -} - -void class_10::reset() -{ - field_257 = field_259 = field_260 = 0; - unusedBool = true; - for (int i = 0; i < 6; ++i) { - motoComponents[i]->setToZeros(); - } -} diff --git a/src/class_10.h b/src/class_10.h deleted file mode 100644 index b72d09f..0000000 --- a/src/class_10.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include - -#include "TimerOrMotoPartOrMenuElem.h" - -class class_10 { -public: - bool unusedBool; - int field_257; - int field_258; - int field_259; - int field_260; - std::vector> motoComponents = std::vector>(6); - - class_10(); - ~class_10(); - void reset(); -}; \ No newline at end of file