diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ff9d18..a53249a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,7 +152,8 @@ else() ) endmacro() copy_helper("assets/Inter-VariableFont.ttf") - copy_helper("assets/the_entertainer.ogg") + copy_helper("assets/MartianMono-VariableFont.ttf") + copy_helper("assets/sunset_on_the_beach.ogg") copy_helper("assets/gs_tiger.svg") endif() diff --git a/src/adventura.cpp b/src/adventura.cpp new file mode 100644 index 0000000..be07f14 --- /dev/null +++ b/src/adventura.cpp @@ -0,0 +1,109 @@ +#include +#include + +#include +#include + +#include "world.hpp" +#include "player.hpp" +#include "blockRegistry.hpp" +#include "movementHandler.hpp" +#include "output.hpp" + +using std::string; +using std::cout; +using std::endl; + +bool startWorld(string worldFile); +bool parseArgs(int argc, char *argv[]); + +static bool testMode = false; +static unsigned int worldIndex = 2; + +/** + * Entry point of the program. + * If a world file is provided as an argument, play through that world. + * Otherwise, play through all worlds in the worlds directory. + * In case the player dies during gameplay, exit without printing the victory screen. + * If the player reaches the goal of the final level, print the victory screen and exit. + */ +int start(int argc, char *argv[]) { + if (parseArgs(argc, argv)) return 0; + + if (!testMode) { + printFile("./screens/start.txt", Color::BRIGHT_YELLOW); // Show the story introduction + waitForInput(); + printGuide(); // Show the block guide + waitForInput(); + } + + // Load every world in order + for (const auto & world : getOrderedFileNames("./worlds/", ".txt")) + if (!startWorld(world)) return 0; // If the player dies, exit + + // Print the victory screen once all levels have been completed + printFile("./screens/victory.txt", Color::BRIGHT_GREEN); + + return 0; +} + +/** + * Start a new world defined in the file at worldFile. + * If the player reaches the goal, return true. + * In case they die, print the death screen and return false. + * @return true if the player reached the goal, false in case of death + */ +bool startWorld(string worldFile) { + BlockRegistry blockRegistry = BlockRegistry(); + World world = World(blockRegistry); + + world.loadFromFile(worldFile); + Player player = Player(world.getStartPos(), world); + render(world, player.mapToWorldspace()); + + inputLoop(player, world, testMode, worldIndex); + + worldIndex++; + if (!player.isAlive()) printFile("./screens/death.txt", Color::BRIGHT_RED); + return player.hasReachedGoal(); +} + +/** + * Parses command-line arguments to set the game's configuration. + * + * Recognizes the following arguments: + * - "-h" or "--help": Displays the help screen and exits. + * - "-t" or "--test": Enables test mode, starting an automated playthrough. + * - "-l" or "--level ": Loads and plays only the specified level. + * + * If a custom action is specified, the function returns true to indicate + * immediate termination after executing the requested action. + * + * @param argc The number of command-line arguments. + * @param argv The array containing the command-line arguments. + * @return true if the program should exit after handling the arguments, false otherwise. + */ +bool parseArgs(int argc, char *argv[]) { + if (argc > 1) { + for (int i = 1; i < argc; i++) { + string arg = string(argv[i]); // Unsafe buffer usage warnings can be safely ignored, as we do check for the size + if (arg == "-h" || arg == "--help") + break; + else if (arg == "-t" || arg == "--test") + testMode = true; + + else if ((arg == "-l" || arg == "--level") && argc > i + 1) { + if (!startWorld("./" + string(argv[i+1]))) // This warning can also be ignored, again – we do this in a safe way + return true; // Load only the specified world + else + printFile("./screens/completed_single_level.txt", Color::BRIGHT_GREEN); + return true; + } + } + if (!testMode) { + printFile("./screens/help.txt", Color::BRIGHT_BLUE); // Print help screen + return true; + } + } + return false; +} diff --git a/src/assets/MartianMono-VariableFont.ttf b/src/assets/MartianMono-VariableFont.ttf new file mode 100644 index 0000000..9d677f3 Binary files /dev/null and b/src/assets/MartianMono-VariableFont.ttf differ diff --git a/src/assets/attributions.txt b/src/assets/attributions.txt index 2fc7fde..46ba3af 100644 --- a/src/assets/attributions.txt +++ b/src/assets/attributions.txt @@ -1,3 +1,3 @@ -"The Entertainer" Kevin MacLeod (incompetech.com) -Licensed under Creative Commons: By Attribution 4.0 License -http://creativecommons.org/licenses/by/4.0/ \ No newline at end of file +"Sunset On The Beach" Kumiku +Licensed under CC0 1.0 +https://freemusicarchive.org/music/Komiku/Poupis_incredible_adventures_/Komiku_-_Poupis_incredible_adventures__-_55_Sunset_on_the_beach/ \ No newline at end of file diff --git a/src/assets/sunset_on_the_beach.ogg b/src/assets/sunset_on_the_beach.ogg new file mode 100644 index 0000000..8b2a9c0 Binary files /dev/null and b/src/assets/sunset_on_the_beach.ogg differ diff --git a/src/assets/the_entertainer.ogg b/src/assets/the_entertainer.ogg deleted file mode 100644 index e4203ac..0000000 Binary files a/src/assets/the_entertainer.ogg and /dev/null differ diff --git a/src/main.cpp b/src/main.cpp index 512bab3..6aa2ced 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,109 +1,284 @@ -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include -#include +#include "adventura.cpp" -#include "world.hpp" -#include "player.hpp" -#include "blockRegistry.hpp" -#include "movementHandler.hpp" -#include "output.hpp" +constexpr uint32_t windowStartWidth = 1200; +constexpr uint32_t windowStartHeight = 800; -using std::string; -using std::cout; -using std::endl; +void loadWorld(void* appstate, string worldFile); +void resetWorld(void* appstate); +void loadNextWorld(void* appstate); -bool startWorld(string worldFile); -bool parseArgs(int argc, char *argv[]); +struct AppContext { + SDL_Window* window; + SDL_Renderer* renderer; + SDL_Texture* messageTex, *imageTex; + SDL_FRect messageDest; + SDL_AudioDeviceID audioDevice; + Mix_Music* music; + SDL_AppResult app_quit = SDL_APP_CONTINUE; + Player* player; + World* world; + TTF_Font* font; +}; -static bool testMode = false; -static unsigned int worldIndex = 2; - -/** - * Entry point of the program. - * If a world file is provided as an argument, play through that world. - * Otherwise, play through all worlds in the worlds directory. - * In case the player dies during gameplay, exit without printing the victory screen. - * If the player reaches the goal of the final level, print the victory screen and exit. - */ -int main(int argc, char *argv[]) { - if (parseArgs(argc, argv)) return 0; - - if (!testMode) { - printFile("./screens/start.txt", Color::BRIGHT_YELLOW); // Show the story introduction - waitForInput(); - printGuide(); // Show the block guide - waitForInput(); - } - - // Load every world in order - for (const auto & world : getOrderedFileNames("./worlds/", ".txt")) - if (!startWorld(world)) return 0; // If the player dies, exit - - // Print the victory screen once all levels have been completed - printFile("./screens/victory.txt", Color::BRIGHT_GREEN); - - return 0; +SDL_AppResult SDL_Fail(){ + SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Error %s", SDL_GetError()); + return SDL_APP_FAILURE; } -/** - * Start a new world defined in the file at worldFile. - * If the player reaches the goal, return true. - * In case they die, print the death screen and return false. - * @return true if the player reached the goal, false in case of death - */ -bool startWorld(string worldFile) { +SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { + // init the library, here we make a window so we only need the Video capabilities. + if (not SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)){ + return SDL_Fail(); + } + + // init TTF + if (not TTF_Init()) { + return SDL_Fail(); + } + + // create a window + + SDL_Window* window = SDL_CreateWindow("Adventura", windowStartWidth, windowStartHeight, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY); + if (not window){ + return SDL_Fail(); + } + + // create a renderer + SDL_Renderer* renderer = SDL_CreateRenderer(window, NULL); + if (not renderer){ + return SDL_Fail(); + } + + // load the font +#if __ANDROID__ + std::filesystem::path basePath = ""; // on Android we do not want to use basepath. Instead, assets are available at the root directory. +#else + auto basePathPtr = SDL_GetBasePath(); + if (not basePathPtr){ + return SDL_Fail(); + } + const std::filesystem::path basePath = basePathPtr; +#endif + + const auto fontPath = basePath / "assets/MartianMono-VariableFont.ttf"; + TTF_Font* font = TTF_OpenFont(fontPath.string().c_str(), 36); + if (not font) { + return SDL_Fail(); + } + + // render the font to a surface + const std::string_view text = "Hello SDL!"; + SDL_Surface* surfaceMessage = TTF_RenderText_Solid(font, text.data(), text.length(), { 255,255,255 }); + + // make a texture from the surface + SDL_Texture* messageTex = SDL_CreateTextureFromSurface(renderer, surfaceMessage); + + // we no longer need the font or the surface, so we can destroy those now. + //TTF_CloseFont(font); + SDL_DestroySurface(surfaceMessage); + + // load the SVG + auto svg_surface = IMG_Load((basePath / "assets/gs_tiger.svg").string().c_str()); + SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, svg_surface); + SDL_DestroySurface(svg_surface); + + + // get the on-screen dimensions of the text. this is necessary for rendering it + auto messageTexProps = SDL_GetTextureProperties(messageTex); + SDL_FRect text_rect{ + .x = 0, + .y = 0, + .w = float(SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_WIDTH_NUMBER, 0)), + .h = float(SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0)) + }; + + // init SDL Mixer + auto audioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL); + if (not audioDevice) { + return SDL_Fail(); + } + if (not Mix_OpenAudio(audioDevice, NULL)) { + return SDL_Fail(); + } + + // load the music + auto musicPath = basePath / "assets/sunset_on_the_beach.ogg"; + auto music = Mix_LoadMUS(musicPath.string().c_str()); + if (not music) { + return SDL_Fail(); + } + + // play the music (does not loop) + Mix_PlayMusic(music, 99999); + + // print some information about the window + SDL_ShowWindow(window); + { + int width, height, bbwidth, bbheight; + SDL_GetWindowSize(window, &width, &height); + SDL_GetWindowSizeInPixels(window, &bbwidth, &bbheight); + SDL_Log("Window size: %ix%i", width, height); + SDL_Log("Backbuffer size: %ix%i", bbwidth, bbheight); + if (width != bbwidth){ + SDL_Log("This is a highdpi environment."); + } + } BlockRegistry blockRegistry = BlockRegistry(); - World world = World(blockRegistry); - - world.loadFromFile(worldFile); - Player player = Player(world.getStartPos(), world); - render(world, player.mapToWorldspace()); - - inputLoop(player, world, testMode, worldIndex); + World *world = new World(blockRegistry); + Player *player = new Player(world->getStartPos(), *world); - worldIndex++; - if (!player.isAlive()) printFile("./screens/death.txt", Color::BRIGHT_RED); - return player.hasReachedGoal(); + // set up the application data + *appstate = new AppContext{ + .window = window, + .renderer = renderer, + .messageTex = messageTex, + .imageTex = tex, + .messageDest = text_rect, + .audioDevice = audioDevice, + .music = music, + .player = player, + .world = world, + .font = font + }; + loadNextWorld(*appstate); + + SDL_SetRenderVSync(renderer, -1); // enable vysnc + + SDL_Log("Application started successfully!"); + + //start(argc, argv); + + return SDL_APP_CONTINUE; } -/** - * Parses command-line arguments to set the game's configuration. - * - * Recognizes the following arguments: - * - "-h" or "--help": Displays the help screen and exits. - * - "-t" or "--test": Enables test mode, starting an automated playthrough. - * - "-l" or "--level ": Loads and plays only the specified level. - * - * If a custom action is specified, the function returns true to indicate - * immediate termination after executing the requested action. - * - * @param argc The number of command-line arguments. - * @param argv The array containing the command-line arguments. - * @return true if the program should exit after handling the arguments, false otherwise. - */ -bool parseArgs(int argc, char *argv[]) { - if (argc > 1) { - for (int i = 1; i < argc; i++) { - string arg = string(argv[i]); // Unsafe buffer usage warnings can be safely ignored, as we do check for the size - if (arg == "-h" || arg == "--help") - break; - else if (arg == "-t" || arg == "--test") - testMode = true; - - else if ((arg == "-l" || arg == "--level") && argc > i + 1) { - if (!startWorld("./" + string(argv[i+1]))) // This warning can also be ignored, again – we do this in a safe way - return true; // Load only the specified world - else - printFile("./screens/completed_single_level.txt", Color::BRIGHT_GREEN); - return true; - } - } - if (!testMode) { - printFile("./screens/help.txt", Color::BRIGHT_BLUE); // Print help screen - return true; +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event* event) { + auto* app = (AppContext*)appstate; + + if (event->type == SDL_EVENT_KEY_DOWN) { + if (event->key.scancode == SDL_SCANCODE_W) { + onInput('w', *(app->world), *(app->player)); + } else if (event->key.scancode == SDL_SCANCODE_A) { + onInput('a', *(app->world), *(app->player)); + } else if (event->key.scancode == SDL_SCANCODE_S) { + onInput('s', *(app->world), *(app->player)); + } else if (event->key.scancode == SDL_SCANCODE_D) { + onInput('d', *(app->world), *(app->player)); } } - return false; + + if ((*app->player).hasReachedGoal()) { + loadNextWorld(appstate); + } + + if (event->type == SDL_EVENT_QUIT) { + app->app_quit = SDL_APP_SUCCESS; + } + + return SDL_APP_CONTINUE; } + +SDL_AppResult SDL_AppIterate(void *appstate) { + auto* app = (AppContext*)appstate; + + // draw a color + auto time = SDL_GetTicks() / 1000.f; + auto red = (std::sin(time) + 1) / 2.0 * 255; + auto green = (std::sin(time / 2) + 1) / 2.0 * 255; + auto blue = (std::sin(time) * 2 + 1) / 2.0 * 255; + SDL_SetRenderDrawBlendMode(app->renderer, SDL_BLENDMODE_ADD); + SDL_SetRenderDrawColor(app->renderer, red, green, blue, 1); + SDL_SetRenderDrawColor(app->renderer, 0, 0, 0, 20); + + SDL_RenderClear(app->renderer); + + // Renderer uses the painter's algorithm to make the text appear above the image, we must render the image first. + //SDL_RenderTexture(app->renderer, app->imageTex, NULL, NULL); + vector> worldState = render(*(app->world), (*app->player).mapToWorldspace()); + for (int i = 0; i < worldState.size(); i++) { + vector line = worldState.at(i); + SDL_Surface* surfaceMessage = TTF_RenderText_Solid(app->font, line.data(), line.size(), { 255,255,255 }); + + // make a texture from the surface + SDL_Texture* messageTex = SDL_CreateTextureFromSurface(app->renderer, surfaceMessage); + + auto messageTexProps = SDL_GetTextureProperties(messageTex); + SDL_FRect text_rect{ + .x = 0, + .y = float(i * SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0)*2), + .w = float(SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_WIDTH_NUMBER, 0))*2, + .h = float(SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0))*2 + }; + SDL_DestroySurface(surfaceMessage); + SDL_RenderTexture(app->renderer, messageTex, NULL, &text_rect); + SDL_DestroyTexture(messageTex); + } + //SDL_RenderTexture(app->renderer, app->messageTex, NULL, &app->messageDest); + + SDL_RenderPresent(app->renderer); + + return app->app_quit; +} + +void SDL_AppQuit(void* appstate, SDL_AppResult result) { + auto* app = (AppContext*)appstate; + if (app) { + SDL_DestroyRenderer(app->renderer); + SDL_DestroyWindow(app->window); + + Mix_FadeOutMusic(1000); // prevent the music from abruptly ending. + Mix_FreeMusic(app->music); // this call blocks until the music has finished fading + Mix_CloseAudio(); + SDL_CloseAudioDevice(app->audioDevice); + + delete app; + } + TTF_Quit(); + Mix_Quit(); + + SDL_Log("Application quit successfully!"); + SDL_Quit(); +} + +string currentWorld = ""; + +void loadWorld(void* appstate, string worldFile) { + auto* app = (AppContext*)appstate; + + (*app->world).loadFromFile(worldFile); + (app -> player) = new Player((*app->world).getStartPos(), *app->world); + currentWorld = worldFile; +} + +void resetWorld(void* appstate) { + auto* app = (AppContext*)appstate; + + loadWorld(appstate, currentWorld); +} +void loadNextWorld(void* appstate) { + auto* app = (AppContext*)appstate; + + vector worldFiles = getOrderedFileNames("./worlds/", ".txt"); + if (currentWorld == "") { + loadWorld(app, worldFiles.at(0)); + return; + } + + int index = std::find(worldFiles.begin(), worldFiles.end(), currentWorld) - worldFiles.begin(); + if (index + 1 < worldFiles.size()) { + loadWorld(appstate, worldFiles.at(index + 1)); + } + else { + resetWorld(appstate); + // You won + } +} \ No newline at end of file diff --git a/src/movementHandler.hpp b/src/movementHandler.hpp index 8c8ea3f..7953b7d 100644 --- a/src/movementHandler.hpp +++ b/src/movementHandler.hpp @@ -154,6 +154,7 @@ static void tryBlockGravity(BlockPos& playerPos, World& world) { */ static bool onInput(char lastChar, World& world, Player& player) { + if (player.isFreeFalling()) return false; switch (lastChar) { case ' ': case 'w': @@ -198,10 +199,10 @@ static void inputLoop(Player& player, World& world, bool testMode, unsigned int jumpBackOneLine(); } - for (char lastChar : currentInput) { - if (onInput(lastChar, world, player)) - redraw(world, player.mapToWorldspace()); - } + //for (char lastChar : currentInput) { + // if (onInput(lastChar, world, player)) + // redraw(world, player.mapToWorldspace()); + //} } inputIndex = 0; } diff --git a/src/output.cpp b/src/output.cpp new file mode 100644 index 0000000..3d14956 --- /dev/null +++ b/src/output.cpp @@ -0,0 +1,70 @@ +#include +#include + +#include "output.hpp" + +using std::string; +using std::cout; +using std::endl; + +/** + * Move the console cursor up by one line. + * Used to overwrite the previous line. + */ +static void jumpBackOneLine() { + std::cout << "\033[1A"; +} + +/** + * Renders the current state of the game world and player onto the console. + * It prints the world's blocks with their respective colors and encodings (characters). + * On positions that overlap with the player texture, the relevant character of the player's texture is printed instead. + * + * @param world Reference to the World object representing the current world. + * @param playerTexture Reference to the current Player texture. + */ +static vector> render(World &world, vector> playerTexture) { + vector> canvas = world.getFieldState(); + vector> out; + + for (unsigned int y = 0; y <= world.getMaxY(); y++) { + vector line; + for (unsigned int x = 0; x <= world.getMaxX(); x++) { + if (!world.getBlockAt(BlockPos(x, y)).getSettings().isPushable() + && playerTexture.size() > y && playerTexture.at(y).size() > x && playerTexture.at(y).at(x) != ' ') { + line.push_back(playerTexture.at(y).at(x)); + //<< Color::BRIGHT_YELLOW << playerTexture.at(y).at(x); + } + else if (canvas.size() > y && canvas.at(y).size() > x) { + line.push_back(canvas.at(y).at(x).getEncoding()); + //cout << canvas.at(y).at(x).getColor() << canvas.at(y).at(x).getEncoding(); + } + else line.push_back(' '); + } + out.push_back(line); + } + return out; +} + +/** + * Prints a guide for the player, explaining what each block in the game + * represents. + */ +static void printGuide() { + // We use a vector here instead of a map, because we want to keep this order + std::vector> guide = { + {"- Plattform", Color::RESET}, + {"H Leiter", Color::BRIGHT_MAGENTA}, + {"S Start", Color::RESET}, + {"O Ziel", Color::BRIGHT_GREEN}, + {"0 Wand", Color::RESET}, + {"^ Stacheln", Color::BRIGHT_RED}, + {"~ Wasser", Color::BRIGHT_BLUE}, + {"x Kiste", Color::BRIGHT_CYAN}, + {"* Sand", Color::BRIGHT_YELLOW} + }; + for (std::pair p : guide) { + cout << p.second << p.first << endl; + } + cout << endl << Color::RESET << "WASD + Enter -> Spiel starten" << endl; +} diff --git a/src/output.hpp b/src/output.hpp index c506320..949c4f4 100644 --- a/src/output.hpp +++ b/src/output.hpp @@ -1,19 +1,12 @@ #pragma once -#include -#include #include "world.hpp" -using std::string; -using std::cout; -using std::endl; - /** * Move the console cursor up by one line. * Used to overwrite the previous line. */ static void jumpBackOneLine() { - std::cout << "\033[1A"; } /** @@ -24,40 +17,27 @@ static void jumpBackOneLine() { * @param world Reference to the World object representing the current world. * @param playerTexture Reference to the current Player texture. */ -static void render(World &world, vector> playerTexture) { +static vector> render(World &world, vector> playerTexture) { vector> canvas = world.getFieldState(); - + vector> out; for (unsigned int y = 0; y <= world.getMaxY(); y++) { + vector line; for (unsigned int x = 0; x <= world.getMaxX(); x++) { if (!world.getBlockAt(BlockPos(x, y)).getSettings().isPushable() && playerTexture.size() > y && playerTexture.at(y).size() > x && playerTexture.at(y).at(x) != ' ') { - cout << Color::BRIGHT_YELLOW << playerTexture.at(y).at(x); + line.push_back(playerTexture.at(y).at(x)); + //cout << Color::BRIGHT_YELLOW << playerTexture.at(y).at(x); } else if (canvas.size() > y && canvas.at(y).size() > x) { - cout << canvas.at(y).at(x).getColor() << canvas.at(y).at(x).getEncoding(); + line.push_back(canvas.at(y).at(x).getEncoding()); + //cout << canvas.at(y).at(x).getColor() << canvas.at(y).at(x).getEncoding(); } - else cout << ' '; + else line.push_back(' '); } - cout << endl; + out.push_back(line); } -} - -/** - * Redraws the game world and player state on the console. - * This function first moves the console cursor up by the number of lines - * equivalent to the world's height, effectively clearing previous output. - * It then calls the render function to display the current state of the world - * and the player. - * - * @param world Reference to the World object representing the current world. - * @param playerTexture Reference to the current Player texture. - */ -static void redraw(World &world, vector> playerTexture) { - for (unsigned int y = 0; y <= world.getMaxY(); y++) { - jumpBackOneLine(); - } - render(world, playerTexture); + return out; } /** @@ -65,20 +45,4 @@ static void redraw(World &world, vector> playerTexture) { * represents. */ static void printGuide() { - // We use a vector here instead of a map, because we want to keep this order - std::vector> guide = { - {"- Plattform", Color::RESET}, - {"H Leiter", Color::BRIGHT_MAGENTA}, - {"S Start", Color::RESET}, - {"O Ziel", Color::BRIGHT_GREEN}, - {"0 Wand", Color::RESET}, - {"^ Stacheln", Color::BRIGHT_RED}, - {"~ Wasser", Color::BRIGHT_BLUE}, - {"x Kiste", Color::BRIGHT_CYAN}, - {"* Sand", Color::BRIGHT_YELLOW} - }; - for (std::pair p : guide) { - cout << p.second << p.first << endl; - } - cout << endl << Color::RESET << "WASD + Enter -> Spiel starten" << endl; } diff --git a/src/player.hpp b/src/player.hpp index 9991424..f3e1d0c 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -47,7 +47,6 @@ public: void move(BlockPos offset) { setPos(pos + offset); } - /** * Updates the player's position and checks for any conditions that would update the state of the player. @@ -65,13 +64,15 @@ public: if (world.getBlockAt(newPos.add(0, 2)) == world.getBlockRegistry().WATER) fallLength = 0; - isFreeFalling = !world.getBlockAt(newPos.add(0, 2)).getSettings().isSolid(); - if (isFreeFalling) { + freeFalling = !world.getBlockAt(newPos.add(0, 2)).getSettings().isSolid(); + if (freeFalling) { fallLength += 1; if (fallLength > 2) playerTexture = FALLING_PLAYER_TEXTURE; - redraw(world, this->mapToWorldspace()); - std::this_thread::sleep_for(std::chrono::milliseconds(100 / fallLength + 50)); - move(0, 1); + SDL_AddTimer(100 / fallLength + 50, [](void *userdata, SDL_TimerID timerID, Uint32 interval) -> Uint32 { + Player *player = (Player *)userdata; + player->move(0, 1); + return 0; + }, this); } else { playerTexture = REGULAR_PLAYER_TEXTURE; @@ -87,7 +88,7 @@ public: */ void unalive() { playerTexture = DEAD_PLAYER_TEXTURE; - redraw(world, this->mapToWorldspace()); + //redraw(world, this->mapToWorldspace()); alive = false; } @@ -99,6 +100,14 @@ public: bool isAlive() { return alive; } + /** + * Checks if the player is still alive. + * + * @return true if the player is alive, false otherwise. + */ + bool isFreeFalling() { + return freeFalling; + } /** * Checks if the player has reached the goal in the current world. @@ -141,7 +150,7 @@ private: std::array, 3> playerTexture; BlockPos pos = BlockPos(0, 0); bool alive = true; - bool isFreeFalling = false; + bool freeFalling = false; bool reachedGoal = false; int fallLength = 0;