diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..291038955f3afbc385afd94f038234fd992f033b --- /dev/null +++ b/.clang-format @@ -0,0 +1,187 @@ +--- +BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: AlwaysBreak +AlignArrayOfStructures: Right +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: Consecutive +AlignConsecutiveMacros: None +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterJavaFieldAnnotations: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeConceptDeclarations: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: ^"(llvm|llvm-c|clang|clang-c)/ + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: ^(<|"(gtest|gmock|isl|json)/) + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: .* + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: (Test)?$ +IncludeIsMainSourceRegex: "" +IndentAccessModifiers: true +IndentCaseBlocks: true +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +Language: Cpp +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PPIndentWidth: -1 +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDeclarationName: false + AfterFunctionDefinitionName: false + AfterIfMacros: true + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME diff --git a/CMakeLists.txt b/CMakeLists.txt index c2881f59cea2b31d4a2e9240a5a6340428e37fb6..593dc285ecf55564704b46a637189892eeaf8171 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,23 @@ set(HIGHFIVE_UNIT_TESTS OFF) FetchContent_MakeAvailable(highfive) +FetchContent_Declare( + box2d + GIT_REPOSITORY https://github.com/erincatto/box2d.git + GIT_TAG v3.0.0 +) + +# Box2D Build-Optionen konfigurieren +set(BOX2D_BUILD_TESTBED OFF CACHE BOOL "" FORCE) +set(BOX2D_BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE) +set(BOX2D_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + +FetchContent_MakeAvailable(box2d) +set(Boost_USE_STATIC_LIBS OFF) +set(Boost_USE_MULTITHREADED ON) +set(Boost_USE_STATIC_RUNTIME OFF) +find_package(Boost 1.71.0 REQUIRED COMPONENTS graph) # Quellen sammeln file(GLOB_RECURSE ADVANCED_WARS_SOURCES @@ -31,6 +47,12 @@ file(GLOB_RECURSE ADVANCED_WARS_SOURCES set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_BUILD_TYPE Debug) + +set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Wpedantic") +set(CMAKE_C_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Wpedantic") + +add_definitions(-DDEBUG) # Compiler-Warnungen aktivieren if(MSVC) @@ -43,7 +65,8 @@ endif() set(ASSETS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/assets) set(OUTPUT_ASSETS_DIR ${CMAKE_CURRENT_BINARY_DIR}/assets) file(MAKE_DIRECTORY ${OUTPUT_ASSETS_DIR}) -file(GLOB FONT_FILES ${ASSETS_DIR}/*.ttf) +file(GLOB FONT_FILES ${ASSETS_DIR}/*.TTF) +file(GLOB IMAGE_FILES ${ASSETS_DIR}/*.png) # Executable erstellen add_executable(advanced_wars ${ADVANCED_WARS_SOURCES}) @@ -51,6 +74,7 @@ add_executable(advanced_wars ${ADVANCED_WARS_SOURCES}) target_include_directories(advanced_wars PRIVATE ${highfive_SOURCE_DIR}/include + ${Boost_INCLUDE_DIRS} ) foreach(FONT ${FONT_FILES}) @@ -61,6 +85,14 @@ foreach(FONT ${FONT_FILES}) ) endforeach() +foreach(IMAGE ${IMAGE_FILES}) + add_custom_command( + TARGET advanced_wars PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${IMAGE} ${OUTPUT_ASSETS_DIR} + COMMENT "Kopiere Image: ${IMAGE} nach ${OUTPUT_ASSETS_DIR}" + ) +endforeach() + set(CMAKE_MODULE_PATH ${ADVANCED_WARS_SOURCE_DIR}/cmake/ ${CMAKE_MODULE_PATH}) # Plattform-spezifische Konfiguration @@ -80,12 +112,14 @@ if(APPLE) ${SDL2_PATH}/SDL2.framework/SDL2 ${SDL2_PATH}/SDL2_image.framework/SDL2_image ${SDL2_PATH}/SDL2_ttf.framework/SDL2_ttf + Boost::graph + box2d ) - # Debug-Ausgaben message(STATUS "Include Dir (SDL2): ${SDL2_PATH}/SDL2.framework/Headers") message(STATUS "Include Dir (SDL2_image): ${SDL2_PATH}/SDL2_image.framework/Headers") message(STATUS "Include Dir (SDL2_ttf): ${SDL2_PATH}/SDL2_ttf.framework/Headers") + message(STATUS "Boost Include Dirs: ${Boost_INCLUDE_DIRS}") else() find_package(SDL2 REQUIRED) find_package(SDL2_IMAGE REQUIRED) @@ -104,9 +138,8 @@ else() -lSDL2 -lSDL2_image -lSDL2_ttf + Boost::graph + box2d m ) - - endif() - diff --git a/README.md b/README.md index 8e5bcd0f74eb02bafe338d75ed3e513d8e39bcff..e464b3acdb696833c43b9cfd88279d0063da4246 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,31 @@ 4. Visual Studio erkennt automatisch das CMake-Projekt 5. Build über "Build All" ausführen +#### Falls Syntax errors + +1. Erstelle .vscode/c_cpp_properties.json Datei +2. Füge die folgende JSON so oder so ähnlich ein: + +```json +{ + "configurations": [ + { + "name": "Fedora", + "includePath": [ + "/usr/include", + "/usr/include/SDL2" + ], + "defines": [], + "intelliSenseMode": "linux-gcc-x64", + "compilerPath": "/usr/bin/gcc", + "cStandard": "c17", + "cppStandard": "c++17" + } + ], + "version": 4 +} +``` + ## Build-Optionen CMake kann mit verschiedenen Optionen konfiguriert werden: diff --git a/level-spezifikation.md b/level-spezifikation.md new file mode 100644 index 0000000000000000000000000000000000000000..1f087e54abc5225b023b263701732589063a3788 --- /dev/null +++ b/level-spezifikation.md @@ -0,0 +1,36 @@ +# Levelspezifikation + +Das Level wird ueber 2 Datasets in der HDF5-Datei repraesentiert. +Ein Dataset in XML Format, das die Level Metadaten (Breite, Hoehe, Dataset ID Tiles Array) angibt. +Ein weiters Dataset, das letztlich ein array<uint8_t> ist, welches die Levelmap ueber ein Array von Tile IDs definiert. + +## 1. XML Dataset mit Level Metadaten +```xml +<?xml version="1.0" encoding="ASCII"?> +<level> + <width>20</width> <!-- Breite des Levels --> + <height>20</height> <!-- Hoehe des Levels --> + <name>Geiles Level<name><!-- Name des Levels --> +</level> +``` + +## 2. Tiles Array +Das Tiles Array wird als array<uint8_t> in einem Dataset in der HDF5-Datei gespeichert. +Die Laenge des Arrays ist Breite X Hoehe (kann man aus XML Leveldefinition entnehmen). +Aus den einzelnen Werte in dem Array lassen sich die IDs der entsprechenden Gebaudes/Terrains ableiten. +0 - 29 sind Terrain IDs => Zuordnung siehe entsprechendes Enum in Tile.hpp +30-49 sind undefiniert +50-79 sind Gebaeude IDs: +50 => Faction ID 0, Gebaeude ID 0 +51 => Faction ID 0, Gebaeude ID 1 +... +55 => Faction ID 1, Gebaeude ID 0 +56 => Faction ID 1, Gebaeude ID 1 +57 => Faction ID 1, Gebaeude ID 2 +... +Allgemein: +Sei t ein Wert im Tiles Array, dann gillt +falls t < 30: Terrain ID = t +falls t >= 50: Faction ID = (t - 50) / 5 Gebaeude ID = (t - 50) % 5 +t wird ermittelt mit entweder t = Terrain ID fuer Terrains +oder t = 50 + 5*Faction Id + Gebaeude ID fuer Gebaeude \ No newline at end of file diff --git a/src/assets/ARCADECLASSIC.TTF b/src/assets/ARCADECLASSIC.TTF new file mode 100644 index 0000000000000000000000000000000000000000..394a9f781cedaa283a11b3b8b43c9006f4e5bac7 Binary files /dev/null and b/src/assets/ARCADECLASSIC.TTF differ diff --git a/src/assets/main_background.png b/src/assets/main_background.png new file mode 100644 index 0000000000000000000000000000000000000000..5958dc0d0a7144697dece04db5dc2a1646088207 Binary files /dev/null and b/src/assets/main_background.png differ diff --git a/src/building.cpp b/src/building.cpp index b4af7f80e17c04f33d1e25d06dc7ba9d714126f1..c6b47dc815c2f2642ddd11aa13766ca36419ed09 100644 --- a/src/building.cpp +++ b/src/building.cpp @@ -1,30 +1,31 @@ #include "building.hpp" #include "spritesheet.hpp" -namespace advanced_wars { +namespace advanced_wars +{ Building::Building(int x, int y, BuildingId id, BuildingFaction faction) - : x(x), y(y), id(id), faction(faction) {}; + : x(x), y(y), id(id), faction(faction){}; -void Building::render(Engine &engine, int scale) { - Spritesheet *spritesheet = engine.get_spritesheet(); +void Building::render(Engine* engine, int scale) +{ + Spritesheet* spritesheet = engine->get_spritesheet(); - SDL_Rect src; - src.x = static_cast<int>(id) * spritesheet->get_building_width(); - src.y = 0; - src.w = spritesheet->get_building_width(); - src.h = spritesheet->get_building_height(); + SDL_Rect src; + src.x = static_cast<int>(id) * spritesheet->get_building_width(); + src.y = 0; + src.w = spritesheet->get_building_width(); + src.h = spritesheet->get_building_height(); - SDL_Rect dst; - dst.x = x * spritesheet->get_tile_width() * scale; - dst.y = (y - 1) * spritesheet->get_tile_height() * scale; - dst.w = spritesheet->get_building_width() * scale; - dst.h = spritesheet->get_building_height() * scale; + SDL_Rect dst; + dst.x = x * spritesheet->get_tile_width() * scale; + dst.y = (y - 1) * spritesheet->get_tile_height() * scale; + dst.w = spritesheet->get_building_width() * scale; + dst.h = spritesheet->get_building_height() * scale; - SDL_RenderCopyEx( - engine.renderer(), - spritesheet->get_building_textures()[static_cast<int>(faction)], &src, - &dst, 0, NULL, SDL_FLIP_NONE); + SDL_RenderCopyEx( + engine->renderer(), spritesheet->get_building_textures()[static_cast<int>(faction)], &src, + &dst, 0, NULL, SDL_FLIP_NONE); } } // namespace advanced_wars \ No newline at end of file diff --git a/src/building.hpp b/src/building.hpp index d83597f26ae58562ad4b8d787564432d7fda9bd8..fa7a39d6c85f414e519cd03b17da7fe6a2823b9a 100644 --- a/src/building.hpp +++ b/src/building.hpp @@ -3,35 +3,39 @@ #include "engine.hpp" #include "scene.hpp" -namespace advanced_wars { - -enum class BuildingFaction { - RED = 0, - BLUE = 1, - YELLOW = 2, - GREEN = 3, - PURPLE = 4, - NEUTRAL = 5, +namespace advanced_wars +{ + +enum class BuildingFaction +{ + RED = 0, + BLUE = 1, + YELLOW = 2, + GREEN = 3, + PURPLE = 4, + NEUTRAL = 5, }; -enum class BuildingId { - HEADQUARTER = 0, - CITY = 1, - FACTORY = 2, - PORT = 3, - SATELLITE = 4, +enum class BuildingId +{ + HEADQUARTER = 0, + CITY = 1, + FACTORY = 2, + PORT = 3, + SATELLITE = 4, }; -class Building { -public: - Building(int x, int y, BuildingId id, BuildingFaction faction); +class Building +{ + public: + Building(int x, int y, BuildingId id, BuildingFaction faction); - int x; - int y; - BuildingId id; - BuildingFaction faction; + int x; + int y; + BuildingId id; + BuildingFaction faction; - void render(Engine &engine, int scale); + void render(Engine* engine, int scale); }; } // namespace advanced_wars \ No newline at end of file diff --git a/src/effect.cpp b/src/effect.cpp index c0e22d6510192011825cbb817abdf8866a8c1dac..f3ab594394084dd214a95a6078715c81478a447c 100644 --- a/src/effect.cpp +++ b/src/effect.cpp @@ -2,28 +2,27 @@ #include "spritesheet.hpp" #include <vector> -namespace advanced_wars { +namespace advanced_wars +{ Effect::Effect(int x, int y, EffectId id, bool repeat) - : x(x), y(y), id(id), repeat(repeat), start(0) { + : x(x), y(y), id(id), repeat(repeat), start(0){ - }; + }; -void Effect::render(Engine &engine, int scale) { - Spritesheet *spritesheet = engine.get_spritesheet(); - if (start == 0) { - start = engine.get_stage(); - } +void Effect::render(Engine* engine, int scale) +{ + Spritesheet* spritesheet = engine->get_spritesheet(); + if (start == 0) + { + start = engine->get_stage(); + } - int step = engine.get_stage() % - spritesheet->get_effect_textures().at(static_cast<int>(id)).second; + int step = + engine->get_stage() % spritesheet->get_effect_textures().at(static_cast<int>(id)).second; - if (engine.get_stage() - start <= - spritesheet->get_effect_textures().at(static_cast<int>(id)).second || - repeat) { SDL_Rect src; - src.x = step * spritesheet->get_effect_width() + - step * spritesheet->get_effect_height(); + src.x = step * spritesheet->get_effect_width() + step * spritesheet->get_effect_height(); src.y = 0; src.w = spritesheet->get_effect_width(); src.h = spritesheet->get_effect_height(); @@ -35,10 +34,16 @@ void Effect::render(Engine &engine, int scale) { dest.h = spritesheet->get_effect_height() * scale; SDL_RenderCopyEx( - engine.renderer(), - spritesheet->get_effect_textures().at(static_cast<int>(id)).first, &src, + engine->renderer(), spritesheet->get_effect_textures().at(static_cast<int>(id)).first, &src, &dest, 0, NULL, SDL_FLIP_NONE); - } +} + +bool Effect::is_finished(Engine* engine) +{ + return !( + engine->get_stage() - start <= + engine->get_spritesheet()->get_effect_textures().at(static_cast<int>(id)).second || + repeat); } } // namespace advanced_wars \ No newline at end of file diff --git a/src/effect.hpp b/src/effect.hpp index e5b94a12d1fd50bb12507df543f3403b0eabe803..f33923d82c7be695dd428e2024d98818b471047a 100644 --- a/src/effect.hpp +++ b/src/effect.hpp @@ -2,27 +2,32 @@ #include "engine.hpp" -namespace advanced_wars { - -enum class EffectId { - LAND_EXPLOSION = 0, - AIR_EXPLOSION = 1, - NAVAL_EXPLOSION = 2, - SUBMARINE_HIDE = 3, - SUBMARINE_APPEAR = 4 +namespace advanced_wars +{ + +enum class EffectId +{ + LAND_EXPLOSION = 0, + AIR_EXPLOSION = 1, + NAVAL_EXPLOSION = 2, + SUBMARINE_HIDE = 3, + SUBMARINE_APPEAR = 4 }; -class Effect { -public: - Effect(int x, int y, EffectId id, bool repeat); +class Effect +{ + public: + Effect(int x, int y, EffectId id, bool repeat); + + void render(Engine* engine, int scale); - void render(Engine &engine, int scale); + bool is_finished(Engine* engine); - int x; - int y; - EffectId id; - bool repeat; - int start; + int x; + int y; + EffectId id; + bool repeat; + int start; }; } // namespace advanced_wars \ No newline at end of file diff --git a/src/engine.cpp b/src/engine.cpp index e5f9b8eea21e02691adfc2a154019f7c1c9c7e47..f7c58d6efeb46bb4350a0b7c02429d166e524f36 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -1,85 +1,129 @@ #include "engine.hpp" +#include "SDL_events.h" +#include "SDL_timer.h" #include "scene.hpp" #include "spritesheet.hpp" #include "window.hpp" #include <SDL.h> #include <SDL_image.h> #include <SDL_render.h> +#include <deque> +#include <memory> +#include <optional> #include <stdexcept> -#include <vector> -namespace advanced_wars { +namespace advanced_wars +{ -Engine::Engine(Window &window) : window(window), quit(false) { +Engine::Engine(Window& window) : window(window), quit(false) +{ - if (SDL_Init(SDL_INIT_VIDEO) < 0) { - throw std::runtime_error("SDL could not initialize: " + - std::string(SDL_GetError())); - } + this->sdl_renderer = SDL_CreateRenderer( + this->window.sdl_window(), -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); - int imgFlags = IMG_INIT_PNG; - if (!(IMG_Init(imgFlags) & imgFlags)) { - throw std::runtime_error( - "SDL_image could not initialize! SDL_image Error: " + - std::string(IMG_GetError())); - } + if (sdl_renderer == nullptr) + { + throw std::runtime_error("SDL could not generate renderer: " + std::string(SDL_GetError())); + } +} + +std::deque<SDL_Event>& Engine::events() +{ + return this->_events; +} - this->sdl_renderer = - SDL_CreateRenderer(this->window.sdl_window(), -1, - SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); +void Engine::push_scene(std::shared_ptr<Scene> scene) +{ + this->scenes.push_back(scene); +} - if (sdl_renderer == nullptr) { - throw std::runtime_error("SDL could not generate renderer: " + - std::string(SDL_GetError())); - } +void Engine::return_to_menu() +{ + // TODO: discuss if we outsource this to a separate function + // clear everything except the first scene + while (this->scenes.size() > 1) + { + this->scenes.pop_back(); + } } -void Engine::set_scene(Scene &scene) { this->scene = &scene; } +std::optional<std::shared_ptr<Scene>> Engine::pop_scene() +{ + if (this->scenes.empty()) + { + return std::nullopt; + } + std::shared_ptr<Scene> tmp = scenes.back(); + this->scenes.pop_back(); -void Engine::set_spritesheet(Spritesheet &spritesheet) { - this->spritesheet = &spritesheet; + return tmp; } -Spritesheet *Engine::get_spritesheet() { return spritesheet.value(); } +void Engine::set_spritesheet(Spritesheet& spritesheet) +{ + this->spritesheet = &spritesheet; +} -void Engine::pump() { - SDL_Event e; - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) { - this->quit = true; - } else { - this->events.push_back(e); +void Engine::pump() +{ + SDL_Event e; + while (SDL_PollEvent(&e)) + { + if (e.type == SDL_QUIT) + { + this->quit = true; + } + else + { + this->_events.push_back(e); + } } - } } -bool Engine::exited() { return this->quit; } +void Engine::exit() +{ + this->quit = true; +} -int Engine::get_stage() { return this->stage; } +bool Engine::exited() +{ + return this->quit; +} -void Engine::render() { - if (SDL_RenderClear(this->sdl_renderer) != 0) { - throw std::runtime_error("Could not clear renderer: " + - std::string(SDL_GetError())); - } +void Engine::render() +{ + if (SDL_RenderClear(this->sdl_renderer) != 0) + { + throw std::runtime_error("Could not clear renderer: " + std::string(SDL_GetError())); + } - if (!scene.has_value()) { - return; - } + std::shared_ptr<Scene> currentScene = scenes.back(); - stage = SDL_GetTicks() / 300; + currentScene->render(this); - this->scene.value()->render(*this, this->events); + SDL_RenderPresent(this->sdl_renderer); +} - SDL_RenderPresent(this->sdl_renderer); +int Engine::get_stage() +{ + return SDL_GetTicks() / 300; } -SDL_Renderer *Engine::renderer() { return this->sdl_renderer; } +Spritesheet* Engine::get_spritesheet() +{ + return spritesheet.value(); +} + +SDL_Renderer* Engine::renderer() +{ + return this->sdl_renderer; +} -Engine::~Engine() { - SDL_DestroyRenderer(sdl_renderer); - IMG_Quit(); - SDL_Quit(); +Engine::~Engine() +{ + SDL_DestroyRenderer(sdl_renderer); + IMG_Quit(); + SDL_Quit(); } } // namespace advanced_wars diff --git a/src/engine.hpp b/src/engine.hpp index 827d05d44eabb3b30f03785fbfead435046a989a..4b247e85cd1549afb1ca724aadce6927bf6168ff 100644 --- a/src/engine.hpp +++ b/src/engine.hpp @@ -1,51 +1,66 @@ #pragma once +#include "SDL_events.h" #include "scene.hpp" #include "spritesheet.hpp" #include "window.hpp" #include <SDL.h> #include <SDL_render.h> +#include <deque> +#include <memory> #include <optional> -#include <vector> -namespace advanced_wars { +namespace advanced_wars +{ + +// Forward declaration +class Scene; /** * @brief The main window of the game */ -class Engine { -public: - Engine(Window &window); +class Engine +{ + public: + Engine(Window& window); + + Engine(const Engine&) = delete; + Engine& operator=(const Engine&) = delete; + + bool exited(); + + void exit(); + + void pump(); - Engine(const Engine &) = delete; - Engine &operator=(const Engine &) = delete; + void push_scene(std::shared_ptr<Scene> scene); - bool exited(); + std::optional<std::shared_ptr<Scene>> pop_scene(); - void pump(); + void return_to_menu(); - void set_scene(Scene &scene); + std::deque<SDL_Event>& events(); - void set_spritesheet(Spritesheet &spritesheet); + void set_spritesheet(Spritesheet& spritesheet); - Spritesheet *get_spritesheet(); + Spritesheet* get_spritesheet(); - int get_stage(); + int get_stage(); - void render(); + void render(); - SDL_Renderer *renderer(); + SDL_Renderer* renderer(); - ~Engine(); + ~Engine(); -private: - Window &window; - SDL_Renderer *sdl_renderer; - std::optional<Scene *> scene; - std::optional<Spritesheet *> spritesheet; - std::vector<SDL_Event> events; - bool quit; - int stage; + private: + Window& window; + SDL_Renderer* sdl_renderer; + std::vector<std::shared_ptr<Scene>> scenes; + std::optional<Spritesheet*> spritesheet; + std::deque<SDL_Event> _events; + bool quit; + int stage; }; } // namespace advanced_wars diff --git a/src/level.cpp b/src/level.cpp index 154a2ea70a1e4a4d32e903dae96d255741d417fd..927ca47a5c15490203634e5c098e158a165274e5 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -1,225 +1,342 @@ #include "level.hpp" -#include "SDL_error.h" #include "building.hpp" #include "effect.hpp" #include "engine.hpp" #include "spritesheet.hpp" +#include "ui/contextmenu.hpp" +#include "ui/pausemenu.hpp" #include "unit.hpp" #include <SDL.h> +#include <algorithm> #include <iostream> #include <string> -#include <algorithm> namespace advanced_wars { - Level::Level(std::string name, int width, int height, std::vector<Tile> tiles, - std::vector<Building> buildings, std::vector<Unit> units, - std::vector<Effect> effects) - : name(name), width(width), height(height), tiles(tiles), - buildings(buildings), units(units), effects(effects) - { +Level::Level( + std::string name, int width, int height, std::vector<Tile> tiles, + std::vector<Building> buildings, std::vector<Unit> units, std::vector<Effect> effects) + : name(name), width(width), height(height), tiles(tiles), context_menu(ContextMenu()), + context_menu_active(false), id(0) +{ + + context_menu.setOptions({"Move", "Info", "Wait"}); + + for (Building building : buildings) + { + this->add_building(building); + } + + for (Unit unit : units) + { + this->add_unit(unit); + } + + for (Effect effect : effects) + { + this->add_effect(effect); + } if ((size_t)(width * height) != tiles.size()) { - throw std::runtime_error("level tile mismatch"); + throw std::runtime_error("level tile mismatch"); } - }; +}; - const int RENDERING_SCALE = 3; +const int RENDERING_SCALE = 3; - bool Level::click_check_left(int tileX, int tileY) - { +bool Level::click_check_left(int tileX, int tileY) +{ if (selectUnit(tileX, tileY)) { - return true; + return true; } if (selectBuilding(tileX, tileY)) { - return true; + return true; } return false; - } +} - bool Level::click_check_right(int tileX, int tileY) - { +bool Level::click_check_right(int tileX, int tileY) +{ if (target_unit(tileX, tileY)) { - return true; + return true; } return false; - } +} - bool Level::selectUnit(int tileX, int tileY) - { +bool Level::selectUnit(int tileX, int tileY) +{ // std::cout << "tileX:" << tileX << "tileX:" << tileY << std::endl; - for (auto &unit : units) + for (auto& [id, unit] : units) { - if (unit.x == tileX && unit.y == tileY) - { - // std::cout << "unitX:" << unit.x << "unitY:" << unit.y << std::endl; + if (unit.x == tileX && unit.y == tileY) + { + // std::cout << "unitX:" << unit.x << "unitY:" << unit.y << std::endl; - selectedUnit = &unit; - return true; - } + selectedUnit = id; + return true; + } } return false; - } +} - bool Level::target_unit(int tileX, int tileY) - { +bool Level::target_unit(int tileX, int tileY) +{ // std::cout << "tileX:" << tileX << "tileX:" << tileY << std::endl; - for (auto &unit : units) + for (auto& [id, unit] : units) { - if (unit.x == tileX && unit.y == tileY) - { - // std::cout << "unitX:" << unit.x << "unitY:" << unit.y << std::endl; + if (unit.x == tileX && unit.y == tileY) + { + // std::cout << "unitX:" << unit.x << "unitY:" << unit.y << std::endl; - targetedUnit = &unit; - return true; - } + targetedUnit = id; + return true; + } } return false; - } +} - bool Level::selectBuilding(int tileX, int tileY) - { +bool Level::selectBuilding(int tileX, int tileY) +{ - for (auto &building : buildings) + for (auto& [id, building] : buildings) { - if (building.x == tileX && building.y == tileY) - { - // std::cout << "X:" << unit.x << "Y:" << unit.y << std::endl; - selectedBuilding = &building; - return true; - } + if (building.x == tileX && building.y == tileY) + { + // std::cout << "X:" << unit.x << "Y:" << unit.y << std::endl; + selectedBuilding = id; + return true; + } } return false; - } +} - void Level::handleEvent(Engine &engine, SDL_Event &event) - { +void Level::handleEvent(Engine& engine, SDL_Event& event) +{ switch (event.type) { case SDL_MOUSEBUTTONDOWN: - if (event.button.button == SDL_BUTTON_LEFT) - { - - int tileX = event.button.x / (16 * RENDERING_SCALE); - int tileY = event.button.y / (16 * RENDERING_SCALE); - - if (click_check_left(tileX, tileY)) + if (event.button.button == SDL_BUTTON_LEFT) { - if (selectedUnit) - { - selectedUnit->on_left_click(event, units); - } - - if (selectedBuilding) - { - // building stuff - } + int tileX = event.button.x / (16 * RENDERING_SCALE); + int tileY = event.button.y / (16 * RENDERING_SCALE); + + if (click_check_left(tileX, tileY)) + { + + if (selectedUnit > -1) + { + units.at(selectedUnit).on_left_click(event); + } + + if (selectedBuilding > -1) + { + // building stuff + } + } + else + { + + std::cout << "Neither building nor unit clicked!" << std::endl; + selectedUnit = -1; + selectedBuilding = -1; + } } - else + else if (event.button.button == SDL_BUTTON_RIGHT) { - std::cout << "Neither building nor unit clicked!" << std::endl; - selectedUnit = nullptr; - selectedBuilding = nullptr; - } - } - else if (event.button.button == SDL_BUTTON_RIGHT) - { - - if (selectedUnit) - { + if (selectedUnit > -1) + { - int tileX = event.button.x / (16 * RENDERING_SCALE); - int tileY = event.button.y / (16 * RENDERING_SCALE); + int tileX = event.button.x / (16 * RENDERING_SCALE); + int tileY = event.button.y / (16 * RENDERING_SCALE); - if (click_check_right(tileX, tileY)) - { + if (click_check_right(tileX, tileY)) + { - selectedUnit->attack(targetedUnit); + units.at(selectedUnit).attack(&(units.at(targetedUnit))); - units.erase( - std::remove_if(units.begin(), units.end(), - [](const Unit &unit) - { return unit.health <= 0; }), - units.end()); - } - else - { + if (units.at(selectedUnit).health <= 0) + { + remove_unit(selectedUnit); + } + } + else + { - selectedUnit->update_position(tileX, tileY); - } - } - else - { + units.at(selectedUnit).update_position(tileX, tileY); + } + } + else + { - std::cout << "No unit selected! " << std::endl; + std::cout << "No unit selected! " << std::endl; + } } - } } - } +} - void Level::render(Engine &engine, std::vector<SDL_Event> &events) - { +void Level::render(Engine* engine) +{ // Iterate over all events - while (!events.empty()) + while (!engine->events().empty()) { - // events.erase(events.begin()); - - handleEvent(engine, events.at(0)); - events.erase(events.begin()); + // handleEvent(engine, engine->events().at(0)); + handleEvent(*engine, engine->events().at(0)); + engine->events().pop_front(); } // Tiles - for (Tile &tile : tiles) + for (Tile& tile : tiles) { - tile.render(engine, RENDERING_SCALE); + tile.render(engine, RENDERING_SCALE); } // Buildings - for (Building &building : buildings) + for (auto& [id, building] : buildings) { - building.render(engine, RENDERING_SCALE); + building.render(engine, RENDERING_SCALE); } // Units - for (Unit &unit : units) + for (auto& [id, unit] : units) { - unit.render(engine, RENDERING_SCALE); + unit.render(engine, RENDERING_SCALE); } // Effects - for (Effect &effect : effects) + std::vector<int> effects_to_remove; + for (auto& [id, effect] : effects) + { + if (effect.is_finished(engine)) + { + effects_to_remove.push_back(id); + } + else + { + effect.render(engine, RENDERING_SCALE); + } + } + + // Remove finished effects after iteration + for (int id : effects_to_remove) { - effect.render(engine, RENDERING_SCALE); + this->remove_effect(id); } - // Set background color for renderer - if (SDL_SetRenderDrawColor(engine.renderer(), 255, 0, 0, 0)) + if (context_menu_active) + { + context_menu.render(engine); + } +} + +void Level::handleEvent(Engine* engine, SDL_Event& event) +{ + // Handle events for the level + if (event.type == SDL_KEYDOWN) + { + if (event.key.keysym.sym == SDLK_ESCAPE) + { + // Pause the game + std::cout << "Pausing game..." << std::endl; + SDL_Texture* currentTexture = SDL_CreateTexture( + engine->renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 800, 600); + + PauseMenu pauseMenu(0, currentTexture); + engine->push_scene(std::make_shared<PauseMenu>(pauseMenu)); + } + if (context_menu_active) + { + if (event.key.keysym.sym == SDLK_DOWN) + { + context_menu.handleEvent(event); + } + if (event.key.keysym.sym == SDLK_UP) + { + context_menu.handleEvent(event); + } + if (event.key.keysym.sym == SDLK_RETURN) + { + if (context_menu.getSelectedOption() == "Wait") + { + context_menu_active = false; + } + } + } + } + if (event.type == SDL_MOUSEBUTTONDOWN) { - std::cout << "Could not set render draw color: " << SDL_GetError() - << std::endl; + context_menu.update(event.button.x, event.button.y); + context_menu_active = true; } - } +} + +int Level::add_building(Building building) +{ + buildings.insert({id, building}); + id += 1; + + return id - 1; +} + +Building Level::remove_building(int id) +{ + Building value = buildings.at(id); + buildings.erase(id); + + return value; +} + +int Level::add_unit(Unit unit) +{ + units.insert({id, unit}); + id += 1; + + return id - 1; +} + +Unit Level::remove_unit(int id) +{ + Unit value = units.at(id); + units.erase(id); + + return value; +} + +int Level::add_effect(Effect effect) +{ + effects.insert({id, effect}); + id += 1; + + return id - 1; +} + +Effect Level::remove_effect(int id) +{ + Effect value = effects.at(id); + effects.erase(id); + + return value; +} -} // namespace advanced_wars +} // namespace advanced_wars \ No newline at end of file diff --git a/src/level.hpp b/src/level.hpp index 7c5e99bad884921a5f443bafa6bb22e630344a7e..07501485dffd2ac452ad84816bc272beeebe0722 100644 --- a/src/level.hpp +++ b/src/level.hpp @@ -5,43 +5,67 @@ #include "engine.hpp" #include "scene.hpp" #include "tile.hpp" +#include "ui/contextmenu.hpp" #include "unit.hpp" #include <SDL.h> #include <string> +#include <unordered_map> #include <vector> -namespace advanced_wars { +namespace advanced_wars +{ /** * @brief The main window of the game */ -class Level : public Scene { -public: - Level(std::string name, int width, int height, std::vector<Tile> tiles, - std::vector<Building> buildings, std::vector<Unit> units, - std::vector<Effect>); - - void render(Engine &engine, std::vector<SDL_Event> &events); - - void handleEvent(Engine &engine, SDL_Event &event); - -private: - std::string name; - int width; - int height; - std::vector<Tile> tiles; - std::vector<Building> buildings; - std::vector<Unit> units; - std::vector<Effect> effects; - Unit* selectedUnit; - Unit* targetedUnit; - Building* selectedBuilding; - bool selectUnit (int tileX, int tileY); - bool target_unit (int tileX, int tileY); - bool selectBuilding(int tileX, int tileY); - - bool click_check_left(int mouseX, int mouseY); - bool click_check_right(int mouseX, int mouseY); +class Level : public Scene +{ + public: + Level( + std::string name, int width, int height, std::vector<Tile> tiles, + std::vector<Building> buildings, std::vector<Unit> units, std::vector<Effect>); + + void render(Engine* engine); + + void handleEvent(Engine* engine, SDL_Event& event); + + int add_building(Building building); + + Building remove_building(int id); + + int add_unit(Unit unit); + + Unit remove_unit(int id); + + int add_effect(Effect effect); + + Effect remove_effect(int id); + + void handleEvent(Engine& engine, SDL_Event& event); + + private: + std::string name; + int width; + int height; + + std::vector<Tile> tiles; + std::unordered_map<int, Building> buildings; + std::unordered_map<int, Unit> units; + std::unordered_map<int, Effect> effects; + int selectedUnit; + int targetedUnit; + int selectedBuilding; + bool selectUnit(int tileX, int tileY); + bool target_unit(int tileX, int tileY); + bool selectBuilding(int tileX, int tileY); + + bool click_check_left(int mouseX, int mouseY); + bool click_check_right(int mouseX, int mouseY); + + ContextMenu context_menu; + bool context_menu_active; + + int id; }; } // namespace advanced_wars diff --git a/src/main.cpp b/src/main.cpp index a6d4dd45b055f6b42dc37f39117f4b44bff1d752..3d249123e685913d5727ca85408d1db9e6548d62 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,96 +1,55 @@ -#include "building.hpp" -#include "effect.hpp" #include "engine.hpp" -#include "level.hpp" #include "spritesheet.hpp" -#include "tile.hpp" -#include "unit.hpp" +#include "ui/contextmenu.hpp" +#include "ui/menu.hpp" #include "window.hpp" -#include <cstddef> +#include <SDL2/SDL.h> +#include <SDL_image.h> +#include <memory> +#include <stdexcept> #include <vector> using namespace advanced_wars; -int main() { +int main() +{ - Window window("Advanced Wars", 960, 960); - - Engine engine(window); - - // Construct a level - std::vector<Tile> tiles; - for (int y = 0; y < 20; y++) { - for (int x = 0; x < 20; x++) { - tiles.push_back(Tile(TileId::PLAIN, x, y)); + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { + throw std::runtime_error("SDL could not initialize: " + std::string(SDL_GetError())); } - } - - // Fill the edges with water - for (size_t n = 0; n < 20; n++) { - // Vertical - tiles.at(n * 20) = Tile(TileId::WATER, 0, n); - tiles.at(n * 20 + 19) = Tile(TileId::WATER, 19, n); - // Horizontal - tiles.at(n) = Tile(TileId::WATER, n, 0); - tiles.at(19 * 20 + n) = Tile(TileId::WATER, n, 19); - } - - // Make the edges cliffs - for (size_t n = 1; n < 19; n++) { - // Vertical - tiles.at(n * 20 + 1) = Tile(TileId::CLIFF_RIGHT, 1, n); - tiles.at(n * 20 + 18) = Tile(TileId::CLIFF_LEFT, 18, n); - - // Horizontal - tiles.at(20 + n) = Tile(TileId::CLIFF_BOTTOM, n, 1); - tiles.at(18 * 20 + n) = Tile(TileId::CLIFF_TOP, n, 18); - } - // Fix the corners - tiles.at(20 + 1) = Tile(TileId::CLIFF_CORNER_TOP_LEFT, 1, 1); - tiles.at(20 + 18) = Tile(TileId::CLIFF_CORNER_TOP_RIGHT, 18, 1); - tiles.at(18 * 20 + 1) = Tile(TileId::CLIFF_CORNER_BOTTOM_LEFT, 1, 18); - tiles.at(18 * 20 + 18) = Tile(TileId::CLIFF_CORNER_BOTTOM_RIGHT, 18, 18); - - // Buildings - std::vector<Building> buildings; - - for (int y = 0; y < 6; y++) { - for (int x = 0; x < 5; x++) { - BuildingId id = static_cast<BuildingId>(x); - BuildingFaction faction = static_cast<BuildingFaction>(y); - - buildings.push_back(Building(3 + x, 3 + 2 * y, id, faction)); + int imgFlags = IMG_INIT_PNG; + if (!(IMG_Init(imgFlags) & imgFlags)) + { + throw std::runtime_error( + "SDL_image could not initialize! SDL_image Error: " + std::string(IMG_GetError())); } - } - // Units - std::vector<Unit> units; + Window window("Advanced Wars", 960, 960); - for (int y = 0; y < 19; y++) { - for (int x = 0; x < 6; x++) { - units.push_back(Unit(x + 9, y + 2, UnitFaction::URED, - static_cast<UnitId>(y), static_cast<UnitState>(x))); - } - } + Engine engine(window); - std::vector<Effect> effects({Effect(3, 15, EffectId::LAND_EXPLOSION, true), - Effect(5, 15, EffectId::AIR_EXPLOSION, true), - Effect(5, 18, EffectId::NAVAL_EXPLOSION, true)}); + Spritesheet spritesheet("/media/data/rust/sprite-extractor/spritesheet.h5", engine); - Level level("Osnabrück", 20, 20, tiles, buildings, units, effects); + engine.set_spritesheet(spritesheet); - engine.set_scene(level); + std::shared_ptr<Menu> menu = std::make_shared<Menu>(0); + std::shared_ptr<ContextMenu> context_menu = std::make_shared<ContextMenu>(); + context_menu->setOptions({"Move", "Info", "Wait"}); - Spritesheet spritesheet("./spritesheet.h5", - engine); + std::string basePath = SDL_GetBasePath(); + std::string relativePath = "assets/main_background.png"; + std::string fullPath = basePath + relativePath; + menu->loadBackground(engine.renderer(), fullPath.c_str()); - engine.set_spritesheet(spritesheet); + engine.push_scene(menu); - while (!engine.exited()) { - engine.pump(); - engine.render(); - } + while (!engine.exited()) + { + engine.pump(); + engine.render(); + } - return 0; + return 0; } diff --git a/src/scene.hpp b/src/scene.hpp index 5bd5283a07afabe5297ffc5ce98357ba2dbdfc5a..011b642e330d2fccf24a89ec0e9cbc52e69ffda5 100644 --- a/src/scene.hpp +++ b/src/scene.hpp @@ -1,15 +1,18 @@ #pragma once +#include "engine.hpp" #include <SDL.h> -#include <vector> -namespace advanced_wars { +namespace advanced_wars +{ // Forward declaration class Engine; -class Scene { -public: - virtual void render(Engine &engine, std::vector<SDL_Event> &events) = 0; +class Scene +{ + public: + virtual void render(Engine* engine) = 0; }; + } // namespace advanced_wars diff --git a/src/spritesheet.cpp b/src/spritesheet.cpp index 96a8f2ae7d9efe42218b3d47e6ad7e98ac0616b7..efd7a939de4d48d09d9207dd088627909cc66dfe 100644 --- a/src/spritesheet.cpp +++ b/src/spritesheet.cpp @@ -1,5 +1,12 @@ +/** + * Spritesheet.hpp + * + * @date 30.1.2025 + * @author Frederik Keens + * @author David Maul + */ + #include "spritesheet.hpp" -#include "SDL_pixels.h" #include "engine.hpp" #include "highfive/H5File.hpp" #include <SDL_image.h> @@ -10,377 +17,446 @@ #include <string> #include <vector> -namespace advanced_wars { - -Spritesheet::Spritesheet(std::string path, Engine &engine) { - - HighFive::File file(path, HighFive::File::ReadOnly); - - // Tiles - std::vector<std::string> tiles({"plain", - "water", - "forest", - "mountain", - "bridge_horizontal", - "bridge_vertical", - "street_horizontal", - "street_vertical", - "street_crossing", - "street_junction_right", - "street_junction_left", - "street_junction_down", - "street_junction_up", - "street_corner_top_left", - "street_corner_top_right", - "street_corner_bottom_left", - "street_corner_bottom_right", - "riff", - "cliff_top", - "cliff_bottom", - "cliff_left", - "cliff_right", - "cliff_corner_top_left", - "cliff_corner_top_right", - "cliff_corner_bottom_left", - "cliff_corner_bottom_right", - "cliff_inverse_corner_top_left", - "cliff_inverse_corner_top_right", - "cliff_inverse_corner_bottom_left", - "cliff_inverse_corner_bottom_right"}); - - for (size_t tile_idx = 0; tile_idx < tiles.size(); tile_idx++) { - HighFive::DataSet units_ds = file.getDataSet("tiles/" + tiles[tile_idx]); - - std::vector<std::vector<std::vector<uint32_t>>> tile_frames; - units_ds.read(tile_frames); - - std::vector<uint32_t> tile_buffer(16 * 16 * tile_frames.size(), 0); - - for (size_t n = 0; n < tile_frames.size(); n++) { - for (size_t y = 0; y < 16; y++) { - for (size_t x = 0; x < 16; x++) { - size_t index = (y * tile_frames.size() * 16) + (n * 16 + x); - - tile_buffer.at(index) = tile_frames.at(n).at(16 - y - 1).at(x); +namespace advanced_wars +{ + +Spritesheet::Spritesheet(std::string path, Engine& engine) +{ + + HighFive::File file(path, HighFive::File::ReadOnly); + + // Tiles + std::vector<std::string> tiles( + {"plain", + "water", + "forest", + "mountain", + "bridge_horizontal", + "bridge_vertical", + "street_horizontal", + "street_vertical", + "street_crossing", + "street_junction_right", + "street_junction_left", + "street_junction_down", + "street_junction_up", + "street_corner_top_left", + "street_corner_top_right", + "street_corner_bottom_left", + "street_corner_bottom_right", + "riff", + "cliff_top", + "cliff_bottom", + "cliff_left", + "cliff_right", + "cliff_corner_top_left", + "cliff_corner_top_right", + "cliff_corner_bottom_left", + "cliff_corner_bottom_right", + "cliff_inverse_corner_top_left", + "cliff_inverse_corner_top_right", + "cliff_inverse_corner_bottom_left", + "cliff_inverse_corner_bottom_right"}); + + // every sub data set of tiles + for (size_t tile_idx = 0; tile_idx < tiles.size(); tile_idx++) + { + HighFive::DataSet units_ds = file.getDataSet("tiles/" + tiles[tile_idx]); + + std::vector<std::vector<std::vector<uint32_t>>> tile_frames; + units_ds.read(tile_frames); + + std::vector<uint32_t> tile_buffer(16 * 16 * tile_frames.size(), 0); + + // every animation frame + for (size_t n = 0; n < tile_frames.size(); n++) + { + for (size_t y = 0; y < 16; y++) + { + for (size_t x = 0; x < 16; x++) + { + size_t index = (y * tile_frames.size() * 16) + (n * 16 + x); + + tile_buffer.at(index) = tile_frames.at(n).at(16 - y - 1).at(x); + } + } } - } - } - - SDL_Texture *tmp = SDL_CreateTexture( - engine.renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, - tile_frames.size() * 16, 16); - - SDL_SetTextureBlendMode(tmp, SDL_BLENDMODE_BLEND); - - if (tmp == nullptr) { - throw std::runtime_error( - "Fehler beim Erstellen der Textur für die Units: " + - std::string(SDL_GetError())); - } - - if (SDL_UpdateTexture(tmp, NULL, tile_buffer.data(), - tile_frames.size() * 16 * sizeof(int32_t)) != 0) { - throw std::runtime_error( - "Fehler beim updaten der Textur für die Units: " + - std::string(SDL_GetError())); - } - tile_textures.push_back( - std::pair<SDL_Texture *, int>(tmp, tile_frames.size())); - } + SDL_Texture* tmp = SDL_CreateTexture( + engine.renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, + tile_frames.size() * 16, 16); - this->tile_width = 16; - this->tile_height = 16; - - // Buildings - std::vector<std::string> building_factions( - {"red", "blue", "yellow", "green", "purple", "neutral"}); - - for (std::string faction : building_factions) { - HighFive::DataSet buildings_ds = file.getDataSet("buildings/" + faction); - - std::vector<std::vector<std::vector<uint32_t>>> buildings_frames; - - buildings_ds.read(buildings_frames); - - std::vector<uint32_t> building_buffer(32 * 16 * buildings_frames.size(), 0); - - for (size_t n = 0; n < buildings_frames.size(); n++) { - for (size_t y = 0; y < 32; y++) { - for (size_t x = 0; x < 16; x++) { - size_t index = (y * buildings_frames.size() * 16) + (n * 16 + x); + SDL_SetTextureBlendMode(tmp, SDL_BLENDMODE_BLEND); - building_buffer.at(index) = - buildings_frames.at(n).at(32 - y - 1).at(x); + if (tmp == nullptr) + { + throw std::runtime_error( + "Fehler beim Erstellen der Textur für die Units: " + std::string(SDL_GetError())); } - } - } - SDL_Texture *tmp = SDL_CreateTexture( - engine.renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, - buildings_frames.size() * 16, 32); - - SDL_SetTextureBlendMode(tmp, SDL_BLENDMODE_BLEND); - - if (tmp == nullptr) { - throw std::runtime_error( - "Fehler beim Erstellen der Textur für die Buildings: " + - std::string(SDL_GetError())); - } + if (SDL_UpdateTexture( + tmp, NULL, tile_buffer.data(), tile_frames.size() * 16 * sizeof(int32_t)) != 0) + { + throw std::runtime_error( + "Fehler beim updaten der Textur für die Units: " + std::string(SDL_GetError())); + } - if (SDL_UpdateTexture(tmp, NULL, building_buffer.data(), - buildings_frames.size() * 16 * sizeof(int32_t)) != - 0) { - throw std::runtime_error( - "Fehler beim updaten der Textur für die Buildings: " + - std::string(SDL_GetError())); + tile_textures.push_back(std::pair<SDL_Texture*, int>(tmp, tile_frames.size())); } - this->building_textures.push_back(tmp); - } - - this->building_width = 16; - this->building_height = 32; - - // Units - std::vector<std::string> unit_factions( - {"red", "blue", "green", "yellow", "purple"}); - - std::vector<std::string> units( - {"infantery", "mechanized_infantery", "recon", "medium_tank", - "heavy_tank", "neo_tank", "apc", "anti_air_tank", "artillery", - "rocket_artillery", "anti_air_missile_launcher", "fighter", "bomber", - "battle_helicopter", "transport_helicopter", "battleship", "cruiser", - "lander", "submarine"}); - - std::vector<std::string> unit_states({"idle", "unavailable"}); - std::vector<std::string> unit_movement_states( - {"left", "right", "down", "up"}); - - for (size_t faction_idx = 0; faction_idx < unit_factions.size(); - faction_idx++) { - std::string faction = unit_factions.at(faction_idx); - // Create entry for units for in a faction - unit_textures.push_back( - std::vector<std::vector<std::pair<SDL_Texture *, int>>>()); + this->tile_width = 16; + this->tile_height = 16; - for (size_t unit_idx = 0; unit_idx < units.size(); unit_idx++) { - std::string unit = units.at(unit_idx); + // Buildings + std::vector<std::string> building_factions( + {"red", "blue", "yellow", "green", "purple", "neutral"}); - // Create entry for states for a unit - unit_textures.at(faction_idx) - .push_back(std::vector<std::pair<SDL_Texture *, int>>()); + // every sub data set of buildings + for (std::string faction : building_factions) + { + HighFive::DataSet buildings_ds = file.getDataSet("buildings/" + faction); - for (size_t state_idx = 0; state_idx < unit_states.size(); state_idx++) { - std::string unit_state = unit_states.at(state_idx); + std::vector<std::vector<std::vector<uint32_t>>> buildings_frames; - HighFive::DataSet units_ds = - file.getDataSet("units/" + faction + "/" + unit + "/" + unit_state); + buildings_ds.read(buildings_frames); - std::vector<std::vector<std::vector<uint32_t>>> unit_frames; - units_ds.read(unit_frames); + std::vector<uint32_t> building_buffer(32 * 16 * buildings_frames.size(), 0); - std::vector<uint32_t> unit_buffer(16 * 16 * unit_frames.size(), 0); + // every type of building + for (size_t n = 0; n < buildings_frames.size(); n++) + { + for (size_t y = 0; y < 32; y++) + { + for (size_t x = 0; x < 16; x++) + { + size_t index = (y * buildings_frames.size() * 16) + (n * 16 + x); - for (size_t n = 0; n < unit_frames.size(); n++) { - for (size_t y = 0; y < 16; y++) { - for (size_t x = 0; x < 16; x++) { - size_t index = (y * unit_frames.size() * 16) + (n * 16 + x); - - unit_buffer.at(index) = unit_frames.at(n).at(16 - y - 1).at(x); + building_buffer.at(index) = buildings_frames.at(n).at(32 - y - 1).at(x); + } } - } } - SDL_Texture *tmp = SDL_CreateTexture( - engine.renderer(), SDL_PIXELFORMAT_RGBA8888, - SDL_TEXTUREACCESS_STATIC, unit_frames.size() * 16, 16); + SDL_Texture* tmp = SDL_CreateTexture( + engine.renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, + buildings_frames.size() * 16, 32); SDL_SetTextureBlendMode(tmp, SDL_BLENDMODE_BLEND); - if (tmp == nullptr) { - throw std::runtime_error( - "Fehler beim Erstellen der Textur für die Units: " + - std::string(SDL_GetError())); + if (tmp == nullptr) + { + throw std::runtime_error( + "Fehler beim Erstellen der Textur für die Buildings: " + + std::string(SDL_GetError())); } - if (SDL_UpdateTexture(tmp, NULL, unit_buffer.data(), - unit_frames.size() * 16 * sizeof(int32_t)) != 0) { - throw std::runtime_error( - "Fehler beim updaten der Textur für die Units: " + - std::string(SDL_GetError())); + if (SDL_UpdateTexture( + tmp, NULL, building_buffer.data(), + buildings_frames.size() * 16 * sizeof(int32_t)) != 0) + { + throw std::runtime_error( + "Fehler beim updaten der Textur für die Buildings: " + std::string(SDL_GetError())); } - unit_textures.at(faction_idx) - .at(unit_idx) - .push_back(std::pair<SDL_Texture *, int>(tmp, unit_frames.size())); - } - - for (size_t movement_state_idx = 0; - movement_state_idx < unit_movement_states.size(); - movement_state_idx++) { - std::string movement_state = - unit_movement_states.at(movement_state_idx); - - HighFive::DataSet units_ds = file.getDataSet( - "units/" + faction + "/" + unit + "/movement/" + movement_state); - - std::vector<std::vector<std::vector<uint32_t>>> unit_frames; - units_ds.read(unit_frames); + this->building_textures.push_back(tmp); + } - std::vector<uint32_t> unit_buffer(24 * 24 * unit_frames.size(), 0); + this->building_width = 16; + this->building_height = 32; + + // Units + std::vector<std::string> unit_factions({"red", "blue", "green", "yellow", "purple"}); + + std::vector<std::string> units( + {"infantery", "mechanized_infantery", "recon", "medium_tank", "heavy_tank", "neo_tank", + "apc", "anti_air_tank", "artillery", "rocket_artillery", "anti_air_missile_launcher", + "fighter", "bomber", "battle_helicopter", "transport_helicopter", "battleship", "cruiser", + "lander", "submarine"}); + + std::vector<std::string> unit_states({"idle", "unavailable"}); + std::vector<std::string> unit_movement_states({"left", "right", "down", "up"}); + + // every factions sub data set + for (size_t faction_idx = 0; faction_idx < unit_factions.size(); faction_idx++) + { + std::string faction = unit_factions.at(faction_idx); + // Create entry for units for in a faction + unit_textures.push_back(std::vector<std::vector<std::pair<SDL_Texture*, int>>>()); + + // every unit sub data set + for (size_t unit_idx = 0; unit_idx < units.size(); unit_idx++) + { + std::string unit = units.at(unit_idx); + + // Create entry for states for a unit + unit_textures.at(faction_idx).push_back(std::vector<std::pair<SDL_Texture*, int>>()); + + // every state sub data set + for (size_t state_idx = 0; state_idx < unit_states.size(); state_idx++) + { + std::string unit_state = unit_states.at(state_idx); + + HighFive::DataSet units_ds = + file.getDataSet("units/" + faction + "/" + unit + "/" + unit_state); + + std::vector<std::vector<std::vector<uint32_t>>> unit_frames; + units_ds.read(unit_frames); + + std::vector<uint32_t> unit_buffer(16 * 16 * unit_frames.size(), 0); + + for (size_t n = 0; n < unit_frames.size(); n++) + { + for (size_t y = 0; y < 16; y++) + { + for (size_t x = 0; x < 16; x++) + { + size_t index = (y * unit_frames.size() * 16) + (n * 16 + x); + + unit_buffer.at(index) = unit_frames.at(n).at(16 - y - 1).at(x); + } + } + } + + SDL_Texture* tmp = SDL_CreateTexture( + engine.renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, + unit_frames.size() * 16, 16); + + SDL_SetTextureBlendMode(tmp, SDL_BLENDMODE_BLEND); + + if (tmp == nullptr) + { + throw std::runtime_error( + "Fehler beim Erstellen der Textur für die Units: " + + std::string(SDL_GetError())); + } + + if (SDL_UpdateTexture( + tmp, NULL, unit_buffer.data(), unit_frames.size() * 16 * sizeof(int32_t)) != + 0) + { + throw std::runtime_error( + "Fehler beim updaten der Textur für die Units: " + + std::string(SDL_GetError())); + } + + unit_textures.at(faction_idx) + .at(unit_idx) + .push_back(std::pair<SDL_Texture*, int>(tmp, unit_frames.size())); + } - for (size_t n = 0; n < unit_frames.size(); n++) { - for (size_t y = 0; y < 24; y++) { - for (size_t x = 0; x < 24; x++) { - size_t index = (y * unit_frames.size() * 24) + (n * 24 + x); + // every movement state sub data set + for (size_t movement_state_idx = 0; movement_state_idx < unit_movement_states.size(); + movement_state_idx++) + { + std::string movement_state = unit_movement_states.at(movement_state_idx); + + HighFive::DataSet units_ds = file.getDataSet( + "units/" + faction + "/" + unit + "/movement/" + movement_state); + + std::vector<std::vector<std::vector<uint32_t>>> unit_frames; + units_ds.read(unit_frames); + + std::vector<uint32_t> unit_buffer(24 * 24 * unit_frames.size(), 0); + + for (size_t n = 0; n < unit_frames.size(); n++) + { + for (size_t y = 0; y < 24; y++) + { + for (size_t x = 0; x < 24; x++) + { + size_t index = (y * unit_frames.size() * 24) + (n * 24 + x); + + unit_buffer.at(index) = unit_frames.at(n).at(24 - y - 1).at(x); + } + } + } + + SDL_Texture* tmp = SDL_CreateTexture( + engine.renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, + unit_frames.size() * 24, 24); + + SDL_SetTextureBlendMode(tmp, SDL_BLENDMODE_BLEND); + + if (tmp == nullptr) + { + throw std::runtime_error( + "Fehler beim Erstellen der Textur für die Units: " + + std::string(SDL_GetError())); + } + + if (SDL_UpdateTexture( + tmp, NULL, unit_buffer.data(), unit_frames.size() * 24 * sizeof(int32_t)) != + 0) + { + throw std::runtime_error( + "Fehler beim updaten der Textur für die Units: " + + std::string(SDL_GetError())); + } + + unit_textures.at(faction_idx) + .at(unit_idx) + .push_back(std::pair<SDL_Texture*, int>(tmp, unit_frames.size())); + } + } + } - unit_buffer.at(index) = unit_frames.at(n).at(24 - y - 1).at(x); + this->unit_width = 16; + this->unit_height = 16; + this->unit_moving_width = 24; + this->unit_moving_height = 24; + + // Effects + std::vector<std::string> effects( + {"land_explosion", "air_explosion", "naval_explosion", "submarine_hide", + "submarine_appear"}); + + // Every effect sub data set + for (size_t effect_idx = 0; effect_idx < effects.size(); effect_idx++) + { + HighFive::DataSet effect_ds = file.getDataSet("effects/" + effects[effect_idx]); + + std::vector<std::vector<std::vector<uint32_t>>> effect_frames; + effect_ds.read(effect_frames); + + std::vector<uint32_t> effect_buffer(32 * 32 * effect_frames.size(), 0); + + // every animation frame + for (size_t n = 0; n < effect_frames.size(); n++) + { + for (size_t y = 0; y < 32; y++) + { + for (size_t x = 0; x < 32; x++) + { + size_t index = (y * effect_frames.size() * 32) + (n * 32 + x); + + effect_buffer.at(index) = effect_frames.at(n).at(32 - y - 1).at(x); + } } - } } - SDL_Texture *tmp = SDL_CreateTexture( - engine.renderer(), SDL_PIXELFORMAT_RGBA8888, - SDL_TEXTUREACCESS_STATIC, unit_frames.size() * 24, 24); + SDL_Texture* tmp = SDL_CreateTexture( + engine.renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, + effect_frames.size() * 32, 32); SDL_SetTextureBlendMode(tmp, SDL_BLENDMODE_BLEND); - if (tmp == nullptr) { - throw std::runtime_error( - "Fehler beim Erstellen der Textur für die Units: " + - std::string(SDL_GetError())); + if (tmp == nullptr) + { + throw std::runtime_error( + "Fehler beim Erstellen der Textur für die Effects: " + std::string(SDL_GetError())); } - if (SDL_UpdateTexture(tmp, NULL, unit_buffer.data(), - unit_frames.size() * 24 * sizeof(int32_t)) != 0) { - throw std::runtime_error( - "Fehler beim updaten der Textur für die Units: " + - std::string(SDL_GetError())); + if (SDL_UpdateTexture( + tmp, NULL, effect_buffer.data(), effect_frames.size() * 32 * sizeof(int32_t)) != 0) + { + throw std::runtime_error( + "Fehler beim updaten der Textur für die Tiles: " + std::string(SDL_GetError())); } - unit_textures.at(faction_idx) - .at(unit_idx) - .push_back(std::pair<SDL_Texture *, int>(tmp, unit_frames.size())); - } + effect_textures.push_back(std::pair<SDL_Texture*, int>(tmp, effect_frames.size())); } - } - - this->unit_width = 16; - this->unit_height = 16; - this->unit_moving_width = 24; - this->unit_moving_height = 24; - - // Effects - std::vector<std::string> effects({"land_explosion", "air_explosion", - "naval_explosion", "submarine_hide", - "submarine_appear"}); - - for (size_t effect_idx = 0; effect_idx < effects.size(); effect_idx++) { - HighFive::DataSet effect_ds = - file.getDataSet("effects/" + effects[effect_idx]); - - std::vector<std::vector<std::vector<uint32_t>>> effect_frames; - effect_ds.read(effect_frames); - - std::vector<uint32_t> effect_buffer(32 * 32 * effect_frames.size(), 0); - - for (size_t n = 0; n < effect_frames.size(); n++) { - for (size_t y = 0; y < 32; y++) { - for (size_t x = 0; x < 32; x++) { - size_t index = (y * effect_frames.size() * 32) + (n * 32 + x); - - effect_buffer.at(index) = effect_frames.at(n).at(32 - y - 1).at(x); - } - } - } - - SDL_Texture *tmp = SDL_CreateTexture( - engine.renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, - effect_frames.size() * 32, 32); - - SDL_SetTextureBlendMode(tmp, SDL_BLENDMODE_BLEND); - - if (tmp == nullptr) { - throw std::runtime_error( - "Fehler beim Erstellen der Textur für die Effects: " + - std::string(SDL_GetError())); - } - - if (SDL_UpdateTexture(tmp, NULL, effect_buffer.data(), - effect_frames.size() * 32 * sizeof(int32_t)) != 0) { - throw std::runtime_error( - "Fehler beim updaten der Textur für die Tiles: " + - std::string(SDL_GetError())); - } - - effect_textures.push_back( - std::pair<SDL_Texture *, int>(tmp, effect_frames.size())); - } - this->effect_width = 32; - this->effect_height = 32; + this->effect_width = 32; + this->effect_height = 32; } // Tiles -int Spritesheet::get_tile_width() { return tile_width; } +int Spritesheet::get_tile_width() +{ + return tile_width; +} -int Spritesheet::get_tile_height() { return tile_height; } +int Spritesheet::get_tile_height() +{ + return tile_height; +} -std::vector<std::pair<SDL_Texture *, int>> &Spritesheet::get_tile_textures() { - return tile_textures; +std::vector<std::pair<SDL_Texture*, int>>& Spritesheet::get_tile_textures() +{ + return tile_textures; } // Buildings -int Spritesheet::get_building_width() { return this->building_width; } +int Spritesheet::get_building_width() +{ + return this->building_width; +} -int Spritesheet::get_building_height() { return this->building_height; } +int Spritesheet::get_building_height() +{ + return this->building_height; +} -std::vector<SDL_Texture *> &Spritesheet::get_building_textures() { - return building_textures; +std::vector<SDL_Texture*>& Spritesheet::get_building_textures() +{ + return building_textures; } // Units -int Spritesheet::get_unit_width() { return this->unit_width; } +int Spritesheet::get_unit_width() +{ + return this->unit_width; +} -int Spritesheet::get_unit_height() { return this->unit_height; } +int Spritesheet::get_unit_height() +{ + return this->unit_height; +} -int Spritesheet::get_unit_moving_width() { return this->unit_moving_width; } +int Spritesheet::get_unit_moving_width() +{ + return this->unit_moving_width; +} -int Spritesheet::get_unit_moving_height() { return this->unit_moving_height; } +int Spritesheet::get_unit_moving_height() +{ + return this->unit_moving_height; +} -std::vector<std::vector<std::vector<std::pair<SDL_Texture *, int>>>> & -Spritesheet::get_unit_textures() { - return this->unit_textures; +std::vector<std::vector<std::vector<std::pair<SDL_Texture*, int>>>>& +Spritesheet::get_unit_textures() +{ + return this->unit_textures; } // Effects -int Spritesheet::get_effect_width() { return this->effect_width; } +int Spritesheet::get_effect_width() +{ + return this->effect_width; +} -int Spritesheet::get_effect_height() { return this->effect_height; } +int Spritesheet::get_effect_height() +{ + return this->effect_height; +} -std::vector<std::pair<SDL_Texture *, int>> &Spritesheet::get_effect_textures() { - return this->effect_textures; +std::vector<std::pair<SDL_Texture*, int>>& Spritesheet::get_effect_textures() +{ + return this->effect_textures; } -Spritesheet::~Spritesheet() { - for (std::pair<SDL_Texture *, int> tile_texture : tile_textures) { - SDL_DestroyTexture(tile_texture.first); - } - - for (SDL_Texture *building_texture : building_textures) { - SDL_DestroyTexture(building_texture); - } - - for (std::vector<std::vector<std::pair<SDL_Texture *, int>>> faction : - unit_textures) { - for (std::vector<std::pair<SDL_Texture *, int>> unit : faction) { - for (std::pair<SDL_Texture *, int> state : unit) { - SDL_DestroyTexture(state.first); - } +Spritesheet::~Spritesheet() +{ + for (std::pair<SDL_Texture*, int> tile_texture : tile_textures) + { + SDL_DestroyTexture(tile_texture.first); + } + + for (SDL_Texture* building_texture : building_textures) + { + SDL_DestroyTexture(building_texture); + } + + for (std::vector<std::vector<std::pair<SDL_Texture*, int>>> faction : unit_textures) + { + for (std::vector<std::pair<SDL_Texture*, int>> unit : faction) + { + for (std::pair<SDL_Texture*, int> state : unit) + { + SDL_DestroyTexture(state.first); + } + } } - } } } // namespace advanced_wars \ No newline at end of file diff --git a/src/spritesheet.hpp b/src/spritesheet.hpp index d7d8b0776b3a0b86f3b0936626f1a7b6a3933eb4..c6ae316725e2d6f088be7052cd904277efde047f 100644 --- a/src/spritesheet.hpp +++ b/src/spritesheet.hpp @@ -1,81 +1,180 @@ +/** + * Spritesheet.hpp + * + * @date 30.1.2025 + * @author Frederik Keens + * @author David Maul + */ + #pragma once +#include <SDL_render.h> #include <SDL.h> #include <SDL_render.h> #include <string> #include <vector> -namespace advanced_wars { +namespace advanced_wars +{ // Forward declaration class Engine; -class Spritesheet { -public: - Spritesheet(std::string path, Engine &engine); - - ~Spritesheet(); - - Spritesheet(const Spritesheet &) = delete; - - Spritesheet &operator=(const Spritesheet &) = delete; - - // Tiles - - int get_tile_width(); - - int get_tile_height(); - - std::vector<std::pair<SDL_Texture *, int>> &get_tile_textures(); - - // Buildings - int get_building_width(); - - int get_building_height(); - - std::vector<SDL_Texture *> &get_building_textures(); - - // Units - int get_unit_width(); - - int get_unit_height(); - - int get_unit_moving_width(); - - int get_unit_moving_height(); - - std::vector<std::vector<std::vector<std::pair<SDL_Texture *, int>>>> & - get_unit_textures(); - - // Effects - int get_effect_width(); - - int get_effect_height(); - - std::vector<std::pair<SDL_Texture *, int>> &get_effect_textures(); - -private: - // Tiles - int tile_width; - int tile_height; - std::vector<std::pair<SDL_Texture *, int>> tile_textures; - - // Buildings - std::vector<SDL_Texture *> building_textures; - int building_width; - int building_height; - - // Units - std::vector<std::vector<std::vector<std::pair<SDL_Texture *, int>>>> - unit_textures; - int unit_width; - int unit_height; - int unit_moving_width; - int unit_moving_height; - - // Effects - std::vector<std::pair<SDL_Texture *, int>> effect_textures; - int effect_width; - int effect_height; +/** + * Spritesheet representation + */ +class Spritesheet +{ + public: + /** + * Constructor + * + * @param path Path to the file to load the spritesheet from + * @param path Engine object with valid SDL context + */ + Spritesheet(std::string path, Engine& engine); + + /** + * Destructor + */ + ~Spritesheet(); + + Spritesheet(const Spritesheet&) = delete; + + Spritesheet& operator=(const Spritesheet&) = delete; + + // Tiles + + /** + * @return The width of a floor tile in pixels + */ + int get_tile_width(); + + /** + * @return The height of a floor tile in pixels + */ + int get_tile_height(); + + /** + * Gets vector containing the pairs of an SDL Texture + * and the number animations steps it has. + * + * E.g. The vector at 1 contains a pair of an SDL Texture for water, its animations + * and the number of animation steps. Animation frames are store linearised. + * + * Which index respresents which tile can be found in the enum TileId of tile.hpp + * + * @return A vector of all floor tile textures and their animations + */ + std::vector<std::pair<SDL_Texture*, int>>& get_tile_textures(); + + // Buildings + + /** + * @return The width of a building in pixels + */ + int get_building_width(); + + /** + * @return The height of a building in pixels + */ + int get_building_height(); + + /** + * Every element represents the texture for all buildings from a faction linearised. + * + * Which FactionId represents which color + * can be found in the BuildingId enum in building.hpp + * + * Order of the building sprites is the same as in buildingId enum in building.hpp + * + * @return Vector of all Building textures + */ + std::vector<SDL_Texture*>& get_building_textures(); + + // Units + + /** + * @return The width of a unit while standing still in pixels + */ + int get_unit_width(); + + /** + * @return The height of a unit while standing still in pixels + */ + int get_unit_height(); + + /** + * @return The width of a unit while moving in pixels + */ + int get_unit_moving_width(); + + /** + * @return The height of a unit while moving in pixels + */ + int get_unit_moving_height(); + + /** + * Gets the hierarchical vector of all unit textures. + * + * The vector groups the faction, the UnitId and the state for each unit. + * The pair consist of the texture for that combination and the number of animation steps. + * + * E.g. A red faction(0) Recon(2) Unit that's unavailable(1) would be at [0][2][1]. + * Animation frames are linearised. + * + * Indices can be found in the enums of unit.hpp + * + * @return A 3-dimensional vector of pairs consisting of a texture and + * the number of animation steps + */ + std::vector<std::vector<std::vector<std::pair<SDL_Texture*, int>>>>& get_unit_textures(); + + // Effects + + /** + * @return The width of an effect in pixels + */ + int get_effect_width(); + + /** + * @return The height of an effect in pixels + */ + int get_effect_height(); + + /** + * Vector that contains pairs of effect textures and the number of animation steps it has. + * + * Each texture of an element is a pair of an effect + * and all its animation frames linearised. + * + * Which index represents which effect can be found in the EffectId enum in effect.hpp + * + * @return A vector of all effects and its animations + */ + std::vector<std::pair<SDL_Texture*, int>>& get_effect_textures(); + + private: + // Tiles + int tile_width; + int tile_height; + std::vector<std::pair<SDL_Texture*, int>> tile_textures; + + // Buildings + std::vector<SDL_Texture*> building_textures; + int building_width; + int building_height; + + // Units + std::vector<std::vector<std::vector<std::pair<SDL_Texture*, int>>>> unit_textures; + int unit_width; + int unit_height; + int unit_moving_width; + int unit_moving_height; + + // Effects + std::vector<std::pair<SDL_Texture*, int>> effect_textures; + int effect_width; + int effect_height; }; } // namespace advanced_wars diff --git a/src/tile.cpp b/src/tile.cpp index 909b7fcf6254c09e6f7047a23c2036ada8e13615..525ee8385468b07df1f3ebcddf57be84e523b7e6 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -2,35 +2,33 @@ #include "spritesheet.hpp" #include <vector> -namespace advanced_wars { +namespace advanced_wars +{ -Tile::Tile(TileId id, int x, int y) - : id(id), x(x), y(y) { +Tile::Tile(TileId id, int x, int y) : id(id), x(x), y(y) {} - }; +void Tile::render(Engine* engine, int scale) +{ + Spritesheet* spritesheet = engine->get_spritesheet(); -void Tile::render(Engine &engine, int scale) { - Spritesheet *spritesheet = engine.get_spritesheet(); + int step = + engine->get_stage() % spritesheet->get_tile_textures().at(static_cast<int>(id)).second; - int step = engine.get_stage() % - spritesheet->get_tile_textures().at(static_cast<int>(id)).second; + SDL_Rect src; + src.x = step * spritesheet->get_tile_width(); + src.y = 0; + src.w = spritesheet->get_tile_width(); + src.h = spritesheet->get_tile_height(); - SDL_Rect src; - src.x = step * spritesheet->get_tile_width(); - src.y = 0; - src.w = spritesheet->get_tile_width(); - src.h = spritesheet->get_tile_height(); + SDL_Rect dest; + dest.x = x * spritesheet->get_tile_width() * scale; + dest.y = y * spritesheet->get_tile_height() * scale; + dest.w = spritesheet->get_tile_width() * scale; + dest.h = spritesheet->get_tile_height() * scale; - SDL_Rect dest; - dest.x = x * spritesheet->get_tile_width() * scale; - dest.y = y * spritesheet->get_tile_height() * scale; - dest.w = spritesheet->get_tile_width() * scale; - dest.h = spritesheet->get_tile_height() * scale; - - SDL_RenderCopyEx( - engine.renderer(), - spritesheet->get_tile_textures().at(static_cast<int>(id)).first, &src, - &dest, 0, NULL, SDL_FLIP_NONE); + SDL_RenderCopyEx( + engine->renderer(), spritesheet->get_tile_textures().at(static_cast<int>(id)).first, &src, + &dest, 0, NULL, SDL_FLIP_NONE); } } // namespace advanced_wars \ No newline at end of file diff --git a/src/tile.hpp b/src/tile.hpp index cf54b32b274b3314cc5a8701eee978c20618ef94..ae147b84426fff75b54d48488a706a516853ecff 100644 --- a/src/tile.hpp +++ b/src/tile.hpp @@ -3,49 +3,52 @@ #include "engine.hpp" #include "scene.hpp" -namespace advanced_wars { +namespace advanced_wars +{ -enum class TileId { - PLAIN = 0, - WATER = 1, - FOREST = 2, - MOUNTAIN = 3, - BRIDGE_HORIZONTAL = 4, - BRIDGE_VERTICAL = 5, - STREET_HORIZONTAL = 6, - STREET_VERTICAL = 7, - STREET_CROSSING = 8, - STREET_JUNCTION_RIGHT = 9, - STREET_JUNCTION_LEFT = 10, - STREET_JUNCTION_DOWN = 11, - STREET_JUNCTION_UP = 12, - STREET_CORNER_TOP_LEFT = 13, - STREET_CORNER_TOP_RIGHT = 14, - STREET_CORNER_BOTTOM_LEFT = 15, - STREET_CORNER_BOTTOM_RIGHT = 16, - RIFF = 17, - CLIFF_TOP = 18, - CLIFF_BOTTOM = 19, - CLIFF_LEFT = 20, - CLIFF_RIGHT = 21, - CLIFF_CORNER_TOP_LEFT = 22, - CLIFF_CORNER_TOP_RIGHT = 23, - CLIFF_CORNER_BOTTOM_LEFT = 24, - CLIFF_CORNER_BOTTOM_RIGHT = 25, - CLIFF_INVERSE_CORNER_TOP_LEFT = 26, - CLIFF_INVERSE_CORNER_TOP_RIGHT = 27, - CLIFF_INVERSE_CORNER_BOTTOM_LEFT = 28, - CLIFF_INVERSE_CORNER_BOTTOM_RIGHT = 29, +enum class TileId +{ + PLAIN = 0, + WATER = 1, + FOREST = 2, + MOUNTAIN = 3, + BRIDGE_HORIZONTAL = 4, + BRIDGE_VERTICAL = 5, + STREET_HORIZONTAL = 6, + STREET_VERTICAL = 7, + STREET_CROSSING = 8, + STREET_JUNCTION_RIGHT = 9, + STREET_JUNCTION_LEFT = 10, + STREET_JUNCTION_DOWN = 11, + STREET_JUNCTION_UP = 12, + STREET_CORNER_TOP_LEFT = 13, + STREET_CORNER_TOP_RIGHT = 14, + STREET_CORNER_BOTTOM_LEFT = 15, + STREET_CORNER_BOTTOM_RIGHT = 16, + RIFF = 17, + CLIFF_TOP = 18, + CLIFF_BOTTOM = 19, + CLIFF_LEFT = 20, + CLIFF_RIGHT = 21, + CLIFF_CORNER_TOP_LEFT = 22, + CLIFF_CORNER_TOP_RIGHT = 23, + CLIFF_CORNER_BOTTOM_LEFT = 24, + CLIFF_CORNER_BOTTOM_RIGHT = 25, + CLIFF_INVERSE_CORNER_TOP_LEFT = 26, + CLIFF_INVERSE_CORNER_TOP_RIGHT = 27, + CLIFF_INVERSE_CORNER_BOTTOM_LEFT = 28, + CLIFF_INVERSE_CORNER_BOTTOM_RIGHT = 29, }; -class Tile { -public: - Tile(TileId id, int x, int y); - TileId id; - int x; - int y; +class Tile +{ + public: + Tile(TileId id, int x, int y); + TileId id; + int x; + int y; - void render(Engine &engine, int scale); + void render(Engine* engine, int scale); }; } // namespace advanced_wars \ No newline at end of file diff --git a/src/ui/contextmenu.cpp b/src/ui/contextmenu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8bae5ff334cb15200d3cac4f296e266f871960c9 --- /dev/null +++ b/src/ui/contextmenu.cpp @@ -0,0 +1,83 @@ +#include "contextmenu.hpp" +#include <iostream> +#include <SDL_ttf.h> + +namespace advanced_wars { + + ContextMenu::ContextMenu() + : selectedOption(0) {} + + ContextMenu::~ContextMenu() {} + + void ContextMenu::setOptions(const std::vector<std::string>& newOptions) { + options = newOptions; + selectedOption = 0; // Reset auf die erste Option + } + + void ContextMenu::render(Engine* engine) { + if (!options.empty()) { + if (TTF_Init() == -1) { + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + return; + } + + std::string basePath = SDL_GetBasePath(); + std::string relativePath = "assets/ARCADECLASSIC.TTF"; + std::string fullPath = basePath + relativePath; + TTF_Font *font = TTF_OpenFont(fullPath.c_str(), 16); + if (!font) { + std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; + return; + } + + SDL_Color white = {255, 255, 255, 255}; + SDL_Color yellow = {192, 255, 0, 255}; + + int spacing = 20; // Abstand zwischen den Optionen + + //box around options + SDL_SetRenderDrawColor(engine->renderer(), 0, 0, 255, 255); + SDL_Rect box = {x, y - 3, 50, static_cast<int>(options.size() * spacing)}; + SDL_RenderFillRect(engine->renderer(), &box); + + SDL_SetRenderDrawColor(engine->renderer(), 0, 0, 0, 255); + + for (size_t i = 0; i < options.size(); ++i) { + SDL_Surface* textSurface = TTF_RenderText_Solid(font, options[i].c_str(), (i == selectedOption) ? yellow : white); + if (!textSurface) { + continue; + } + + SDL_Texture* textTexture = SDL_CreateTextureFromSurface(engine->renderer(), textSurface); + SDL_Rect textRect = {x+10, y + static_cast<int>(i * spacing), textSurface->w, textSurface->h}; + SDL_RenderCopy(engine->renderer(), textTexture, nullptr, &textRect); + + SDL_DestroyTexture(textTexture); + SDL_FreeSurface(textSurface); + } + + TTF_CloseFont(font); + TTF_Quit(); + } + } + + void ContextMenu::handleEvent(SDL_Event& event) { + if (event.type == SDL_KEYDOWN) { + if (event.key.keysym.sym == SDLK_DOWN) { + selectedOption = (selectedOption + 1) % options.size(); + } else if (event.key.keysym.sym == SDLK_UP) { + selectedOption = (selectedOption - 1 + options.size()) % options.size(); + } + } + } + + std::string ContextMenu::getSelectedOption() { + return options[selectedOption]; + } + + void ContextMenu::update(int x, int y) { + this->x = x; + this->y = y; + } + +} diff --git a/src/ui/contextmenu.hpp b/src/ui/contextmenu.hpp new file mode 100644 index 0000000000000000000000000000000000000000..94b095f928fa6a4a67f691e22b3799b8ac767599 --- /dev/null +++ b/src/ui/contextmenu.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include <SDL.h> +#include <vector> +#include <string> +#include "../scene.hpp" +#include "../engine.hpp" + +namespace advanced_wars { + +class ContextMenu : public Scene { +private: + size_t selectedOption; + std::vector<std::string> options; + int x; + int y; + +public: + ContextMenu(); + + void setOptions(const std::vector<std::string>& newOptions); + + void render(Engine* engine) override; + + void handleEvent(SDL_Event& event); + + void update(int x, int y); + + std::string getSelectedOption(); + + ~ContextMenu(); +}; + +} \ No newline at end of file diff --git a/src/ui/menu.cpp b/src/ui/menu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..802972994206363a794af2c88d8ea9df188cd803 --- /dev/null +++ b/src/ui/menu.cpp @@ -0,0 +1,249 @@ +#include "menu.hpp" +#include "../building.hpp" +#include "../level.hpp" +#include "../spritesheet.hpp" +#include "../tile.hpp" +#include "../unit.hpp" +#include <SDL.h> +#include <SDL_image.h> +#include <SDL_ttf.h> +#include <iostream> +#include <string> + +namespace advanced_wars +{ + +Menu::Menu(int selectedOption) + : selectedOption(selectedOption), options({"Start Game", "Options", "Exit"}), + backgroundTexture(nullptr) +{ + if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) + { + std::cerr << "Failed to initialize SDL_image: " << IMG_GetError() << std::endl; + } +} + +Menu::~Menu() +{ + if (backgroundTexture) + { + SDL_DestroyTexture(backgroundTexture); + } + IMG_Quit(); +}; + +void Menu::render(Engine* engine) +{ + + // Iterate over all events + while (!engine->events().empty()) + { + SDL_Event event = engine->events().at(0); + engine->events().pop_front(); + handleEvent(engine, event); + } + + if (backgroundTexture) + { + SDL_RenderCopy(engine->renderer(), backgroundTexture, nullptr, nullptr); + } + else + { + SDL_SetRenderDrawColor(engine->renderer(), 0, 0, 0, 255); + SDL_RenderClear(engine->renderer()); + } + + if (TTF_Init() == -1) + { + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + return; + } + + std::string basePath = SDL_GetBasePath(); + std::string relativePath = "assets/ARCADECLASSIC.TTF"; + std::string fullPath = basePath + relativePath; + TTF_Font* titleFont = TTF_OpenFont(fullPath.c_str(), 48); + if (!titleFont) + { + std::cerr << "Failed to load title font: " << fullPath << TTF_GetError() << std::endl; + return; + } + + TTF_Font* menuFont = TTF_OpenFont(fullPath.c_str(), 24); + if (!menuFont) + { + TTF_CloseFont(titleFont); + std::cerr << "Failed to load menu font: " << fullPath << TTF_GetError() << std::endl; + return; + } + + SDL_Color white = {255, 255, 255, 255}; + SDL_Color yellow = {255, 255, 0, 255}; + + SDL_Surface* titleSurface = TTF_RenderText_Solid(titleFont, "Advanced Wars", white); + if (titleSurface) + { + SDL_Texture* titleTexture = SDL_CreateTextureFromSurface(engine->renderer(), titleSurface); + SDL_Rect titleRect = { + static_cast<int>((800 - titleSurface->w) / 2), 50, titleSurface->w, titleSurface->h}; + SDL_RenderCopy(engine->renderer(), titleTexture, nullptr, &titleRect); + SDL_DestroyTexture(titleTexture); + SDL_FreeSurface(titleSurface); + } + + for (size_t i = 0; i < options.size(); ++i) + { + SDL_Surface* textSurface = TTF_RenderText_Solid( + menuFont, options[i].c_str(), (i == selectedOption) ? yellow : white); + if (!textSurface) + { + continue; + } + + SDL_Texture* textTexture = SDL_CreateTextureFromSurface(engine->renderer(), textSurface); + SDL_Rect textRect = { + static_cast<int>((800 - textSurface->w) / 2), static_cast<int>(150 + i * 50), + textSurface->w, textSurface->h}; + SDL_RenderCopy(engine->renderer(), textTexture, nullptr, &textRect); + + SDL_DestroyTexture(textTexture); + SDL_FreeSurface(textSurface); + } + + TTF_CloseFont(titleFont); + TTF_CloseFont(menuFont); + TTF_Quit(); + + SDL_RenderPresent(engine->renderer()); +} + +void Menu::handleEvent(Engine* engine, SDL_Event& event) +{ + if (event.type == SDL_KEYDOWN) + { + if (event.key.keysym.sym == SDLK_DOWN) + { + selectedOption = (selectedOption + 1) % options.size(); + } + else if (event.key.keysym.sym == SDLK_UP) + { + selectedOption = (selectedOption - 1 + options.size()) % options.size(); + } + else if (event.key.keysym.sym == SDLK_RETURN) + { + if (options[selectedOption] == "Exit") + { + std::cout << "Exiting game..." << std::endl; + engine->exit(); + } + else if (options[selectedOption] == "Start Game") + { + std::cout << "Starting game..." << std::endl; + + // Construct a level + std::vector<Tile> tiles; + for (int y = 0; y < 20; y++) + { + for (int x = 0; x < 20; x++) + { + tiles.push_back(Tile(TileId::PLAIN, x, y)); + } + } + + // Fill the edges with water + for (size_t n = 0; n < 20; n++) + { + // Vertical + tiles.at(n * 20) = Tile(TileId::WATER, 0, n); + tiles.at(n * 20 + 19) = Tile(TileId::WATER, 19, n); + // Horizontal + tiles.at(n) = Tile(TileId::WATER, n, 0); + tiles.at(19 * 20 + n) = Tile(TileId::WATER, n, 19); + } + + // Make the edges cliffs + for (size_t n = 1; n < 19; n++) + { + // Vertical + tiles.at(n * 20 + 1) = Tile(TileId::CLIFF_RIGHT, 1, n); + tiles.at(n * 20 + 18) = Tile(TileId::CLIFF_LEFT, 18, n); + + // Horizontal + tiles.at(20 + n) = Tile(TileId::CLIFF_BOTTOM, n, 1); + tiles.at(18 * 20 + n) = Tile(TileId::CLIFF_TOP, n, 18); + } + + // Fix the corners + tiles.at(20 + 1) = Tile(TileId::CLIFF_CORNER_TOP_LEFT, 1, 1); + tiles.at(20 + 18) = Tile(TileId::CLIFF_CORNER_TOP_RIGHT, 18, 1); + tiles.at(18 * 20 + 1) = Tile(TileId::CLIFF_CORNER_BOTTOM_LEFT, 1, 18); + tiles.at(18 * 20 + 18) = Tile(TileId::CLIFF_CORNER_BOTTOM_RIGHT, 18, 18); + + // Buildings + std::vector<Building> buildings; + + for (int y = 0; y < 6; y++) + { + for (int x = 0; x < 5; x++) + { + BuildingId id = static_cast<BuildingId>(x); + BuildingFaction faction = static_cast<BuildingFaction>(y); + + buildings.push_back(Building(3 + x, 3 + 2 * y, id, faction)); + } + } + + // Units + std::vector<Unit> units; + + for (int y = 0; y < 19; y++) + { + for (int x = 0; x < 6; x++) + { + units.push_back(Unit( + x + 9, y + 2, UnitFaction::URED, static_cast<UnitId>(y), + static_cast<UnitState>(x))); + } + } + + std::vector<Effect> effects( + {Effect(3, 15, EffectId::LAND_EXPLOSION, false), + Effect(5, 15, EffectId::AIR_EXPLOSION, true), + Effect(5, 18, EffectId::NAVAL_EXPLOSION, true)}); + + std::shared_ptr<Level> level = + std::make_shared<Level>("Osnabrück", 20, 20, tiles, buildings, units, effects); + + engine->push_scene(level); + } + else if (options[selectedOption] == "Options") + { + std::cout << "Opening options..." << std::endl; + } + } + } +} + +void Menu::loadBackground(SDL_Renderer* renderer, const std::string& imagePath) +{ + // Lade das Hintergrundbild + SDL_Surface* backgroundSurface = IMG_Load(imagePath.c_str()); + if (!backgroundSurface) + { + std::cerr << "Failed to load background image: " << IMG_GetError() << std::endl; + return; + } + + // Erstelle eine Textur aus der Oberfläche und speichere sie als + // Klassenmitglied + backgroundTexture = SDL_CreateTextureFromSurface(renderer, backgroundSurface); + SDL_FreeSurface(backgroundSurface); // Oberfläche freigeben, da sie nicht mehr + // benötigt wird + + if (!backgroundTexture) + { + std::cerr << "Failed to create background texture: " << SDL_GetError() << std::endl; + } +} + +} // namespace advanced_wars diff --git a/src/ui/menu.hpp b/src/ui/menu.hpp new file mode 100644 index 0000000000000000000000000000000000000000..43f84d081e9611a251a5a2430d35d9dde9771ffa --- /dev/null +++ b/src/ui/menu.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include "../scene.hpp" +#include <SDL.h> +#include <array> +#include <iostream> +#include <string> +#include <vector> + +namespace advanced_wars { + +/** + * @class Menu + * @brief Represents the main menu of the game, allowing navigation between different options. + * + * This menu provides three selectable options: + * - "Start Game": Begins a new game session. + * - "Options": Opens the game settings. + * - "Exit": Closes the application. + */ +class Menu : public Scene { +private: + size_t selectedOption; ///< Index of the currently selected menu option. + std::array<std::string, 3> options; ///< The available menu options. + SDL_Texture *backgroundTexture; ///< Pointer to the background texture (if any). + +public: + /** + * @brief Constructs the Menu with an initial selected option. + * + * Initializes the menu with the available options and sets the currently + * selected option based on the given index. + * + * @param selectedOption The index of the initially selected menu option. + */ + Menu(int selectedOption); + + /** + * @brief Renders the menu on the screen. + * + * This method clears the screen, draws the background (if available), + * renders the menu title, and displays the selectable options. The currently + * selected option is highlighted in a different color. + * + * @param engine Pointer to the game engine, used for rendering. + */ + void render(Engine *engine) override; + + /** + * @brief Handles user input events for menu navigation. + * + * This method processes keyboard input to navigate through the menu options. + * - **Arrow Down (`SDLK_DOWN`)**: Moves the selection to the next option. + * - **Arrow Up (`SDLK_UP`)**: Moves the selection to the previous option. + * - **Enter (`SDLK_RETURN`)**: Confirms the selection: + * - **"Start Game"**: Loads the game scene. + * - **"Options"**: Opens the settings menu. + * - **"Exit"**: Closes the application. + * + * @param engine Pointer to the game engine, used to manage scenes. + * @param event The SDL event containing user input data. + */ + void handleEvent(Engine *engine, SDL_Event &event); + + /** + * @brief Loads a background image as a texture. + * + * This method loads an image file, converts it into an SDL texture, and + * assigns it as the menu's background. If the loading fails, an error is + * logged, and the menu will display a plain black background instead. + * + * @param renderer The SDL renderer used to create the texture. + * @param imagePath The file path to the background image. + */ + void loadBackground(SDL_Renderer *renderer, const std::string &imagePath); + + /** + * @brief Destroys the menu and releases resources. + * + * Cleans up allocated resources, including the background texture (if loaded), + * and ensures that SDL_Image is properly shut down. + */ + ~Menu(); +}; + +} // namespace advanced_wars \ No newline at end of file diff --git a/src/ui/pausemenu.cpp b/src/ui/pausemenu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0dd592841e6e0fa352fca4b483d3e6e839dd5923 --- /dev/null +++ b/src/ui/pausemenu.cpp @@ -0,0 +1,118 @@ +#include "pausemenu.hpp" +#include "../engine.hpp" +#include <SDL_ttf.h> + +namespace advanced_wars { + +PauseMenu::PauseMenu(int selectedOption, SDL_Texture *backgroundTexture) + : selectedOption(selectedOption), + options({"Resume", "Options", "Exit"}), backgroundTexture(backgroundTexture) { + // Initialize SDL_ttf + if (TTF_Init() == -1) { + std::cerr << "Failed to initialize SDL_ttf: " << TTF_GetError() << std::endl; + } + + if (!backgroundTexture) { + this->backgroundTexture = nullptr; + } +} + +PauseMenu::~PauseMenu() { + if (backgroundTexture) { + SDL_DestroyTexture(backgroundTexture); + backgroundTexture = nullptr; + } + TTF_Quit(); +} + +void PauseMenu::render(Engine *engine) { + + while (!engine->events().empty()) { + SDL_Event event = engine->events().at(0); + engine->events().pop_front(); + handleEvent(engine, event); + } + + SDL_Renderer *renderer = engine->renderer(); + + // Render the existing level + //engine->render(); + + // Render the dialog background + if (backgroundTexture) { + SDL_RenderCopy(renderer, backgroundTexture, nullptr, nullptr); + } + + if (TTF_Init() == -1) { + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + return; + } + + // Render the dialog options on top of the background + std::string basePath = SDL_GetBasePath(); + std::string relativePath = "assets/ARCADECLASSIC.TTF"; + std::string fullPath = basePath + relativePath; + + TTF_Font *font = TTF_OpenFont(fullPath.c_str(), 24); + if (!font) { + std::cerr << "Failed to load menu font: " << fullPath << " " << TTF_GetError() + << std::endl; + return; + } + + SDL_Color white = {255, 255, 255, 255}; + SDL_Color yellow = {255, 255, 0, 255}; + + for (size_t i = 0; i < options.size(); ++i) { + SDL_Surface *textSurface = TTF_RenderText_Solid( + font, options[i].c_str(), (i == selectedOption) ? yellow : white); + SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface); + + SDL_Rect destRect = {100, static_cast<int>(100 + i * 50), textSurface->w, textSurface->h}; + SDL_RenderCopy(renderer, textTexture, nullptr, &destRect); + + SDL_FreeSurface(textSurface); + SDL_DestroyTexture(textTexture); + } + TTF_CloseFont(font); + SDL_RenderPresent(renderer); +} + +void PauseMenu::handleEvent(Engine *engine, SDL_Event &event) { + if (event.type == SDL_KEYDOWN) { + if (event.key.keysym.sym == SDLK_DOWN) { + selectedOption = (selectedOption + 1) % options.size(); + } else if (event.key.keysym.sym == SDLK_UP) { + selectedOption = (selectedOption - 1 + options.size()) % options.size(); + } else if (event.key.keysym.sym == SDLK_ESCAPE) { + std::cout << "Resuming game..." << std::endl; + engine->pop_scene(); + } else if (event.key.keysym.sym == SDLK_RETURN) { + if (options[selectedOption] == "Exit") { + // exit into main menu + std::cout << "Exiting game..." << std::endl; + engine->return_to_menu(); + } else if (options[selectedOption] == "Resume") { + // resume game + std::cout << "Resuming game..." << std::endl; + engine->pop_scene(); + } + } + + } + // Handle events for the pause menu +} + + + +void PauseMenu::loadBackground(SDL_Renderer *renderer, const std::string &imagePath) { + SDL_Surface *surface = IMG_Load(imagePath.c_str()); + if (!surface) { + std::cerr << "Failed to load image: " << IMG_GetError() << std::endl; + return; + } + backgroundTexture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_FreeSurface(surface); +} + +} // namespace advanced_wars \ No newline at end of file diff --git a/src/ui/pausemenu.hpp b/src/ui/pausemenu.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6dc2d2cec236d7d614e8d012ce2ce935675c233d --- /dev/null +++ b/src/ui/pausemenu.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include "../scene.hpp" +#include <SDL.h> +#include <array> +#include <iostream> +#include <string> +#include <vector> +#include <SDL_image.h> +#include <SDL_ttf.h> + +namespace advanced_wars { + +/** + * @class PauseMenu + * @brief A scene that represents the in-game pause menu. + * + * The pause menu allows the player to: + * - **Resume**: Return to the current game scene. + * - **Options**: (Currently not implemented). + * - **Exit**: Return to the main menu. + * + * The menu supports keyboard navigation and responds to user input. + */ +class PauseMenu : public Scene { +private: + size_t selectedOption; ///< Index of the currently selected menu option. + std::array<std::string, 3> options; ///< The available pause menu options. + SDL_Texture *backgroundTexture; ///< Pointer to the background texture (if available). + +public: + /** + * @brief Constructs the pause menu with a background texture. + * + * The pause menu initializes the menu options and stores the provided + * background texture. If no texture is provided, a default black background is used. + * + * @param selectedOption The index of the initially selected menu option. + * @param backgroundTexture A pointer to the background texture (can be nullptr). + */ + PauseMenu(int selectedOption, SDL_Texture *backgroundTexture); + + /** + * @brief Renders the pause menu on the screen. + * + * This method: + * - Draws the background (if available). + * - Displays the menu options with the currently selected option highlighted. + * - Presents the rendered frame to the screen. + * + * @param engine Pointer to the game engine, used for rendering. + */ + void render(Engine *engine) override; + + /** + * @brief Handles user input events for menu navigation. + * + * This method processes keyboard input to navigate and interact with the pause menu. + * - **Arrow Down (`SDLK_DOWN`)**: Moves the selection to the next option. + * - **Arrow Up (`SDLK_UP`)**: Moves the selection to the previous option. + * - **Escape (`SDLK_ESCAPE`)**: Resumes the game by removing the pause menu. + * - **Enter (`SDLK_RETURN`)**: Executes the selected option: + * - **"Resume"**: Closes the pause menu and resumes the game. + * - **"Exit"**: Returns to the main menu. + * + * @param engine Pointer to the game engine, used to manage scenes. + * @param event The SDL event containing user input data. + */ + void handleEvent(Engine *engine, SDL_Event &event); + + /** + * @brief Loads a background image as a texture. + * + * This method loads an image file, converts it into an SDL texture, and assigns it + * as the menu’s background. If the loading fails, an error is logged, and the menu + * will display a plain black background instead. + * + * @param renderer The SDL renderer used to create the texture. + * @param imagePath The file path to the background image. + */ + void loadBackground(SDL_Renderer *renderer, const std::string &imagePath); + + /** + * @brief Destroys the pause menu and releases resources. + * + * Cleans up allocated resources, including the background texture (if loaded), + * and ensures that SDL_ttf is properly shut down. + */ + ~PauseMenu(); +}; + +} // namespace advanced_wars \ No newline at end of file diff --git a/src/unit.cpp b/src/unit.cpp index c938f48d1a0d250738a6adbe0c620057b2230d42..4ece9fb5a13f8ca81b247be886cbc35f50d7e26a 100644 --- a/src/unit.cpp +++ b/src/unit.cpp @@ -1,187 +1,219 @@ #include "unit.hpp" #include <iostream> -namespace advanced_wars { +namespace advanced_wars +{ Unit::Unit(int x, int y, UnitFaction faction, UnitId id, UnitState state) - : x(x), y(y), faction(faction), id(id), state(state), max_health(100) { - // das ist nur für Testzwecke - if (id == UnitId::INFANTERY) { - secondary_weapon = Weapon("Machine-Gun", {{UnitId::INFANTERY, 55}}); - } - health = max_health; -}; - -void Unit::render(Engine &engine, int scale) { - Spritesheet *spritesheet = engine.get_spritesheet(); - - int step = engine.get_stage() % spritesheet->get_unit_textures() - .at(static_cast<int>(faction)) - .at(static_cast<int>(id)) - .at(static_cast<int>(state)) - .second; - - if (state == UnitState::IDLE || state == UnitState::UNAVAILABLE) { - - SDL_Rect src; - src.x = step * spritesheet->get_unit_width(); - src.y = 0; - src.w = spritesheet->get_unit_width(); - src.h = spritesheet->get_unit_height(); - - SDL_Rect dst; - dst.x = x * spritesheet->get_unit_width() * scale; - dst.y = y * spritesheet->get_unit_height() * scale; - dst.w = spritesheet->get_unit_width() * scale; - dst.h = spritesheet->get_unit_height() * scale; - - SDL_RenderCopyEx(engine.renderer(), - spritesheet->get_unit_textures() - .at(static_cast<int>(faction)) - .at(static_cast<int>(id)) - .at(static_cast<int>(state)) - .first, - &src, &dst, 0, NULL, SDL_FLIP_NONE); - } else { - // The moving states have a resolution of 24x24 instead of 16x16 and need to - // be handled separately - SDL_Rect src; - src.x = step * spritesheet->get_unit_moving_width(); - src.y = 0; - src.w = spritesheet->get_unit_moving_width(); - src.h = spritesheet->get_unit_moving_height(); - - SDL_Rect dst; - dst.x = ((x * spritesheet->get_unit_width()) - 4) * scale; - dst.y = ((y * spritesheet->get_unit_height()) - 4) * scale; - dst.w = spritesheet->get_unit_moving_width() * scale; - dst.h = spritesheet->get_unit_moving_height() * scale; - - SDL_RenderCopyEx(engine.renderer(), - spritesheet->get_unit_textures() - .at(static_cast<int>(faction)) - .at(static_cast<int>(id)) - .at(static_cast<int>(state)) - .first, - &src, &dst, 0, NULL, SDL_FLIP_NONE); - } + : x(x), y(y), faction(faction), id(id), state(state), max_health(100) +{ + // das ist nur für Testzwecke + if (id == UnitId::INFANTERY) + { + secondary_weapon = Weapon( + "Machine-Gun", { + {UnitId::INFANTERY, 55} + }); + } + health = max_health; +} + +void Unit::render(Engine* engine, int scale) +{ + Spritesheet* spritesheet = engine->get_spritesheet(); + + int step = engine->get_stage() % spritesheet->get_unit_textures() + .at(static_cast<int>(faction)) + .at(static_cast<int>(id)) + .at(static_cast<int>(state)) + .second; + + if (state == UnitState::IDLE || state == UnitState::UNAVAILABLE) + { + + SDL_Rect src; + src.x = step * spritesheet->get_unit_width(); + src.y = 0; + src.w = spritesheet->get_unit_width(); + src.h = spritesheet->get_unit_height(); + + SDL_Rect dst; + dst.x = x * spritesheet->get_unit_width() * scale; + dst.y = y * spritesheet->get_unit_height() * scale; + dst.w = spritesheet->get_unit_width() * scale; + dst.h = spritesheet->get_unit_height() * scale; + + SDL_RenderCopyEx( + engine->renderer(), + spritesheet->get_unit_textures() + .at(static_cast<int>(faction)) + .at(static_cast<int>(id)) + .at(static_cast<int>(state)) + .first, + &src, &dst, 0, NULL, SDL_FLIP_NONE); + } + else + { + // The moving states have a resolution of 24x24 instead of 16x16 and need to + // be handled separately + SDL_Rect src; + src.x = step * spritesheet->get_unit_moving_width(); + src.y = 0; + src.w = spritesheet->get_unit_moving_width(); + src.h = spritesheet->get_unit_moving_height(); + + SDL_Rect dst; + dst.x = ((x * spritesheet->get_unit_width()) - 4) * scale; + dst.y = ((y * spritesheet->get_unit_height()) - 4) * scale; + dst.w = spritesheet->get_unit_moving_width() * scale; + dst.h = spritesheet->get_unit_moving_height() * scale; + + SDL_RenderCopyEx( + engine->renderer(), + spritesheet->get_unit_textures() + .at(static_cast<int>(faction)) + .at(static_cast<int>(id)) + .at(static_cast<int>(state)) + .first, + &src, &dst, 0, NULL, SDL_FLIP_NONE); + } } -void Unit::attack(Unit *enemy) { - // Angenommen, primary_weapon und secondary_weapon wurden bereits korrekt - // initialisiert - auto primary_weapon_damage_it = primary_weapon.damage.find(enemy->id); - auto secondary_weapon_damage_it = secondary_weapon.damage.find(enemy->id); +void Unit::attack(Unit* enemy) +{ + // Angenommen, primary_weapon und secondary_weapon wurden bereits korrekt + // initialisiert + auto primary_weapon_damage_it = primary_weapon.damage.find(enemy->id); + auto secondary_weapon_damage_it = secondary_weapon.damage.find(enemy->id); + + int attacker_damage_value = 0; - int attacker_damage_value = 0; + // Die Waffe mit dem höchsten Schaden wählen + if (secondary_weapon_damage_it != secondary_weapon.damage.end()) + { + attacker_damage_value = secondary_weapon_damage_it->second; + } - // Die Waffe mit dem höchsten Schaden wählen - if (secondary_weapon_damage_it != secondary_weapon.damage.end()) { - attacker_damage_value = secondary_weapon_damage_it->second; - } + if (primary_weapon_damage_it != primary_weapon.damage.end()) + { + if (primary_weapon_damage_it->second > attacker_damage_value) + { + // Munitionsabzug sollte hier erfolgen, falls zutreffend + attacker_damage_value = primary_weapon_damage_it->second; + } + } - if (primary_weapon_damage_it != primary_weapon.damage.end()) { - if (primary_weapon_damage_it->second > attacker_damage_value) { - // Munitionsabzug sollte hier erfolgen, falls zutreffend - attacker_damage_value = primary_weapon_damage_it->second; + if (attacker_damage_value == 0) + { + std::cout << "No damage value found for attack from unit " << static_cast<int>(id) + << " against unit " << static_cast<int>(enemy->id) << std::endl; } - } - - if (attacker_damage_value == 0) { - std::cout << "No damage value found for attack from unit " - << static_cast<int>(id) << " against unit " - << static_cast<int>(enemy->id) << std::endl; - } else { - int off_damage = - attacker_damage_value * (static_cast<float>(health) / max_health); - enemy->health -= off_damage; - enemy->health = std::max( - 0, - enemy->health); // Sicherstellen, dass die Gesundheit nicht negativ wird - std::cout << "Enemy health after attack: " << enemy->health << std::endl; - - // Prüfen, ob der Gegner noch am Leben ist um zurückzuschlagen - if (enemy->health > 0) { - // Weapon tables for the defender - auto defender_primary_weapon_damage_it = - enemy->primary_weapon.damage.find(id); - auto defender_secondary_weapon_damage_it = - enemy->secondary_weapon.damage.find(id); - - int defender_damage_value = 0; // Declare outside for later use - - // Determine the damage value for the defender - if (defender_secondary_weapon_damage_it != - enemy->secondary_weapon.damage.end()) { - defender_damage_value = defender_secondary_weapon_damage_it->second; - } - - if (defender_primary_weapon_damage_it != - enemy->primary_weapon.damage.end()) { - if (defender_primary_weapon_damage_it->second > defender_damage_value) { - // Munitionsabzug für primäre Waffe, falls zutreffend - defender_damage_value = defender_primary_weapon_damage_it->second; + else + { + int off_damage = attacker_damage_value * (static_cast<float>(health) / max_health); + enemy->health -= off_damage; + enemy->health = std::max( + 0, + enemy->health); // Sicherstellen, dass die Gesundheit nicht negativ wird + std::cout << "Enemy health after attack: " << enemy->health << std::endl; + + // Prüfen, ob der Gegner noch am Leben ist um zurückzuschlagen + if (enemy->health > 0) + { + // Weapon tables for the defender + auto defender_primary_weapon_damage_it = enemy->primary_weapon.damage.find(id); + auto defender_secondary_weapon_damage_it = enemy->secondary_weapon.damage.find(id); + + int defender_damage_value = 0; // Declare outside for later use + + // Determine the damage value for the defender + if (defender_secondary_weapon_damage_it != enemy->secondary_weapon.damage.end()) + { + defender_damage_value = defender_secondary_weapon_damage_it->second; + } + + if (defender_primary_weapon_damage_it != enemy->primary_weapon.damage.end()) + { + if (defender_primary_weapon_damage_it->second > defender_damage_value) + { + // Munitionsabzug für primäre Waffe, falls zutreffend + defender_damage_value = defender_primary_weapon_damage_it->second; + } + } + + // If a valid damage value was determined for retaliation + if (defender_damage_value > 0) + { + int def_damage = static_cast<int>( + defender_damage_value * static_cast<float>(enemy->health) / enemy->max_health); + this->health -= def_damage; + this->health = std::max(0, this->health); // Safeguard against negative health + std::cout << "Ally health after retaliation: " << this->health << std::endl; + } } - } - - // If a valid damage value was determined for retaliation - if (defender_damage_value > 0) { - int def_damage = static_cast<int>(defender_damage_value * - static_cast<float>(enemy->health) / - enemy->max_health); - this->health -= def_damage; - this->health = - std::max(0, this->health); // Safeguard against negative health - std::cout << "Ally health after retaliation: " << this->health - << std::endl; - } } - } } -void Unit::update_position(int posX, int posY) { - calc_state(posX, posY); +void Unit::update_position(int posX, int posY) +{ + calc_state(posX, posY); - this->x = posX; - this->y = posY; + this->x = posX; + this->y = posY; } -void Unit::calc_state(int posX, int posY) { - int deltaX = this->x - posX; - int deltaY = this->y - posY; +void Unit::calc_state(int posX, int posY) +{ + int deltaX = this->x - posX; + int deltaY = this->y - posY; - if (deltaX == 0 && deltaY == 0) { - // Unit is already at the target position - return; - } + if (deltaX == 0 && deltaY == 0) + { + // Unit is already at the target position + return; + } - if (abs(deltaX) >= abs(deltaY)) { - if (deltaX > 0) { - this->state = advanced_wars::UnitState::MOVEMENTLEFT; - } else { - this->state = advanced_wars::UnitState::MOVEMENTRIGHT; + if (abs(deltaX) >= abs(deltaY)) + { + if (deltaX > 0) + { + this->state = advanced_wars::UnitState::MOVEMENTLEFT; + } + else + { + this->state = advanced_wars::UnitState::MOVEMENTRIGHT; + } } - } else { - if (deltaY > 0) { - this->state = advanced_wars::UnitState::MOVEMENTUP; - } else { - this->state = advanced_wars::UnitState::MOVEMENTDOWN; + else + { + if (deltaY > 0) + { + this->state = advanced_wars::UnitState::MOVEMENTUP; + } + else + { + this->state = advanced_wars::UnitState::MOVEMENTDOWN; + } } - } } -void Unit::on_left_click(SDL_Event event, std::vector<Unit> &unitVector) { +void Unit::on_left_click(SDL_Event event) +{ - std::cout << "Left-button pressed on unit: " << this->health << std::endl; + std::cout << "Left-button pressed on unit: " << this->health << std::endl; } -bool Unit::inRange(Unit *enemy) { - if (this->x == enemy->x) { - return abs(this->y - enemy->y) <= this->range; - } else if (this->y == enemy->y) { - return abs(this->x - enemy->x) <= this->range; - } - return false; +bool Unit::inRange(Unit* enemy) +{ + if (this->x == enemy->x) + { + return abs(this->y - enemy->y) <= this->range; + } + else if (this->y == enemy->y) + { + return abs(this->x - enemy->x) <= this->range; + } + return false; } } // namespace advanced_wars \ No newline at end of file diff --git a/src/unit.hpp b/src/unit.hpp index 722e52f74039c3d3f2897697ff08d4482515499f..489dd3c45a606ce7d21bc44c7cd26ec3175ea99a 100644 --- a/src/unit.hpp +++ b/src/unit.hpp @@ -1,137 +1,139 @@ #pragma once -#include <unordered_map> #include "engine.hpp" #include "weapon.hpp" #include <optional> +#include <unordered_map> -namespace advanced_wars { +namespace advanced_wars +{ -enum class UnitFaction { - URED = 0, - UBLUE = 1, - UGREEN = 2, - UYELLOW = 3, - UPURPLE = 4, +enum class UnitFaction +{ + URED = 0, + UBLUE = 1, + UGREEN = 2, + UYELLOW = 3, + UPURPLE = 4, }; -enum class UnitId { - INFANTERY = 0, - MECHANIZED_INFANTERY = 1, - RECON = 2, - MEDIUM_TANK = 3, - HEAVY_TANK = 4, - NEO_TANK = 5, - APC = 6, - ANTI_AIR_TANK = 7, - ARTILLERY = 8, - ROCKET_ARTILLERY = 9, - ANTI_AIR_MISSILE_LAUNCHER = 10, - FIGHTER = 11, - BOMBER = 12, - BATTLE_HELICOPTER = 13, - TRANSPORT_HELICOPTER = 14, - BATTLESHIP = 15, - CRUISER = 16, - LANDER = 17, - SUBMARINE = 18, +enum class UnitId +{ + INFANTERY = 0, + MECHANIZED_INFANTERY = 1, + RECON = 2, + MEDIUM_TANK = 3, + HEAVY_TANK = 4, + NEO_TANK = 5, + APC = 6, + ANTI_AIR_TANK = 7, + ARTILLERY = 8, + ROCKET_ARTILLERY = 9, + ANTI_AIR_MISSILE_LAUNCHER = 10, + FIGHTER = 11, + BOMBER = 12, + BATTLE_HELICOPTER = 13, + TRANSPORT_HELICOPTER = 14, + BATTLESHIP = 15, + CRUISER = 16, + LANDER = 17, + SUBMARINE = 18, }; -enum class UnitState { - IDLE = 0, - UNAVAILABLE = 1, - MOVEMENTLEFT = 2, - MOVEMENTRIGHT = 3, - MOVEMENTDOWN = 4, - MOVEMENTUP = 5, +enum class UnitState +{ + IDLE = 0, + UNAVAILABLE = 1, + MOVEMENTLEFT = 2, + MOVEMENTRIGHT = 3, + MOVEMENTDOWN = 4, + MOVEMENTUP = 5, }; -enum class MovementType { - FOOT = 0, - TIRES = 1, - TREAD = 2, - AIR = 3, - SHIP = 4, - LANDER = 5, +enum class MovementType +{ + FOOT = 0, + TIRES = 1, + TREAD = 2, + AIR = 3, + SHIP = 4, + LANDER = 5, }; using MatchupTable = std::unordered_map<UnitId, std::unordered_map<UnitId, int>>; -class Unit { -public: - int x; - int y; - int health; //health equals max_health at construction - - - Unit(int x, int y, UnitFaction faction, UnitId id, UnitState state); - ~Unit() { - //Assuming that the destruktion of a unit triggers events - } - - void render(Engine &engine, int scale); - - /* - Check if attacker is in Range to initiate combat - TODO: This should probably tie back into rendering the units differently - If a unit is selected, it should call inRange on all other enemy units on the field - */ - - bool inRange(Unit *enemy); - - /* - The attacker will move towards the defender and thus initiate combat - @params Takes a reference to the defender - - Will Update the health for both units - Attacker deals damage to the defender first - */ - - void attack(Unit* enemy) ; - - - /* - @params Takes the desired position of the unit and updates its values - This will teleport the unit, there is no smooth transition between tiles - */ - void update_position(int posX, int posY); - - /* - This function needs to be able to determine the possible movement-paths the unit can take - MUST take into consideration that different units behave differently on certain terrain - MUST show all movements possible - */ - void calculate_movement(); - - - void calc_state(int posX, int posY); - -/* -This function will be called by an external event-handler, eventually. -It should start displaying standard unit information, such as UI and move_range -*/ -void on_left_click(SDL_Event event, std::vector<Unit> &unitVector); - -private: - UnitFaction faction; - UnitId id; - UnitState state; - - - int max_health; // max_health required for damage_scaling - int range; - int fuel; - int max_fuel; - - bool has_moved; - bool has_attacked; - bool is_selected; - bool is_targeted; - Weapon secondary_weapon; - Weapon primary_weapon; - - int ammo; - +class Unit +{ + public: + int x; + int y; + int health; // health equals max_health at construction + + Unit(int x, int y, UnitFaction faction, UnitId id, UnitState state); + ~Unit() + { + // Assuming that the destruktion of a unit triggers events + } + + void render(Engine* engine, int scale); + + /* + Check if attacker is in Range to initiate combat + TODO: This should probably tie back into rendering the units differently + If a unit is selected, it should call inRange on all other enemy units on the field + */ + + bool inRange(Unit* enemy); + + /* + The attacker will move towards the defender and thus initiate combat + @params Takes a reference to the defender + + Will Update the health for both units + Attacker deals damage to the defender first + */ + + void attack(Unit* enemy); + + /* + @params Takes the desired position of the unit and updates its values + This will teleport the unit, there is no smooth transition between tiles + */ + void update_position(int posX, int posY); + + /* + This function needs to be able to determine the possible movement-paths the unit can take + MUST take into consideration that different units behave differently on certain terrain + MUST show all movements possible + */ + void calculate_movement(); + + void calc_state(int posX, int posY); + + /* + This function will be called by an external event-handler, eventually. + It should start displaying standard unit information, such as UI and move_range + */ + void on_left_click(SDL_Event event); + + private: + UnitFaction faction; + UnitId id; + UnitState state; + + int max_health; // max_health required for damage_scaling + int range; + int fuel; + int max_fuel; + + bool has_moved; + bool has_attacked; + bool is_selected; + bool is_targeted; + Weapon secondary_weapon; + Weapon primary_weapon; + + int ammo; }; } // namespace advanced_wars \ No newline at end of file diff --git a/src/weapon.cpp b/src/weapon.cpp index b93d09cf32fd3804e954c8614b13c8738a22ba91..e324130e26681f5cef3d0334e60abb90086c4aa7 100644 --- a/src/weapon.cpp +++ b/src/weapon.cpp @@ -2,26 +2,28 @@ namespace advanced_wars { - Weapon::Weapon() : name(""), damage() {} +Weapon::Weapon() : name(""), damage() {} - Weapon::Weapon(const std::string &weaponName, const std::unordered_map<UnitId, int> &damageValues) - : name(weaponName), damage(damageValues) {} +Weapon::Weapon(const std::string& weaponName, const std::unordered_map<UnitId, int>& damageValues) + : name(weaponName), damage(damageValues) +{ +} - // Funktion zum Hinzufügen von Schadenswerten - void Weapon::addDamageValue(UnitId unitId, int value) - { - damage[unitId] = value; - } +// Funktion zum Hinzufügen von Schadenswerten +void Weapon::addDamageValue(UnitId unitId, int value) +{ + damage[unitId] = value; +} - // Funktion zum Abrufen eines Schadenswertes - int Weapon::getDamageValue(UnitId unitId) const +// Funktion zum Abrufen eines Schadenswertes +int Weapon::getDamageValue(UnitId unitId) const +{ + auto it = damage.find(unitId); + if (it != damage.end()) { - auto it = damage.find(unitId); - if (it != damage.end()) - { - return it->second; - } - return 0; // oder ein Fehlerwert + return it->second; } + return 0; // oder ein Fehlerwert +} -} \ No newline at end of file +} // namespace advanced_wars \ No newline at end of file diff --git a/src/weapon.hpp b/src/weapon.hpp index 2192e61bd4732434f93fedccc971833cde491d43..028eef1a67f30ea825b2a5c2b633d48af2c20216 100644 --- a/src/weapon.hpp +++ b/src/weapon.hpp @@ -3,27 +3,29 @@ #include <string> #include <unordered_map> -namespace advanced_wars { +namespace advanced_wars +{ enum class UnitId; -class Weapon { -public: - // Konstruktoren - Weapon(); - Weapon(const std::string &weaponName, const std::unordered_map<UnitId, int> &damageValues); +class Weapon +{ + public: + // Konstruktoren + Weapon(); + Weapon(const std::string& weaponName, const std::unordered_map<UnitId, int>& damageValues); - // Methode, um einen Schadenswert hinzuzufügen - void addDamageValue(UnitId unitId, int value); + // Methode, um einen Schadenswert hinzuzufügen + void addDamageValue(UnitId unitId, int value); - // Methode, um einen Schadenswert abzurufen - int getDamageValue(UnitId unitId) const; + // Methode, um einen Schadenswert abzurufen + int getDamageValue(UnitId unitId) const; - // Name der Waffe - std::string name; + // Name der Waffe + std::string name; - // Schadenstabelle - std::unordered_map<UnitId, int> damage; + // Schadenstabelle + std::unordered_map<UnitId, int> damage; }; } // namespace advanced_wars diff --git a/src/window.cpp b/src/window.cpp index aa7568152015a5a476320381b6caafe34e11f0eb..926cbf79b73a88cf72a7c6384b8b1d5cf788078a 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1,35 +1,49 @@ #include "window.hpp" #include <stdexcept> -namespace advanced_wars { - -Window::Window(std::string title, int w, int h) { - /// Init width and height - width = w; - height = h; - - // Generate SDL main window - window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, width, height, - SDL_WINDOW_SHOWN); - - if (window == nullptr) { - throw std::runtime_error("SDL window could not be generated: " + - std::string(SDL_GetError())); - } +namespace advanced_wars +{ + +Window::Window(std::string title, int w, int h) +{ + /// Init width and height + width = w; + height = h; + + // Generate SDL main window + window = SDL_CreateWindow( + title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, + SDL_WINDOW_SHOWN); + + if (window == nullptr) + { + throw std::runtime_error( + "SDL window could not be generated: " + std::string(SDL_GetError())); + } } -int Window::w() { return width; } +int Window::w() +{ + return width; +} -int Window::h() { return height; } +int Window::h() +{ + return height; +} -SDL_Window *Window::sdl_window() { return window; } +SDL_Window* Window::sdl_window() +{ + return window; +} -Window::~Window() { - if (window) { - SDL_DestroyWindow(window); - window = nullptr; - } +Window::~Window() +{ + if (window) + { + SDL_DestroyWindow(window); + window = nullptr; + } } } // namespace advanced_wars diff --git a/src/window.hpp b/src/window.hpp index 9cd664dc9f4a22e9384b668f41735309e0d412b3..a53586bebf4b7701d89b7539cbc175fa0a938202 100644 --- a/src/window.hpp +++ b/src/window.hpp @@ -3,50 +3,52 @@ #include <SDL.h> #include <string> -namespace advanced_wars { +namespace advanced_wars +{ /** * @brief The main window of the game */ -class Window { -public: - /*** - * Creates a main window with given \ref title, width \ref w and height \ref h - * - * @param title Title of the window - * @param w Width - * @param h Height - */ - Window(std::string title, int w, int h); - - /** - * Forbids the creation of copies of a window - */ - Window(const Window &) = delete; - // Window& operator=(const Window&) = delete; - - /*** - * Destructor. - */ - ~Window(); - - /// Retruns the current width of the window - int w(); - - /// Returns the current height of the window - int h(); - - SDL_Window *sdl_window(); - -private: - /// SDL main window struct - SDL_Window *window; - - /// Window width - int width; - - /// Window height - int height; +class Window +{ + public: + /*** + * Creates a main window with given \ref title, width \ref w and height \ref h + * + * @param title Title of the window + * @param w Width + * @param h Height + */ + Window(std::string title, int w, int h); + + /** + * Forbids the creation of copies of a window + */ + Window(const Window&) = delete; + Window& operator=(const Window&) = delete; + + /*** + * Destructor. + */ + ~Window(); + + /// Retruns the current width of the window + int w(); + + /// Returns the current height of the window + int h(); + + SDL_Window* sdl_window(); + + private: + /// SDL main window struct + SDL_Window* window; + + /// Window width + int width; + + /// Window height + int height; }; } // namespace advanced_wars