Enemy Death Animation

I’m having an issue with Death Animation of EnemyA. It is a spritesheet with 8 frames and sometimes it completes one full cycle and sometimes it doesn’t. I also have a MAP CASE where press Space to continue to next level does not work. Any Advice? Game Project

#include <SDL.h>
#include <SDL_ttf.h>
#include <SDL_image.h>
#include <SDL_mixer.h>
#include <iostream>
#include <memory>
#include <vector>
#include "Game.h"
#include "Button.h"
#include "Story.h"
#include "StoryLines.h"
#include "Sprite.h"
#include "TextureManager.h"
#include "SoundManager.h"
#include "GameState.h"
#include "Combat.h"

// Constants
constexpr int FRAME_DELAY = 16; // Approximately 60 FPS
constexpr int FONT_SIZE = 30;
constexpr int TEXT_WRAP_WIDTH = 600;

// Volume bar constants
constexpr int VOLUME_BAR_WIDTH = 300;
constexpr int VOLUME_BAR_HEIGHT = 20;
constexpr SDL_Color VOLUME_BAR_COLOR = { 0, 255, 0, 255 };
constexpr SDL_Color VOLUME_BAR_BACKGROUND_COLOR = { 100, 100, 100, 255 };

// Declare global objects
TTF_Font* storylineFont = nullptr;
TTF_Font* defaultFont = nullptr;
SDL_Texture* menuBackdropTexture = nullptr;
SDL_Texture* speechBubbleTexture = nullptr;
SDL_Texture* storyBackdropTexture = nullptr;
SDL_Texture* mapBackdropTexture = nullptr;
Sprite* Knight_idle = nullptr;
SoundManager soundManager;
SDL_Renderer* renderer = nullptr;

struct KnightPosition {
    int x;
    int y;
    int width;
    int height;
};

//Other codes

    // Load textures
    menuBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/menu_backdrop.png", renderer);
    speechBubbleTexture = TextureManager::LoadTexture("assets/SpeechStuff/speech_bubble.png", renderer);
    mapBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/Map.png", renderer);
    if (!mapBackdropTexture) {
        SDL_Log("Failed to load map backdrop texture");
    }

    // Initialize Knight_idle as a Sprite
    Knight_idle = new Sprite(renderer, "assets/Sprites/MC/idle.png");
    if (!Knight_idle->loadTexture()) {
        SDL_Log("Failed to load Knight idle sprite.");
        return 1;
    }
    Knight_idle->setAnimationParameters(128, 126, 8, 0.1f);

    if (!menuBackdropTexture || !speechBubbleTexture) {
        SDL_Log("Failed to load textures.");
        TTF_CloseFont(defaultFont);
        TTF_CloseFont(storylineFont);
        soundManager.shutdown();
        TTF_Quit();
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Load music
    if (!soundManager.loadMusic("assets/Music/background_music.wav", "background")) {
        SDL_Log("Failed to load background music.");
    }
    if (!soundManager.loadMusic("assets/Music/story_music.wav", "story")) {
        SDL_Log("Failed to load story music.");
    }
    if (!soundManager.loadMusic("assets/Music/combat_music.wav", "combat")) {
        SDL_Log("Failed to load combat music.");
    }
    if (!soundManager.loadMusic("assets/Music/Lvl1_Story.wav", "lvl1_story")) {
        SDL_Log("Failed to load Level 1 Story music.");
    }
    if (!soundManager.loadMusic("assets/Music/Lvl2_Story.wav", "lvl2_story")) {
        SDL_Log("Failed to load Level 2 Story music.");
    }

    // Initialize story instances
    static Story storyInstance(renderer, storylineFont, storyLines, 50);
    static Story level1StoryInstance(renderer, storylineFont, level1StoryLines, 50);
    static Story level1WinInstance(renderer, storylineFont, level1Win, 50);
    static Story level1LoseInstance(renderer, storylineFont, level1Lose, 50);
    static Story level2StoryInstance(renderer, storylineFont, level2StoryLines, 50);
    static Story level2WinInstance(renderer, storylineFont, level2Win, 50);
    static Story level2LoseInstance(renderer, storylineFont, level2Lose, 50);

    // Button setup
    constexpr int BUTTON_WIDTH = 180;
    constexpr int ABOUT_BUTTON_WIDTH = 250;
    constexpr int BUTTON_HEIGHT = 70;
    constexpr int BUTTON_SPACING = 50;
    constexpr int NUM_BUTTONS = 4;

    int windowWidth, windowHeight;
    SDL_GetRendererOutputSize(renderer, &windowWidth, &windowHeight);
    int totalButtonHeight = NUM_BUTTONS * BUTTON_HEIGHT + (NUM_BUTTONS - 1) * BUTTON_SPACING;
    int startY = (windowHeight - totalButtonHeight) / 2 + 50;

    Button buttons[NUM_BUTTONS] = {
        Button((windowWidth - BUTTON_WIDTH) / 2, startY, BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/start button.png", "assets/Buttons/clicked sta2.png", "assets/Buttons/clicked sta2.png", renderer),
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/tutorial button.png", "assets/Buttons/clicked tutorial2.png", "assets/Buttons/clicked tutorial2.png", renderer),
        Button((windowWidth - BUTTON_WIDTH) / 2, startY + 2 * (BUTTON_HEIGHT + BUTTON_SPACING), BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/option button.png", "assets/Buttons/option_clicked.png", "assets/Buttons/option_clicked.png", renderer),
        Button((windowWidth - ABOUT_BUTTON_WIDTH) / 2, startY + 3 * (BUTTON_HEIGHT + BUTTON_SPACING), ABOUT_BUTTON_WIDTH, BUTTON_HEIGHT, "assets/Buttons/about develper button.png", "assets/Buttons/clicked ab de 2.png", "assets/Buttons/clicked ab de 2.png", renderer)
    };

    buttons[0].setOnClick([&]() {
        gameState = STORY;
        storyInstance.reset();
        storyBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/castle/castle1.jpg", renderer);
        soundManager.stopMusic();
        soundManager.playMusic("story", -1);
        });

    buttons[1].setOnClick([&]() {
        gameState = TUTORIAL;
        });

    buttons[2].setOnClick([&]() {
        gameState = OPTIONS;
        });

    buttons[3].setOnClick([&]() {
        gameState = ABOUT;
        });

    int musicVolume = MIX_MAX_VOLUME;
    SDL_Texture* combatBackdropTexture = nullptr;

    // Main Game Loop
    while (gameState != EXIT) {
        double deltaTime = getDeltaTime();
        frameStart = SDL_GetTicks();
        SDL_Event event;

        // Check if the game state has changed
        if (gameState != previousGameState) {
            // Print the new game state name
            switch (gameState) {
            case MENU:
                printf("Current game state: MENU\n");
                break;
            case STORY:
                printf("Current game state: STORY\n");
                break;
            case TUTORIAL:
                printf("Current game state: TUTORIAL\n");
                break;
            case OPTIONS:
                printf("Current game state: OPTIONS\n");
                break;
            case ABOUT:
                printf("Current game state: ABOUT\n");
                break;
            case PLAYING:
                printf("Current game state: PLAYING\n");
                break;
            case LEVEL1_WIN:
                printf("Current game state: LEVEL1_WIN\n");
                break;
            case LEVEL1_LOSE:
                printf("Current game state: LEVEL1_LOSE\n");
                break;
            case LEVEL1_STORY:
                printf("Current game state: LEVEL1_STORY\n");
                break;
            case LEVEL2_WIN:
                printf("Current game state: LEVEL2_WIN\n");
                break;
            case LEVEL2_LOSE:
                printf("Current game state: LEVEL2_LOSE\n");
                break;
            case LEVEL2_STORY:
                printf("Current game state: LEVEL2_STORY\n");
                break;
            case MAP:
                printf("Current game state: MAP\n");
                break;
            case EXIT:
                printf("Current game state: EXIT\n");
                break;
            default:
                printf("Current game state: UNKNOWN\n");
                break;
            }
            // Update the previous game state
            previousGameState = gameState;
        }

        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT || (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) {
                gameState = EXIT;
            }

            // Handle returning to menu
            if ((gameState == TUTORIAL || gameState == OPTIONS || gameState == ABOUT ||
                gameState == LEVEL1_LOSE || gameState == LEVEL2_LOSE) && event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_b) {
                    gameState = MENU;
                    for (int i = 0; i < NUM_BUTTONS; ++i) {
                        buttons[i].resetState();
                    }
                }
            }

            // Handle story progression
            if ((gameState == STORY || gameState == LEVEL1_STORY || gameState == LEVEL1_WIN ||
                gameState == LEVEL1_LOSE || gameState == LEVEL2_WIN || gameState == LEVEL2_LOSE || gameState == MAP) &&
                event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_SPACE) {

                if (gameState == STORY && storyInstance.isComplete()) {
                    soundManager.stopMusic();
                    gameState = LEVEL1_STORY;
                    level1StoryInstance.reset();
                    SDL_DestroyTexture(storyBackdropTexture);
                    storyBackdropTexture = nullptr;
                    soundManager.playMusic("lvl1_story", -1);
                }
                else if (gameState == LEVEL1_STORY && level1StoryInstance.isComplete()) {
                    combatBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/forest/forest2.jpg", renderer);
                    gameState = PLAYING;
                    initCombatWithRenderer(1, renderer);
                    soundManager.playMusic("combat", -1);
                }
                else if (gameState == LEVEL1_WIN && level1WinInstance.isComplete()) {
                    gameState = MAP;
                    currentLevel = 2; // Only increment level when going to map after win
                    soundManager.stopMusic();
                    soundManager.playMusic("story", -1);
                }
                else if (gameState == LEVEL2_WIN && level2WinInstance.isComplete()) {
                    gameState = MAP;
                    soundManager.stopMusic();
                    soundManager.playMusic("story", -1);
                }
                else if (gameState == STORY) {
                    storyInstance.advanceToNextLine();
                }
                else if (gameState == LEVEL1_STORY) {
                    level1StoryInstance.advanceToNextLine();
                }
                else if (gameState == LEVEL1_LOSE) {
                    level1LoseInstance.advanceToNextLine();
                }
                else if (gameState == LEVEL2_LOSE) {
                    level2LoseInstance.advanceToNextLine();
                }
                else if (gameState == MAP) {
                    renderText(renderer, defaultFont, "Press SPACE to continue", { 255, 255, 255, 255 },
                        (windowWidth - 300) / 2, windowHeight - 50);
                    if (currentLevel == 1) {
                        currentLevel = 2;  // Only increment level when in MAP state and SPACE is pressed
                        combatBackdropTexture = TextureManager::LoadTexture("assets/Backdrops/snow land/snow land4.jpg", renderer);
                        gameState = PLAYING;
                        initCombatWithRenderer(2, renderer);
                        soundManager.playMusic("combat", -1);
                    }
                    else if (currentLevel == 2) {
                        currentLevel = 3;  // Increment to next level
                    }
                }
            }

            // Handle buttons
            if (gameState == MENU) {
                for (int i = 0; i < NUM_BUTTONS; ++i) {
                    buttons[i].handleEvent(&event);
                }
            }

            if (gameState == OPTIONS) {
                if (event.type == SDL_KEYDOWN) {
                    switch (event.key.keysym.sym) {
                    case SDLK_UP:
                        if (musicVolume < MIX_MAX_VOLUME) {
                            musicVolume += 8;
                            if (musicVolume > MIX_MAX_VOLUME) musicVolume = MIX_MAX_VOLUME;
                            Mix_VolumeMusic(musicVolume);
                        }
                        break;
                    case SDLK_DOWN:
                        if (musicVolume > 0) {
                            musicVolume -= 8;
                            if (musicVolume < 0) musicVolume = 0;
                            Mix_VolumeMusic(musicVolume);
                        }
                        break;
                    }
                }
            }

            // Handle combat
            if (gameState == PLAYING) {
                CombatContext context = {
                    renderer,
                    defaultFont,
                    {255, 255, 255, 255},
                    {255, 0, 0, 255},
                    {0, 0, 255, 255},
                    {0, 0, 0, 255}
                };
                handleCombatEvent(&event, &context);

                if (gameOver) {
                    printf("Game over detected\n");
                    SDL_DestroyTexture(combatBackdropTexture);
                    combatBackdropTexture = nullptr;
                    if (currentLevel == 1) {
                        gameState = LEVEL1_LOSE;
                        level1LoseInstance.reset();
                    }
                    else if (currentLevel == 2) {
                        gameState = LEVEL2_LOSE;
                        level2LoseInstance.reset();
                    }
                    soundManager.stopMusic();
                    soundManager.playMusic("story", -1);
                }
                else if (victory1) {
                    printf("Victory 1 detected\n");
                    SDL_DestroyTexture(combatBackdropTexture);
                    combatBackdropTexture = nullptr;
                    gameState = LEVEL1_WIN;
                    level1WinInstance.reset();
                    soundManager.stopMusic();
                    soundManager.playMusic("story", -1);
                    currentLevel = 1; // Make sure we don't increment level here
                }
                else if (victory2) {
                    printf("Victory 2 detected\n");
                    SDL_DestroyTexture(combatBackdropTexture);
                    combatBackdropTexture = nullptr;
                    gameState = LEVEL2_WIN;
                    level2WinInstance.reset();
                    soundManager.playMusic("lvl2_story", -1);
                }
            }
        }

        // Update knight animation with proper delta time
        if (Knight_idle) {
            Knight_idle->updateAnimation(static_cast<float>(deltaTime));
        }

        // Rendering
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);

        switch (gameState) {
        case MENU:
            if (!soundManager.isPlayingMusic()) {
                soundManager.playMusic("background", -1);
            }
            SDL_RenderCopy(renderer, menuBackdropTexture, nullptr, nullptr);
            for (int i = 0; i < NUM_BUTTONS; ++i) {
                buttons[i].render(renderer);
            }
            break;

        case STORY: {
            if (storyBackdropTexture) {
                SDL_RenderCopy(renderer, storyBackdropTexture, nullptr, nullptr);
            }
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2,
                windowHeight - 400,
                800,
                300
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);
            storyInstance.update(SDL_GetTicks());
            storyInstance.render(renderer,
                speechBubbleRect.x + 50,
                speechBubbleRect.y + 90,
                TEXT_WRAP_WIDTH);
            if (storyInstance.isComplete()) {
                renderText(renderer, defaultFont, "Press SPACE to continue", { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case LEVEL1_STORY: {
            static SDL_Texture* level1StoryBackdrop = nullptr;
            if (!level1StoryBackdrop) {
                level1StoryBackdrop = TextureManager::LoadTexture("assets/SpeechStuff/Forestwithspeech.png", renderer);
            }
            if (level1StoryBackdrop) {
                SDL_RenderCopy(renderer, level1StoryBackdrop, nullptr, nullptr);
            }

            if (Knight_idle) {
                SDL_Rect knightRect = {
                    ((windowWidth - 200) / 2) - 380,
                    windowHeight - 750,
                    200,
                    300
                };
                Knight_idle->render(knightRect.x, knightRect.y, knightRect.w, knightRect.h);
            }

            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2,
                windowHeight - 400,
                800,
                300
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);
            level1StoryInstance.update(SDL_GetTicks());
            level1StoryInstance.render(renderer,
                speechBubbleRect.x + 50,
                speechBubbleRect.y + 90,
                TEXT_WRAP_WIDTH);
            if (level1StoryInstance.isComplete()) {
                renderText(renderer, defaultFont, "Press SPACE to continue", { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case PLAYING: {
            if (combatBackdropTexture) {
                SDL_RenderCopy(renderer, combatBackdropTexture, nullptr, nullptr);
            }

            // Render Knight_idle based on the current level
            if (Knight_idle) {
                int currentLevelIndex = currentLevel - 1;
                SDL_Rect knightRect = {
                    knightPositions[currentLevelIndex].x,
                    knightPositions[currentLevelIndex].y,
                    knightPositions[currentLevelIndex].width,
                    knightPositions[currentLevelIndex].height
                };
                Knight_idle->updateAnimation(static_cast<float>(deltaTime));
                Knight_idle->render(knightRect.x, knightRect.y, knightRect.w, knightRect.h);
            }

            // Update enemy animation
            if (currentEnemy) {
                currentEnemy->updateAnimation(static_cast<float>(deltaTime));

                // Check if attack animation is complete
                if (currentEnemy->getAnimationState() == EnemyState::ATTACK &&
                    currentEnemy->isAnimationComplete()) {
                    currentEnemy->setAnimationState(EnemyState::IDLE);
                }

                // Check if enemy is dead
                if (currentEnemy->getHealth() <= 0) {
                    currentEnemy->setAnimationState(EnemyState::DEATH);

                    // Use a timer to manage the delay
                    static Uint32 deathTimer = SDL_GetTicks();
                    const Uint32 DEATH_DELAY = 3000; // 3 seconds delay

                    if (SDL_GetTicks() - deathTimer >= DEATH_DELAY) {
                        // Move to the next level or end the game
                        if (currentLevel == 1) {
                            gameState = LEVEL1_WIN;
                            level1WinInstance.reset();
                        }
                        else if (currentLevel == 2) {
                            gameState = LEVEL2_WIN;
                            level2WinInstance.reset();
                        }
                        else if (currentLevel == 3) {
                            // Handle level 3 win
                        }

                        // Cleanup combat resources
                        cleanupCombat();
                    }
                }
            }

            // Render combat UI elements
            CombatContext context = {
                renderer,
                defaultFont,
                {255, 255, 255, 255},
                {255, 0, 0, 255},
                {0, 0, 255, 255},
                {0, 0, 0, 255}
            };
            renderCombat(&context);
            break;
        }

        case LEVEL1_WIN: {
            static SDL_Texture* level1StoryBackdrop = nullptr;
            if (!level1StoryBackdrop) {
                level1StoryBackdrop = TextureManager::LoadTexture("assets/Backdrops/castle/castle1.jpg", renderer);
            }
            if (level1StoryBackdrop) {
                SDL_RenderCopy(renderer, level1StoryBackdrop, nullptr, nullptr);
            }

            static bool messageDisplayed = false;
            if (!messageDisplayed) {
                renderText(renderer, defaultFont, "Press SPACE to continue", { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
                messageDisplayed = true;
            }

            while (SDL_PollEvent(&event)) {
                if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_SPACE) {
                    messageDisplayed = false;
                    gameState = MAP;
                }
            }

            break;
        }

        case LEVEL1_LOSE: {
            static SDL_Texture* level1StoryBackdrop = nullptr;
            if (!level1StoryBackdrop) {
                level1StoryBackdrop = TextureManager::LoadTexture("assets/Backdrops/castle/castle1.jpg", renderer);
            }
            if (level1StoryBackdrop) {
                SDL_RenderCopy(renderer, level1StoryBackdrop, nullptr, nullptr);
            }
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2,
                windowHeight - 400,
                800,
                300
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);
            level1LoseInstance.update(SDL_GetTicks());
            level1LoseInstance.render(renderer,
                speechBubbleRect.x + 50,
                speechBubbleRect.y + 90,
                TEXT_WRAP_WIDTH);
            if (level1LoseInstance.isComplete()) {
                renderText(renderer, defaultFont, "Press SPACE to return to menu", { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case LEVEL2_WIN: {
            static SDL_Texture* level2WinBackdrop = nullptr;
            if (!level2WinBackdrop) {
                level2WinBackdrop = TextureManager::LoadTexture("assets/Backdrops/snow land/snow land4.jpg", renderer);
            }
            if (level2WinBackdrop) {
                SDL_RenderCopy(renderer, level2WinBackdrop, nullptr, nullptr);
            }
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2,
                windowHeight - 400,
                800,
                300
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);
            level2WinInstance.update(SDL_GetTicks());
            level2WinInstance.render(renderer,
                speechBubbleRect.x + 50,
                speechBubbleRect.y + 90,
                TEXT_WRAP_WIDTH);
            if (level2WinInstance.isComplete()) {
                renderText(renderer, defaultFont, "Press SPACE to continue", { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case LEVEL2_LOSE: {
            static SDL_Texture* level2LoseBackdrop = nullptr;
            if (!level2LoseBackdrop) {
                level2LoseBackdrop = TextureManager::LoadTexture("assets/Backdrops/snow land/snow land4.jpg", renderer);
            }
            if (level2LoseBackdrop) {
                SDL_RenderCopy(renderer, level2LoseBackdrop, nullptr, nullptr);
            }
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2,
                windowHeight - 400,
                800,
                300
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);
            level2LoseInstance.update(SDL_GetTicks());
            level2LoseInstance.render(renderer,
                speechBubbleRect.x + 50,
                speechBubbleRect.y + 90,
                TEXT_WRAP_WIDTH);
            if (level2LoseInstance.isComplete()) {
                renderText(renderer, defaultFont, "Press SPACE to return to menu", { 255, 255, 255, 255 },
                    (windowWidth - 300) / 2, windowHeight - 50);
            }
            break;
        }

        case TUTORIAL: {
            SDL_Rect speechBubbleRect = {
                (windowWidth - 800) / 2,
                windowHeight - 400,
                800,
                300
            };
            SDL_RenderCopy(renderer, speechBubbleTexture, nullptr, &speechBubbleRect);
            break;
        }

        case OPTIONS: {
            const std::string optionsMessage =
                "Options Menu:\n"
                "Here you can configure game settings such as audio volume.\n"
                "Use the Up/Down arrow keys to adjust music volume.";
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, optionsMessage, { 255, 255, 255, 255 },
                (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 },
                (windowWidth - 300) / 2, windowHeight - 50);

            std::string musicVolumeText = "Music Volume: " + std::to_string(musicVolume);
            renderText(renderer, defaultFont, musicVolumeText, { 255, 255, 255, 255 },
                (windowWidth - 300) / 2, windowHeight - 150);

            int volumeBarX = (windowWidth - VOLUME_BAR_WIDTH) / 2;
            int volumeBarY = windowHeight - 100;
            SDL_Rect volumeBarBackground = { volumeBarX, volumeBarY, VOLUME_BAR_WIDTH, VOLUME_BAR_HEIGHT };
            SDL_SetRenderDrawColor(renderer, VOLUME_BAR_BACKGROUND_COLOR.r, VOLUME_BAR_BACKGROUND_COLOR.g,
                VOLUME_BAR_BACKGROUND_COLOR.b, VOLUME_BAR_BACKGROUND_COLOR.a);
            SDL_RenderFillRect(renderer, &volumeBarBackground);
            int filledWidth = (musicVolume * VOLUME_BAR_WIDTH) / MIX_MAX_VOLUME;
            SDL_Rect volumeBarFilled = { volumeBarX, volumeBarY, filledWidth, VOLUME_BAR_HEIGHT };
            SDL_SetRenderDrawColor(renderer, VOLUME_BAR_COLOR.r, VOLUME_BAR_COLOR.g,
                VOLUME_BAR_COLOR.b, VOLUME_BAR_COLOR.a);
            SDL_RenderFillRect(renderer, &volumeBarFilled);
            break;
        }

        case ABOUT: {
            const std::string aboutMessage =
                "Knight Revenger is a story-driven RPG built using SDL in C++, where you play as a wrongfully exiled Knight on a quest to reclaim your honor and uncover the truth behind your expulsion. Featuring challenging gameplay, memorable characters, and immersive visuals and music, the game explores themes of redemption, identity, and justice in a richly crafted world.\n"
                "This project is brought to you by a diverse team of students, blending creativity and collaboration to deliver an unforgettable gaming experience.\n"
                "Thank you for supporting our journey!";
            const std::string returnMessage = "Press B to return to menu";
            renderText(renderer, defaultFont, aboutMessage, { 255, 255, 255, 255 },
                (windowWidth - TEXT_WRAP_WIDTH) / 2, 100, TEXT_WRAP_WIDTH);
            renderText(renderer, defaultFont, returnMessage, { 255, 255, 255, 255 },
                (windowWidth - 300) / 2, windowHeight - 50);
            break;
        }

        case MAP: {
            SDL_RenderCopy(renderer, mapBackdropTexture, nullptr, nullptr);

            // Handle key press events
            while (SDL_PollEvent(&event)) {
                if (event.type == SDL_KEYDOWN) {
                    printf("Key pressed: %d\n", event.key.keysym.sym);
                    if (event.key.keysym.sym == SDLK_SPACE) {
                        printf("Space bar pressed. Current level: %d\n", currentLevel);

                        // Initialize the next level
                        if (currentLevel == 1) {
                            printf("Transitioning to Level 2.\n");
                            currentLevel = 2;
                            initCombatWithRenderer(currentLevel, renderer);
                            // Set up EnemyB for Level 2
                            currentEnemy = new EnemyB(renderer);
                            currentEnemy->initSprite(renderer);
                        }
                        else if (currentLevel == 2) {
                            printf("Transitioning to Level 3.\n");
                            currentLevel = 3;
                            initCombatWithRenderer(currentLevel, renderer);
                            // Set up EnemyC for Level 3
                            currentEnemy = new EnemyC(renderer);
                            currentEnemy->initSprite(renderer);
                        }

                        // Transition to the PLAYING state
                        gameState = PLAYING;
                        printf("Transitioned to PLAYING state.\n");
                    }
                }
            }

            break;
        }
        }

        SDL_RenderPresent(renderer);
        frameTime = SDL_GetTicks() - frameStart;
        if (FRAME_DELAY > frameTime) {
            SDL_Delay(FRAME_DELAY - frameTime);
        }
    }

    // Cleanup
    if (Knight_idle) {
        delete Knight_idle;
        Knight_idle = nullptr;
    }
    cleanupResources();
    return 0;
}
#include "Enemy.h"
#include "Sprite.h"
#include <stdio.h>
#include <cstdlib>

// Animation frame definitions
#define TZ_H (512 / 4) // Width of a single frame (128px)
#define TZ_V (252 / 2) // Height of a single frame (126px)

// Constructor
Enemy::Enemy(int health, const std::string& idleSpritePath, SDL_Renderer* renderer)
    : health(health), sprite(nullptr), renderer(renderer),
    frameWidth(0), frameHeight(0), numFrames(0),
    frameRate(0.1f), frameTimer(0.0f), currentFrame(0),
    currentState(EnemyState::IDLE) {
    animationPaths[EnemyState::IDLE] = idleSpritePath;
    printf("Enemy created with health %d and idle sprite path %s\n", health, idleSpritePath.c_str());
}

// Destructor
Enemy::~Enemy() {
    if (sprite) {
        printf("Deleting enemy sprite\n");
        delete sprite;
    }
}

// Set frame rate
void Enemy::setFrameRate(float rate) {
    frameRate = rate;
    printf("Enemy frame rate set to %.2f\n", frameRate);
}

// Initialize sprite
void Enemy::initSprite(SDL_Renderer* renderer) {
    if (currentState == EnemyState::IDLE) {
        sprite = new Sprite(renderer, animationPaths[EnemyState::IDLE]);
    }
    else if (currentState == EnemyState::ATTACK) {
        sprite = new Sprite(renderer, animationPaths[EnemyState::ATTACK]);
    }
    else if (currentState == EnemyState::DEATH) {
        sprite = new Sprite(renderer, animationPaths[EnemyState::DEATH]);
    }

    if (!sprite->loadTexture()) {
        fprintf(stderr, "Failed to load enemy sprite: %s\n", animationPaths[currentState].c_str());
        delete sprite;
        sprite = nullptr;
        return;
    }
    setupDefaultAnimation(currentState);
    if (sprite) {
        sprite->setAnimationParameters(frameWidth, frameHeight, numFrames, frameRate);
        printf("Enemy sprite initialized with texture at path %s\n", animationPaths[currentState].c_str());
    }
}

// Update animation
void Enemy::updateAnimation(float deltaTime) {
    if (!sprite) {
        printf("Warning: Enemy sprite not initialized!\n");
        return;
    }

    // Ensure animation parameters are set
    if (frameWidth <= 0 || frameHeight <= 0 || numFrames <= 0) {
        setupDefaultAnimation(currentState);
        if (sprite) {
            sprite->setAnimationParameters(frameWidth, frameHeight, numFrames, frameRate);
        }
    }

    frameTimer += deltaTime;
    while (frameTimer >= frameRate) {
        currentFrame = (currentFrame + 1) % numFrames;
        frameTimer -= frameRate;

        // Check if the attack animation has completed a full cycle
        if (currentState == EnemyState::ATTACK && currentFrame == 0) {
            setAnimationState(EnemyState::IDLE); // Transition back to idle state
        }
    }

    if (sprite) {
        sprite->updateAnimation(deltaTime);
    }
}

// Render sprite
void Enemy::render(int x, int y, int width, int height) {
    if (sprite) {
        sprite->render(x, y, width, height);
        printf("Enemy sprite rendered at position (%d, %d) with dimensions (%d, %d)\n", x, y, width, height);
    }
}

// Switch animation state
void Enemy::setAnimationState(EnemyState newState) {
    if (currentState == newState) return;

    currentState = newState;
    const std::string& newPath = animationPaths[currentState];
    if (!newPath.empty()) {
        spritePath = newPath;
        if (sprite) {
            delete sprite;
            sprite = nullptr;
        }
        printf("Switched to animation state: %d\n", static_cast<int>(currentState));
        initSprite(renderer); // Reinitialize the sprite with the new animation
    }
    else {
        fprintf(stderr, "Animation path not set for state: %d\n", static_cast<int>(currentState));
    }
}

// Configure default animation parameters
void Enemy::setupDefaultAnimation(EnemyState state) {
    frameWidth = TZ_H;
    frameHeight = TZ_V;
    numFrames = 8; // All spritesheets have exactly 8 frames

    switch (state) {
    case EnemyState::IDLE:
        frameRate = 0.2f; // Default frame rate (20 FPS)
        break;

    case EnemyState::ATTACK:
        frameRate = 0.15f; // Slower frame rate for attack animation (6.67 FPS)
        break;

    case EnemyState::DEATH:
        frameRate = 0.25f; // Slower frame rate for death animation (4 FPS)
        break;

    default:
        fprintf(stderr, "Unknown animation state: %d\n", static_cast<int>(state));
        break;
    }
}

// EnemyA implementation
EnemyA::EnemyA(SDL_Renderer* renderer)
    : Enemy(30, "assets/Sprites/Enemies/enemy1Idle.png", renderer) {
    animationPaths[EnemyState::ATTACK] = "assets/Sprites/Enemies/enemy1Attack.png";
    animationPaths[EnemyState::DEATH] = "assets/Sprites/Enemies/enemy1Death.png";
    printf("EnemyA created\n");
}

int EnemyA::calculateDamage(int turn) {
    return rand() % 6 + 1;
}

// EnemyB implementation
EnemyB::EnemyB(SDL_Renderer* renderer)
    : Enemy(25, "assets/Sprites/Enemies/enemy2Idle.png", renderer) {
    animationPaths[EnemyState::ATTACK] = "assets/Sprites/Enemies/enemy2Attack.png";
    animationPaths[EnemyState::DEATH] = "assets/Sprites/Enemies/enemy2Death.png";
    printf("EnemyB created\n");
}

int EnemyB::calculateDamage(int turn) {
    return turn * 2;
}

// EnemyC implementation
EnemyC::EnemyC(SDL_Renderer* renderer)
    : Enemy(25, "assets/Sprites/Enemies/enemy3Idle.png", renderer) {
    animationPaths[EnemyState::ATTACK] = "assets/Sprites/Enemies/enemy3Attack.png";
    animationPaths[EnemyState::DEATH] = "assets/Sprites/Enemies/enemy3Death.png";
    printf("EnemyC created\n");
}

int EnemyC::calculateDamage(int turn) {
    return (rand() % 6 + 1);
}

Don’t call SDL_PollEvent in multiple places. See my response to your other thread.

2 Likes

One of the reasonable uses of AI is to brainstorm. It could potentially answer this. Many are free when you’re not using it much. I didn’t read the code but I heard of people deleting or overwriting/recycling the enemy quickly so it doesn’t have time to animate

Have you debugged your code and stepped through it, like I adviced you to in one of your other threads?

1 Like