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/config.xml b/config.xml new file mode 100644 index 0000000000000000000000000000000000000000..d240753866aa0ca5f50957e79e65dd67cdd2b2b1 --- /dev/null +++ b/config.xml @@ -0,0 +1,569 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Units> + <!-- Infantry Unit --> + <Unit key="infantry"> + <Cost>1000</Cost> + <MovementPoints>3</MovementPoints> + <MovementType>Foot</MovementType> + <Ammo>0</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <SecondaryWeapon name="Machine-Gun"> + <DamageTable> + <Damage unitId="infantry" value="55"/> + <Damage unitId="mechanized_infantry" value="45"/> + <Damage unitId="recon" value="12"/> + <Damage unitId="apc" value="14"/> + <Damage unitId="anti_air_tank" value="5"/> + <Damage unitId="medium_tank" value="5"/> + <Damage unitId="heavy_tank" value="1"/> + <Damage unitId="neotank" value="1"/> + <Damage unitId="artillery" value="15"/> + <Damage unitId="rocket_artillery" value="25"/> + <Damage unitId="anti_air_missile_launcher" value="25"/> + <Damage unitId="transport_helicopter" value="30"/> + <Damage unitId="battle_helicopter" value="7"/> + </DamageTable> + </SecondaryWeapon> + </Weapons> + </Unit> + + <!-- Mechanized Infantry Unit --> + <Unit key="mechanized_infantry"> + <Cost>3000</Cost> + <MovementPoints>2</MovementPoints> + <MovementType>Foot</MovementType> + <Ammo>9</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <PrimaryWeapon name="Bazooka"> + <DamageTable> + + <Damage unitId="recon" value="85"/> + <Damage unitId="apc" value="75"/> + <Damage unitId="anti_air_tank" value="65"/> + <Damage unitId="medium_tank" value="55"/> + <Damage unitId="heavy_tank" value="15"/> + <Damage unitId="neotank" value="15"/> + <Damage unitId="artillery" value="70"/> + <Damage unitId="rocket_artillery" value="85"/> + <Damage unitId="anti_air_missile_launcher" value="85"/> + + </DamageTable> + </PrimaryWeapon> + <SecondaryWeapon name="Machine-Gun"> + <DamageTable> + <Damage unitId="infantry" value="65"/> + <Damage unitId="mechanized_infantry" value="55"/> + <Damage unitId="recon" value="18"/> + <Damage unitId="apc" value="20"/> + <Damage unitId="anti_air_tank" value="6"/> + <Damage unitId="medium_tank" value="6"/> + <Damage unitId="heavy_tank" value="1"/> + <Damage unitId="neotank" value="1"/> + <Damage unitId="artillery" value="32"/> + <Damage unitId="rocket_artillery" value="35"/> + <Damage unitId="anti_air_missile_launcher" value="32"/> + <Damage unitId="transport_helicopter" value="35"/> + <Damage unitId="battle_helicopter" value="9"/> + </DamageTable> + </SecondaryWeapon> + </Weapons> + </Unit> + + <!-- Recon Unit --> + <Unit key="recon"> + <Cost>4000</Cost> + <MovementPoints>8</MovementPoints> + <MovementType>Wheeled</MovementType> + <Ammo>0</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <SecondaryWeapon name="Machine-Gun"> + <DamageTable> + <Damage unitId="infantry" value="70"/> + <Damage unitId="mechanized_infantry" value="65"/> + <Damage unitId="recon" value="35"/> + <Damage unitId="apc" value="45"/> + <Damage unitId="anti_air_tank" value="4"/> + <Damage unitId="medium_tank" value="6"/> + <Damage unitId="heavy_tank" value="1"/> + <Damage unitId="neotank" value="1"/> + <Damage unitId="artillery" value="45"/> + <Damage unitId="rocket_artillery" value="55"/> + <Damage unitId="anti_air_missile_launcher" value="28"/> + <Damage unitId="transport_helicopter" value="35"/> + <Damage unitId="battle_helicopter" value="10"/> + </DamageTable> + </SecondaryWeapon> + </Weapons> + </Unit> + + <!-- APC Unit --> + <Unit key="apc"> + <Cost>5000</Cost> + <MovementPoints>6</MovementPoints> + <MovementType>Tread</MovementType> + <Ammo>0</Ammo> + <minRange>0</minRange> + <maxRange>0</maxRange> + <Weapons> + <!-- Keine Waffen, da APC als Transportfahrzeug dient --> + </Weapons> + </Unit> + + <!-- Anti-Air Tank Unit --> + <Unit key="anti_air_tank"> + <Cost>8000</Cost> + <MovementPoints>6</MovementPoints> + <MovementType>Tread</MovementType> + <Ammo>9</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <PrimaryWeapon name="Vulcan"> + <DamageTable> + <Damage unitId="infantry" value="105"/> + <Damage unitId="mechanized_infantry" value="105"/> + <Damage unitId="recon" value="60"/> + <Damage unitId="apc" value="50"/> + <Damage unitId="anti_air_tank" value="45"/> + <Damage unitId="medium_tank" value="25"/> + <Damage unitId="heavy_tank" value="10"/> + <Damage unitId="neotank" value="5"/> + <Damage unitId="artillery" value="50"/> + <Damage unitId="rocket_artillery" value="55"/> + <Damage unitId="anti_air_missile_launcher" value="55"/> + <Damage unitId="transport_helicopter" value="105"/> + <Damage unitId="battle_helicopter" value="105"/> + <Damage unitId="stealth" value="75"/> + <Damage unitId="fighter" value="65"/> + <Damage unitId="bomber" value="75"/> + </DamageTable> + </PrimaryWeapon> + </Weapons> + </Unit> + + <!-- Medium Tank Unit --> + <Unit key="medium_tank"> + <Cost>7000</Cost> + <MovementPoints>6</MovementPoints> + <MovementType>Tread</MovementType> + <Ammo>9</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <PrimaryWeapon name="Cannon"> + <DamageTable> + <Damage unitId="recon" value="85"/> + <Damage unitId="apc" value="75"/> + <Damage unitId="anti_air_tank" value="65"/> + <Damage unitId="medium_tank" value="55"/> + <Damage unitId="heavy_tank" value="15"/> + <Damage unitId="neotank" value="15"/> + <Damage unitId="artillery" value="70"/> + <Damage unitId="rocket_artillery" value="80"/> + <Damage unitId="anti_air_missile_launcher" value="80"/> + </DamageTable> + </PrimaryWeapon> + <SecondaryWeapon name="Machine-Gun"> + <DamageTable> + <Damage unitId="infantry" value="75"/> + <Damage unitId="mechanized_infantry" value="70"/> + <Damage unitId="recon" value="40"/> + <Damage unitId="apc" value="45"/> + <Damage unitId="anti_air_tank" value="6"/> + <Damage unitId="medium_tank" value="6"/> + <Damage unitId="heavy_tank" value="1"/> + <Damage unitId="neotank" value="1"/> + <Damage unitId="artillery" value="45"/> + <Damage unitId="rocket_artillery" value="55"/> + <Damage unitId="anti_air_missile_launcher" value="30"/> + <Damage unitId="transport_helicopter" value="40"/> + <Damage unitId="battle_helicopter" value="10"/> + </DamageTable> + </SecondaryWeapon> + </Weapons> + </Unit> + + <!-- Heavy Tank Unit --> + <Unit key="heavy_tank"> + <Cost>16000</Cost> + <MovementPoints>5</MovementPoints> + <MovementType>Tread</MovementType> + <VisionPoints>1</VisionPoints> + <Ammo>8</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <PrimaryWeapon name="Cannon"> + <DamageTable> + <Damage unitId="recon" value="105"/> + <Damage unitId="apc" value="105"/> + <Damage unitId="anti_air_tank" value="105"/> + <Damage unitId="medium_tank" value="85"/> + <Damage unitId="heavy_tank" value="55"/> + <Damage unitId="neotank" value="45"/> + <Damage unitId="artillery" value="105"/> + <Damage unitId="rocket_artillery" value="105"/> + <Damage unitId="anti_air_missile_launcher" value="105"/> + </DamageTable> + </PrimaryWeapon> + <SecondaryWeapon name="Machine-Gun"> + <DamageTable> + <Damage unitId="infantry" value="105"/> + <Damage unitId="mechanized_infantry" value="95"/> + <Damage unitId="recon" value="45"/> + <Damage unitId="apc" value="45"/> + <Damage unitId="anti_air_tank" value="10"/> + <Damage unitId="medium_tank" value="8"/> + <Damage unitId="heavy_tank" value="1"/> + <Damage unitId="neotank" value="1"/> + <Damage unitId="artillery" value="45"/> + <Damage unitId="rocket_artillery" value="55"/> + <Damage unitId="anti_air_missile_launcher" value="35"/> + <Damage unitId="transport_helicopter" value="45"/> + <Damage unitId="battle_helicopter" value="12"/> + </DamageTable> + </SecondaryWeapon> + </Weapons> + </Unit> + + <!-- Neotank Unit --> + <Unit key="neotank"> + <Cost>22000</Cost> + <MovementPoints>6</MovementPoints> + <MovementType>Tread</MovementType> + <VisionPoints>1</VisionPoints> + <Ammo>9</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <PrimaryWeapon name="New Cannon"> + <DamageTable> + + <Damage unitId="recon" value="125"/> + <Damage unitId="apc" value="125"/> + <Damage unitId="anti_air_tank" value="115"/> + <Damage unitId="medium_tank" value="105"/> + <Damage unitId="heavy_tank" value="75"/> + <Damage unitId="neotank" value="55"/> + <Damage unitId="artillery" value="115"/> + <Damage unitId="rocket_artillery" value="125"/> + <Damage unitId="anti_air_missile_launcher" value="125"/> + + </DamageTable> + </PrimaryWeapon> + <SecondaryWeapon name="Machine-Gun"> + <DamageTable> + <Damage unitId="infantry" value="125"/> + <Damage unitId="mechanized_infantry" value="115"/> + <Damage unitId="recon" value="65"/> + <Damage unitId="apc" value="45"/> + <Damage unitId="anti_air_tank" value="17"/> + <Damage unitId="medium_tank" value="10"/> + <Damage unitId="heavy_tank" value="1"/> + <Damage unitId="neotank" value="1"/> + <Damage unitId="artillery" value="65"/> + <Damage unitId="rocket_artillery" value="75"/> + <Damage unitId="anti_air_missile_launcher" value="55"/> + <Damage unitId="transport_helicopter" value="55"/> + <Damage unitId="battle_helicopter" value="22"/> + </DamageTable> + </SecondaryWeapon> + </Weapons> + </Unit> + + <!-- Artillery Unit --> + <Unit key="artillery"> + <Cost>6000</Cost> + <MovementPoints>5</MovementPoints> + <MovementType>Tread</MovementType> + <VisionPoints>1</VisionPoints> + <Ammo>9</Ammo> + <minRange>2</minRange> + <maxRange>3</maxRange> + <Weapons> + <PrimaryWeapon name="Cannon"> + <DamageTable> + <Damage unitId="infantry" value="90"/> + <Damage unitId="mechanized_infantry" value="85"/> + <Damage unitId="recon" value="80"/> + <Damage unitId="apc" value="70"/> + <Damage unitId="anti_air_tank" value="75"/> + <Damage unitId="medium_tank" value="70"/> + <Damage unitId="heavy_tank" value="45"/> + <Damage unitId="neotank" value="40"/> + <Damage unitId="artillery" value="50"/> + <Damage unitId="rocket_artillery" value="80"/> + <Damage unitId="anti_air_missile_launcher" value="80"/> + <Damage unitId="lander" value="55"/> + <Damage unitId="black_boat" value="55"/> + <Damage unitId="cruiser" value="50"/> + <Damage unitId="submarine" value="60"/> + <Damage unitId="battleship" value="40"/> + </DamageTable> + </PrimaryWeapon> + </Weapons> + </Unit> + + <!-- Rocket Artillery Unit --> + <Unit key="rocket_artillery"> + <Cost>15000</Cost> + <MovementPoints>5</MovementPoints> + <MovementType>Wheeled</MovementType> + <VisionPoints>1</VisionPoints> + <Ammo>6</Ammo> + <minRange>3</minRange> + <maxRange>5</maxRange> + <Weapons> + <PrimaryWeapon name="Rockets"> + <DamageTable> + <Damage unitId="infantry" value="95"/> + <Damage unitId="mechanized_infantry" value="90"/> + <Damage unitId="recon" value="90"/> + <Damage unitId="apc" value="80"/> + <Damage unitId="anti_air_tank" value="85"/> + <Damage unitId="medium_tank" value="80"/> + <Damage unitId="heavy_tank" value="55"/> + <Damage unitId="neotank" value="50"/> + <Damage unitId="artillery" value="80"/> + <Damage unitId="rocket_artillery" value="85"/> + <Damage unitId="anti_air_missile_launcher" value="90"/> + <Damage unitId="lander" value="60"/> + <Damage unitId="black_boat" value="60"/> + <Damage unitId="cruiser" value="60"/> + <Damage unitId="submarine" value="85"/> + <Damage unitId="battleship" value="55"/> + </DamageTable> + </PrimaryWeapon> + </Weapons> + </Unit> + + <!-- Anti-Air Missile Launcher Unit --> + <Unit key="anti_air_missile_launcher"> + <Cost>12000</Cost> + <MovementPoints>4</MovementPoints> + <MovementType>Tread</MovementType> + <VisionPoints>5</VisionPoints> + <Ammo>6</Ammo> + <minRange>3</minRange> + <maxRange>5</maxRange> + <Weapons> + <PrimaryWeapon name="Missiles"> + <DamageTable> + <Damage unitId="transport_helicopter" value="115"/> + <Damage unitId="battle_helicopter" value="115"/> + <Damage unitId="fighter" value="100"/> + <Damage unitId="bomber" value="100"/> + </DamageTable> + </PrimaryWeapon> + </Weapons> + </Unit> + + <!-- Lander Unit --> + <Unit key="lander"> + <Cost>12000</Cost> + <MovementPoints>7</MovementPoints> + <MovementType>Lander</MovementType> + <VisionPoints>1</VisionPoints> + <Ammo>0</Ammo> + <minRange>0</minRange> + <maxRange>0</maxRange> + <Weapons> + <!-- Keine Waffen, da Lander ein Transportfahrzeug ist --> + </Weapons> + </Unit> + + + + <!-- Cruiser Unit --> + <Unit key="cruiser"> + <Cost>18000</Cost> + <MovementPoints>7</MovementPoints> + <MovementType>Sea</MovementType> + <VisionPoints>3</VisionPoints> + <Ammo>9</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <PrimaryWeapon name="Missiles"> + <DamageTable> + <Damage unitId="submarine" value="90"/> + <Damage unitId="transport_helicopter" value="105"/> + <Damage unitId="battle_helicopter" value="105"/> + <Damage unitId="fighter" value="85"/> + <Damage unitId="bomber" value="100"/> + <Damage unitId="Cruiser" value="25" /> + <Damage unitId="Battleship" value="5" /> + </DamageTable> + </PrimaryWeapon> + <SecondaryWeapon name="Anti-Air Gun"> + <DamageTable> + <Damage unitId="transport_helicopter" value="105"/> + <Damage unitId="battle_helicopter" value="105"/> + </DamageTable> + </SecondaryWeapon> + </Weapons> + </Unit> + + <!-- Submarine Unit --> + <Unit key="submarine"> + <Cost>20000</Cost> + <MovementPoints>6</MovementPoints> + <MovementType>Sea</MovementType> + <VisionPoints>5</VisionPoints> + <Ammo>6</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <PrimaryWeapon name="Torpedoes"> + <DamageTable> + <Damage unitId="lander" value="95"/> + <Damage unitId="black_boat" value="95"/> + <Damage unitId="cruiser" value="25"/> + <Damage unitId="submarine" value="55"/> + <Damage unitId="battleship" value="55"/> + </DamageTable> + </PrimaryWeapon> + </Weapons> + </Unit> + + <!-- Battleship Unit --> + <Unit key="battleship"> + <Cost>28000</Cost> + <MovementPoints>6</MovementPoints> + <MovementType>Sea</MovementType> + <VisionPoints>2</VisionPoints> + <Ammo>9</Ammo> + <minRange>2</minRange> + <maxRange>6</maxRange> + <Weapons> + <PrimaryWeapon name="Cannon"> + <DamageTable> + <Damage unitId="infantry" value="95"/> + <Damage unitId="mechanized_infantry" value="90"/> + <Damage unitId="recon" value="90"/> + <Damage unitId="apc" value="80"/> + <Damage unitId="anti_air_tank" value="85"/> + <Damage unitId="medium_tank" value="80"/> + <Damage unitId="heavy_tank" value="55"/> + <Damage unitId="neotank" value="50"/> + <Damage unitId="artillery" value="80"/> + <Damage unitId="rocket_artillery" value="85"/> + <Damage unitId="anti_air_missile_launcher" value="90"/> + <Damage unitId="lander" value="95"/> + <Damage unitId="black_boat" value="95"/> + <Damage unitId="cruiser" value="95"/> + <Damage unitId="submarine" value="95"/> + <Damage unitId="battleship" value="50"/> + </DamageTable> + </PrimaryWeapon> + </Weapons> + </Unit> + + <!-- Transport Helicopter Unit --> + <Unit key="transport_helicopter"> + <Cost>5000</Cost> + <MovementPoints>6</MovementPoints> + <MovementType>Air</MovementType> + <VisionPoints>2</VisionPoints> + <Ammo>0</Ammo> + <minRange>0</minRange> + <maxRange>0</maxRange> + <Weapons> + <!-- Keine Waffen, dient als Transport --> + </Weapons> + </Unit> + + <!-- Battle Helicopter Unit --> + <Unit key="battle_helicopter"> + <Cost>9000</Cost> + <MovementPoints>6</MovementPoints> + <MovementType>Air</MovementType> + <VisionPoints>3</VisionPoints> + <Ammo>6</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <PrimaryWeapon name="Missiles"> + <DamageTable> + <Damage unitId="medium_tank" value="55"/> + <Damage unitId="heavy_tank" value="25"/> + <Damage unitId="neotank" value="20"/> + <Damage unitId="artillery" value="65"/> + <Damage unitId="rocket_artillery" value="65"/> + <Damage unitId="anti_air_missile_launcher" value="65"/> + <Damage unitId="transport_helicopter" value="95"/> + <Damage unitId="battle_helicopter" value="65"/> + </DamageTable> + </PrimaryWeapon> + <SecondaryWeapon name="Machine-Gun"> + <DamageTable> + <Damage unitId="infantry" value="75"/> + <Damage unitId="mechanized_infantry" value="75"/> + + + </DamageTable> + </SecondaryWeapon> + </Weapons> + </Unit> + + <!-- Fighter Unit --> + <Unit key="fighter"> + <Cost>20000</Cost> + <MovementPoints>9</MovementPoints> + <MovementType>Air</MovementType> + <VisionPoints>2</VisionPoints> + <Ammo>9</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <PrimaryWeapon name="Missiles"> + <DamageTable> + <Damage unitId="transport_helicopter" value="100"/> + <Damage unitId="battle_helicopter" value="100"/> + + <Damage unitId="fighter" value="55"/> + <Damage unitId="bomber" value="100"/> + </DamageTable> + </PrimaryWeapon> + </Weapons> + </Unit> + + <!-- Bomber Unit --> + <Unit key="bomber"> + <Cost>22000</Cost> + <MovementPoints>7</MovementPoints> + <MovementType>Air</MovementType> + <VisionPoints>2</VisionPoints> + <Ammo>9</Ammo> + <minRange>1</minRange> + <maxRange>1</maxRange> + <Weapons> + <PrimaryWeapon name="Bombs"> + <DamageTable> + <Damage unitId="infantry" value="110"/> + <Damage unitId="mechanized_infantry" value="110"/> + <Damage unitId="recon" value="105"/> + <Damage unitId="apc" value="105"/> + <Damage unitId="anti_air_tank" value="95"/> + <Damage unitId="medium_tank" value="105"/> + <Damage unitId="heavy_tank" value="95"/> + <Damage unitId="neotank" value="90"/> + <Damage unitId="artillery" value="105"/> + <Damage unitId="rocket_artillery" value="105"/> + <Damage unitId="anti_air_missile_launcher" value="105"/> + <Damage unitId="lander" value="95"/> + <Damage unitId="black_boat" value="95"/> + <Damage unitId="cruiser" value="85"/> + <Damage unitId="submarine" value="95"/> + <Damage unitId="battleship" value="75"/> + </DamageTable> + </PrimaryWeapon> + </Weapons> + </Unit> +</Units> 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/config.cpp b/src/config.cpp new file mode 100644 index 0000000000000000000000000000000000000000..22791848862887084b7a7136beb4994e76f10977 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,244 @@ +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/xml_parser.hpp> +#include <unordered_map> +#include <string> +#include <iostream> +#include <stdexcept> +#include "unit.hpp" +#include "config.hpp" + +namespace advanced_wars +{ + + Config::Config(const std::string &filename) + { + namespace pt = boost::property_tree; + pt::ptree tree; + pt::read_xml(filename, tree); + + for (const auto &unit : tree.get_child("Units")) + { + if (unit.first != "Unit") + continue; + const auto &unitData = unit.second; + + std::string unit_key = unitData.get<std::string>("<xmlattr>.key"); + try + { + UnitId unitId = map_unit_key_to_id(unit_key); + + unit_costs[unitId] = unitData.get<int>("Cost"); + unit_movement_points[unitId] = unitData.get<int>("MovementPoints"); + unit_ammo[unitId] = unitData.get<int>("Ammo"); + unit_min_range[unitId] = unitData.get<int>("minRange", 0); + unit_max_range[unitId] = unitData.get<int>("maxRange", 0); + + std::string movement_type_str = unitData.get<std::string>("MovementType"); + try + { + unit_movement_type[unitId] = map_movement_type(movement_type_str); + } + catch (const std::out_of_range &e) + { + std::cerr << "Unknown movement type: " << movement_type_str << " for unit key: " << unit_key << std::endl; + continue; + } + + for (const auto &weapon : unitData.get_child("Weapons")) + { + if (weapon.first == "PrimaryWeapon") + { + unit_primary_weapon[unitId] = weapon.second.get<std::string>("<xmlattr>.name"); + + for (const auto &damage : weapon.second.get_child("DamageTable")) + { + if (damage.first != "Damage") + continue; + + std::string target_key = damage.second.get<std::string>("<xmlattr>.unitId"); + UnitId targetId = map_unit_key_to_id(target_key); + primary_weapon_damage[unitId][targetId] = damage.second.get<int>("<xmlattr>.value"); + } + } + else if (weapon.first == "SecondaryWeapon") + { + unit_secondary_weapon[unitId] = weapon.second.get<std::string>("<xmlattr>.name"); + + for (const auto &damage : weapon.second.get_child("DamageTable")) + { + if (damage.first != "Damage") + continue; + + std::string target_key = damage.second.get<std::string>("<xmlattr>.unitId"); + UnitId targetId = map_unit_key_to_id(target_key); + secondary_weapon_damage[unitId][targetId] = damage.second.get<int>("<xmlattr>.value"); + } + } + } + } + catch (const std::out_of_range &e) + { + //std::cerr << "Unknown unit key: " << unit_key << std::endl; + continue; + } + } + } + + UnitId Config::map_unit_key_to_id(const std::string &unit_key) const + { + static const std::unordered_map<std::string, UnitId> unit_map = { + {"infantry", UnitId::INFANTERY}, + {"mechanized_infantry", UnitId::MECHANIZED_INFANTERY}, + {"recon", UnitId::RECON}, + {"apc", UnitId::APC}, + {"anti_air_tank", UnitId::ANTI_AIR_TANK}, + {"medium_tank", UnitId::MEDIUM_TANK}, + {"heavy_tank", UnitId::HEAVY_TANK}, + {"neotank", UnitId::NEO_TANK}, + {"artillery", UnitId::ARTILLERY}, + {"rocket_artillery", UnitId::ROCKET_ARTILLERY}, + {"anti_air_missile_launcher", UnitId::ANTI_AIR_MISSILE_LAUNCHER}, + {"lander", UnitId::LANDER}, + {"cruiser", UnitId::CRUISER}, + {"submarine", UnitId::SUBMARINE}, + {"battleship", UnitId::BATTLESHIP}, + {"transport_helicopter", UnitId::TRANSPORT_HELICOPTER}, + {"battle_helicopter", UnitId::BATTLE_HELICOPTER}, + {"fighter", UnitId::FIGHTER}, + {"bomber", UnitId::BOMBER}}; + + auto it = unit_map.find(unit_key); + if (it != unit_map.end()) + { + return it->second; + } + throw std::out_of_range("Unknown unit key: " + unit_key); + } + + MovementType Config::map_movement_type(const std::string &movementTypeStr) const + { + static const std::unordered_map<std::string, MovementType> movement_map = { + {"Foot", MovementType::FOOT}, + {"Wheeled", MovementType::WHEELED}, + {"Tread", MovementType::TREAD}, + {"Air", MovementType::AIR}, + {"Sea", MovementType::SEA}, + {"Lander", MovementType::LANDER}}; + + auto it = movement_map.find(movementTypeStr); + if (it != movement_map.end()) + { + return it->second; + } + throw std::out_of_range("Unknown movement type: " + movementTypeStr); + } + + +int Config::get_unit_cost(UnitId id) const +{ + auto it = unit_costs.find(id); + if (it != unit_costs.end()) + { + return it->second; + } + throw std::runtime_error("Cost for unit ID not found"); +} + +int Config::get_unit_movement_points(UnitId id) const +{ + auto it = unit_movement_points.find(id); + if (it != unit_movement_points.end()) + { + return it->second; + } + throw std::runtime_error("Movement points for unit ID not found"); +} + +MovementType Config::get_unit_movement_type(UnitId id) const +{ + auto it = unit_movement_type.find(id); + if (it != unit_movement_type.end()) + { + return it->second; + } + throw std::runtime_error("Movement type for unit ID not found"); +} + +int Config::get_unit_ammo(UnitId id) const +{ + auto it = unit_ammo.find(id); + if (it != unit_ammo.end()) + { + return it->second; + } + throw std::runtime_error("Ammo for unit ID not found"); +} + +int Config::get_unit_min_range(UnitId id) const +{ + auto it = unit_min_range.find(id); + if (it != unit_min_range.end()) + { + return it->second; + } + throw std::runtime_error("Min range for unit ID not found"); +} + +int Config::get_unit_max_range(UnitId id) const +{ + auto it = unit_max_range.find(id); + if (it != unit_max_range.end()) + { + return it->second; + } + throw std::runtime_error("Max range for unit ID not found"); +} + +std::string Config::get_unit_primary_weapon(UnitId id) const +{ + auto it = unit_primary_weapon.find(id); + if (it != unit_primary_weapon.end()) + { + return it->second; + } + throw std::runtime_error("Primary weapon for unit ID not found"); +} + +std::string Config::get_unit_secondary_weapon(UnitId id) const +{ + auto it = unit_secondary_weapon.find(id); + if (it != unit_secondary_weapon.end()) + { + return it->second; + } + throw std::runtime_error("Secondary weapon for unit ID not found"); +} + +int Config::get_unit_primary_weapon_damage(UnitId attackerid, UnitId defenderid) const +{ + auto it = primary_weapon_damage.find(attackerid); + if (it != primary_weapon_damage.end()) + { + auto damageIt = it->second.find(defenderid); + if (damageIt != it->second.end()) + { + return damageIt->second; + } + } + throw std::runtime_error("Primary weapon damage not found for given attacker/defender combination"); +} + +int Config::get_unit_secondary_weapon_damage(UnitId attackerid, UnitId defenderid) const +{ + auto it = secondary_weapon_damage.find(attackerid); + if (it != secondary_weapon_damage.end()) + { + auto damageIt = it->second.find(defenderid); + if (damageIt != it->second.end()) + { + return damageIt->second; + } + } + throw std::runtime_error("Secondary weapon damage not found for given attacker/defender combination"); +} +} \ No newline at end of file diff --git a/src/config.hpp b/src/config.hpp new file mode 100644 index 0000000000000000000000000000000000000000..eee38da2fa625a0db175d03fc75be37866da6751 --- /dev/null +++ b/src/config.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include <unordered_map> +#include <string> +#include <stdexcept> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/xml_parser.hpp> +#include "unit.hpp" // Include for UnitId and MovementType + +namespace advanced_wars +{ + /** + * @class Config + * @brief Parses and stores unit configuration data from an XML file. + * + * This class reads unit attributes, movement types, weapon data, and damage tables + * from an XML configuration file and provides access to this data through getter methods. + */ + class Config + { + public: + /** + * @brief Constructs a Config object and loads data from an XML file. + * + * The constructor reads the XML file, extracts unit properties, movement types, + * and weapon damage tables, and stores them in internal data structures. + * + * @param filename Path to the XML configuration file. + * @throws std::runtime_error if the file cannot be read or parsed. + */ + explicit Config(const std::string &filename); + + /** @brief Retrieves the cost of a given unit type. */ + int get_unit_cost(UnitId id) const; + + /** @brief Retrieves the movement points of a given unit type. */ + int get_unit_movement_points(UnitId id) const; + + /** @brief Retrieves the maximum ammunition capacity of a given unit type. */ + int get_unit_ammo(UnitId id) const; + + /** @brief Retrieves the minimum attack range of a given unit type. */ + int get_unit_min_range(UnitId id) const; + + /** @brief Retrieves the maximum attack range of a given unit type. */ + int get_unit_max_range(UnitId id) const; + + /** @brief Retrieves the name of the primary weapon of a given unit type. */ + std::string get_unit_primary_weapon(UnitId id) const; + + /** @brief Retrieves the name of the secondary weapon of a given unit type. */ + std::string get_unit_secondary_weapon(UnitId id) const; + + /** @brief Retrieves the damage value of a unit's primary weapon against a target unit type. */ + int get_unit_primary_weapon_damage(UnitId attackerid, UnitId defenderid) const; + + /** @brief Retrieves the damage value of a unit's secondary weapon against a target unit type. */ + int get_unit_secondary_weapon_damage(UnitId attackerid, UnitId defenderid) const; + + /** @brief Retrieves the movement type of a given unit type. */ + MovementType get_unit_movement_type(UnitId id) const; + + private: + /** @brief Maps unit IDs to their cost values. */ + std::unordered_map<UnitId, int> unit_costs; + + /** @brief Maps unit IDs to their movement points. */ + std::unordered_map<UnitId, int> unit_movement_points; + + /** @brief Maps unit IDs to their maximum ammunition capacity. */ + std::unordered_map<UnitId, int> unit_ammo; + + /** @brief Maps unit IDs to their minimum attack range. */ + std::unordered_map<UnitId, int> unit_min_range; + + /** @brief Maps unit IDs to their maximum attack range. */ + std::unordered_map<UnitId, int> unit_max_range; + + /** @brief Maps unit IDs to their primary weapon names. */ + std::unordered_map<UnitId, std::string> unit_primary_weapon; + + /** @brief Maps unit IDs to their secondary weapon names. */ + std::unordered_map<UnitId, std::string> unit_secondary_weapon; + + /** @brief Stores primary weapon damage values for attacker-defender unit combinations. */ + std::unordered_map<UnitId, std::unordered_map<UnitId, int>> primary_weapon_damage; + + /** @brief Stores secondary weapon damage values for attacker-defender unit combinations. */ + std::unordered_map<UnitId, std::unordered_map<UnitId, int>> secondary_weapon_damage; + + /** @brief Maps unit IDs to their movement types. */ + std::unordered_map<UnitId, MovementType> unit_movement_type; + + /** + * @brief Converts a unit key string from the XML file to its corresponding UnitId. + * + * If the key is unknown, it returns UnitId::UNKNOWN. + * + * @param unit_key The string key representing a unit type. + * @return The corresponding UnitId. + */ + UnitId map_unit_key_to_id(const std::string &unit_key) const; + + /** + * @brief Converts a movement type string from the XML file to its corresponding MovementType. + * + * If the movement type is unknown, it returns MovementType::UNKNOWN. + * + * @param movementTypeStr The string representation of the movement type. + * @return The corresponding MovementType. + */ + MovementType map_movement_type(const std::string &movementTypeStr) const; + }; +} 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 eece66dab07c6d8b08c3206c596f1fbea2156846..f7c58d6efeb46bb4350a0b7c02429d166e524f36 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -1,73 +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) +{ - this->sdl_renderer = - SDL_CreateRenderer(this->window.sdl_window(), -1, - SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + this->sdl_renderer = SDL_CreateRenderer( + this->window.sdl_window(), -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); - if (sdl_renderer == nullptr) { - throw std::runtime_error("SDL could not generate renderer: " + - std::string(SDL_GetError())); - } + if (sdl_renderer == nullptr) + { + throw std::runtime_error("SDL could not generate renderer: " + std::string(SDL_GetError())); + } } -void Engine::set_scene(Scene &scene) { this->scene = &scene; } +std::deque<SDL_Event>& Engine::events() +{ + return this->_events; +} -void Engine::set_spritesheet(Spritesheet &spritesheet) { - this->spritesheet = &spritesheet; +void Engine::push_scene(std::shared_ptr<Scene> scene) +{ + this->scenes.push_back(scene); } -Spritesheet *Engine::get_spritesheet() { return spritesheet.value(); } +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::pump() { - SDL_Event e; - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) { - this->quit = true; - } else { - this->events.push_back(e); +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(); + + return tmp; } -bool Engine::exited() { return this->quit; } +void Engine::set_spritesheet(Spritesheet& spritesheet) +{ + this->spritesheet = &spritesheet; +} -int Engine::get_stage() { return this->stage; } +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::render() { - if (SDL_RenderClear(this->sdl_renderer) != 0) { - throw std::runtime_error("Could not clear renderer: " + - std::string(SDL_GetError())); - } +void Engine::exit() +{ + this->quit = true; +} - if (!scene.has_value()) { - return; - } +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())); + } + + 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); +} + +int Engine::get_stage() +{ + return SDL_GetTicks() / 300; +} - SDL_RenderPresent(this->sdl_renderer); +Spritesheet* Engine::get_spritesheet() +{ + return spritesheet.value(); } -SDL_Renderer *Engine::renderer() { return this->sdl_renderer; } +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 32da84fc09d3e135ba669b54479729dd6cac55e7..59f239f04dcfb7bc253f731b2fdb1818ead98e63 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -1,5 +1,4 @@ #include "level.hpp" -#include "SDL_error.h" #include "building.hpp" #include "effect.hpp" #include "engine.hpp" @@ -7,22 +6,45 @@ #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/xml_parser.hpp> #include "spritesheet.hpp" +#include "ui/contextmenu.hpp" +#include "ui/pausemenu.hpp" #include "unit.hpp" #include <SDL.h> +#include <algorithm> #include <iostream> #include <string> -namespace advanced_wars { +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), context_menu(ContextMenu()), + context_menu_active(false), id(0) +{ -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) { + context_menu.setOptions({"Move", "Info", "Wait"}); - if ((size_t)(width * height) != tiles.size()) { - throw std::runtime_error("level tile mismatch"); - } + 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"); + } }; Level Level::loadLevel(std::string path) @@ -71,36 +93,298 @@ Level::Level(std::string name, int width, int height, std::vector<Tile> tiles, void Level::render(Engine &engine, std::vector<SDL_Event> &events) { const int RENDERING_SCALE = 3; - // Iterate over all events - while (!events.empty()) { - events.erase(events.begin()); - } +bool Level::click_check_left(int tileX, int tileY) +{ - // Tiles - for (Tile &tile : tiles) { - tile.render(engine, RENDERING_SCALE); - } + if (selectUnit(tileX, tileY)) + { + return true; + } - // Buildings - for (Building &building : buildings) { - building.render(engine, RENDERING_SCALE); - } + if (selectBuilding(tileX, tileY)) + { + return true; + } - // Units - for (Unit &unit : units) { - unit.render(engine, RENDERING_SCALE); - } + return false; +} - // Effects - for (Effect &effect : effects) { - effect.render(engine, RENDERING_SCALE); - } +bool Level::click_check_right(int tileX, int tileY) +{ - // Set background color for renderer - if (SDL_SetRenderDrawColor(engine.renderer(), 255, 0, 0, 0)) { - std::cout << "Could not set render draw color: " << SDL_GetError() - << std::endl; - } + if (target_unit(tileX, tileY)) + { + return true; + } + + return false; +} + +bool Level::selectUnit(int tileX, int tileY) +{ + + // std::cout << "tileX:" << tileX << "tileX:" << tileY << std::endl; + for (auto& [id, unit] : units) + { + + if (unit.x == tileX && unit.y == tileY) + { + // std::cout << "unitX:" << unit.x << "unitY:" << unit.y << std::endl; + + selectedUnit = id; + return true; + } + } + + return false; +} + +bool Level::target_unit(int tileX, int tileY) +{ + + // std::cout << "tileX:" << tileX << "tileX:" << tileY << std::endl; + for (auto& [id, unit] : units) + { + + if (unit.x == tileX && unit.y == tileY) + { + // std::cout << "unitX:" << unit.x << "unitY:" << unit.y << std::endl; + + targetedUnit = id; + return true; + } + } + + return false; +} + +bool Level::selectBuilding(int tileX, int tileY) +{ + + for (auto& [id, building] : buildings) + { + + 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) +{ + + 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 (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 if (event.button.button == SDL_BUTTON_RIGHT) + { + + if (selectedUnit > -1) + { + + int tileX = event.button.x / (16 * RENDERING_SCALE); + int tileY = event.button.y / (16 * RENDERING_SCALE); + + if (click_check_right(tileX, tileY)) + { + + units.at(selectedUnit).attack(&(units.at(targetedUnit))); + + if (units.at(selectedUnit).health <= 0) + { + remove_unit(selectedUnit); + } + } + else + { + + units.at(selectedUnit).update_position(tileX, tileY); + } + } + else + { + + std::cout << "No unit selected! " << std::endl; + } + } + } +} + +void Level::render(Engine* engine) +{ + + // Iterate over all events + while (!engine->events().empty()) + { + handleEvent(*engine, engine->events().at(0)); + handleEvent2(engine, engine->events().at(0)); + + engine->events().pop_front(); + } + + // Tiles + for (Tile& tile : tiles) + { + tile.render(engine, RENDERING_SCALE); + } + + // Buildings + for (auto& [id, building] : buildings) + { + building.render(engine, RENDERING_SCALE); + } + + // Units + for (auto& [id, unit] : units) + { + unit.render(engine, RENDERING_SCALE); + } + + // 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) + { + this->remove_effect(id); + } + + if (context_menu_active) + { + context_menu.render(engine); + } +} + +void Level::handleEvent2(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) + { + 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 diff --git a/src/level.hpp b/src/level.hpp index 55cbe85989de4350cdc90d52506deed2c9c0f1db..efdc855ac93d8ff570b1a4d382ef2395afe48388 100644 --- a/src/level.hpp +++ b/src/level.hpp @@ -5,34 +5,71 @@ #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>); - - static Level loadLevel(std::string path); - - void render(Engine &engine, std::vector<SDL_Event> &events); - -private: - std::string name; - int width; - int height; - std::vector<Tile> tiles; - std::vector<Building> buildings; - std::vector<Unit> units; - std::vector<Effect> effects; +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>); + + static Level loadLevel(std::string path); + + void render(Engine* engine); + + void handleEvent(Engine* engine, SDL_Event& event); + + void handleEvent2(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 c4127dff8b4255fbeefa9c731acca488805f3d87..5f50eae8ecbfa4c2f27fe2dd12ee4cc87fad49e7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,50 +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 <vector> #include <SDL2/SDL.h> #include <SDL_image.h> +#include <memory> #include <stdexcept> - +#include <vector> using namespace advanced_wars; -int main() { +int main() +{ + + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { + throw std::runtime_error("SDL could not initialize: " + std::string(SDL_GetError())); + } - if (SDL_Init(SDL_INIT_VIDEO) < 0) { - throw std::runtime_error("SDL could not initialize: " + - std::string(SDL_GetError())); - } + 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())); + } - 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())); - } + Window window("Advanced Wars", 960, 960); - Window window("Advanced Wars", 960, 960); + Engine engine(window); - Engine engine(window); + Spritesheet spritesheet("./spritesheet.h5", engine); - Level level = Level::loadLevel("../res/level.h5"); + 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 cb0b87bf3374f7708caa0308f789652a02163670..4ece9fb5a13f8ca81b247be886cbc35f50d7e26a 100644 --- a/src/unit.cpp +++ b/src/unit.cpp @@ -1,65 +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) { - - }; - -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); + + 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; + } + + 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; + } + 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; + } + } + } +} + +void Unit::update_position(int posX, int posY) +{ + calc_state(posX, 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; + + 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; + } + } + 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::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; } } // namespace advanced_wars \ No newline at end of file diff --git a/src/unit.hpp b/src/unit.hpp index 20877bafc57eea6f23defacf270988f311d9ceb9..28ccc62a02f594974e5603327f123532129f7f5a 100644 --- a/src/unit.hpp +++ b/src/unit.hpp @@ -1,69 +1,139 @@ #pragma once #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, + WHEELED = 1, + TREAD = 2, + AIR = 3, + SEA = 4, + LANDER = 5, }; -class Unit { -public: - Unit(int x, int y, UnitFaction faction, UnitId id, UnitState state); +using MatchupTable = std::unordered_map<UnitId, std::unordered_map<UnitId, int>>; - void render(Engine &engine, int scale); +class Unit +{ + public: + int x; + int y; + int health; // health equals max_health at construction -private: - int x; - int y; - UnitFaction faction; - UnitId id; - UnitState state; + 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 new file mode 100644 index 0000000000000000000000000000000000000000..e324130e26681f5cef3d0334e60abb90086c4aa7 --- /dev/null +++ b/src/weapon.cpp @@ -0,0 +1,29 @@ +#include "weapon.hpp" + +namespace advanced_wars +{ +Weapon::Weapon() : name(""), damage() {} + +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 Abrufen eines Schadenswertes +int Weapon::getDamageValue(UnitId unitId) const +{ + auto it = damage.find(unitId); + if (it != damage.end()) + { + return it->second; + } + return 0; // oder ein Fehlerwert +} + +} // namespace advanced_wars \ No newline at end of file diff --git a/src/weapon.hpp b/src/weapon.hpp new file mode 100644 index 0000000000000000000000000000000000000000..028eef1a67f30ea825b2a5c2b633d48af2c20216 --- /dev/null +++ b/src/weapon.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include <string> +#include <unordered_map> + +namespace advanced_wars +{ + +enum class UnitId; + +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 abzurufen + int getDamageValue(UnitId unitId) const; + + // Name der Waffe + std::string name; + + // 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 746923433314815bee18dce9f736662d0c8add8c..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