diff --git a/CMakeLists.txt b/CMakeLists.txt index a4d8044499cc44dc13a6d31a7b8e896efd83b633..baeb663ddaa480176590e70954fa8905899727ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ FetchContent_MakeAvailable(highfive) FetchContent_Declare( box2d GIT_REPOSITORY https://github.com/erincatto/box2d.git - GIT_TAG v3.0.0 + GIT_TAG v2.4.2 ) set(BOX2D_BUILD_TESTBED OFF CACHE BOOL "" FORCE) diff --git a/res/map_split_island.hdf5 b/res/map_split_island.h5 similarity index 100% rename from res/map_split_island.hdf5 rename to res/map_split_island.h5 diff --git a/src/editor/main.cpp b/src/editor/main.cpp index 2688bf6695125425ed12c87436be38998b8ebeab..9831c25bcc06721b76d3faee2bd240a8f22378f2 100644 --- a/src/editor/main.cpp +++ b/src/editor/main.cpp @@ -1,23 +1,23 @@ /** -* main.cpp -* -* @date 27.01.2025 -* @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) -*/ + * main.cpp + * + * @date 27.01.2025 + * @author Jonathan Dueck (jonathan.dueck@informatik.hs-fulda.de) + */ #include <QApplication> -#include "SpriteProvider.hpp" -#include "MainWindow.hpp" #include "LevelScene.hpp" +#include "MainWindow.hpp" +#include "SpriteProvider.hpp" using namespace editor; /** * The main function starts the program. * It checks if any command line arguments were provided. - * If not it terminates. - * Otherwise if a path to a hdf5 level file was provided it + * If not it terminates. + * Otherwise if a path to a hdf5 level file was provided it * starts the level editor and loads the level from the file for editing. * If a width and height was provided, it create a blank level with the given dimensions. */ @@ -28,12 +28,12 @@ int main(int argc, char* argv[]) { // no arguments provided std::cerr << "Bitte uebergben Sie den Pfad zu dem Level, das sie bearbeiten wollen oder " "die Breite und Hoehe des Levels, das sie neu erstellen wollen." - << std::endl; + << "\n"; return 1; } if (argc > 3) { // more than 2 arguments provided - std::cerr << "Zuviele Kommandozeilenargumente." << std::endl; + std::cerr << "Zuviele Kommandozeilenargumente." << "\n"; return 1; } @@ -44,7 +44,6 @@ int main(int argc, char* argv[]) // to get the QGraphicsPixmap for a specific tile. SpriteProvider::initialize("../res/spritesheet.h5"); - LevelScene* level; if (argc == 2) { // 1 argument provided => create Level from file @@ -59,4 +58,4 @@ int main(int argc, char* argv[]) window.resize(1300, 800); window.show(); return app.exec(); -} \ No newline at end of file +} diff --git a/src/game/combat/CombatEngine.cpp b/src/game/combat/CombatEngine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8f5323b6319562d4a61e5af762ee950f3eaa7ed2 --- /dev/null +++ b/src/game/combat/CombatEngine.cpp @@ -0,0 +1,225 @@ +#include "CombatEngine.hpp" +#include "../level/Level.hpp" + +#include <iostream> +#include <memory> +#include <unordered_map> + +namespace advanced_wars +{ + +CombatEngine::CombatEngine() {} + +void CombatEngine::attack(Unit& attacker, Unit& target) +{ + int attackerDamageValue = calculateDamage(attacker, target); + + if (attackerDamageValue > 0) + { + takeDamage(attacker, target, attackerDamageValue); + std::cout << "target health after attack: " << target.getHealth() << "\n"; + + // Check if the target is still alive for counter-attack + if (target.getHealth() > 0) + { + // Check if the target is within attack range + int distanceX = std::abs(target.getXPosition() - attacker.getXPosition()); + int distanceY = std::abs(target.getYPosition() - attacker.getYPosition()); + int distance = distanceX + distanceY; + + if (distance >= target.getMinRange() && distance <= target.getMaxRange()) + { + // Now, they are reversed for the counter-attack + int defenderDamageValue = calculateDamage(target, attacker); + if (defenderDamageValue > 0) + { + takeDamage(target, attacker, defenderDamageValue); + std::cout << "Ally health after retaliation: " << attacker.getHealth() << "\n"; + } + } + else + { + std::cout << "target out of range for counter-attack." << "\n"; + } + } + } + else + { + std::cout << "No damage value found for attack from unit " + << static_cast<int>(attacker.getUnitTypeId()) << " against unit " + << static_cast<int>(target.getUnitTypeId()) << "\n"; + } +} + +int CombatEngine::calculateDamage(Unit& attacker, Unit& target) +{ + // Reference to Weapon objects + Weapon& primaryWeapon = attacker.getPrimaryWeapon(); + Weapon& secondaryWeapon = attacker.getSecondaryWeapon(); + + // Find the corresponding damage values + auto primaryDamageIt = primaryWeapon.getDamage().find(target.getUnitTypeId()); + auto secondaryDamageIt = secondaryWeapon.getDamage().find(target.getUnitTypeId()); + + int damageValue = 0; + + // Calculate damage using secondary weapon if available + if (secondaryDamageIt != secondaryWeapon.getDamage().end()) + { + damageValue = secondaryDamageIt->second; + } + + // Calculate damage using primary weapon if higher and ammo is available + if (primaryDamageIt != primaryWeapon.getDamage().end()) + { + if (attacker.getAmmo() > 0 && primaryDamageIt->second > damageValue) + { + damageValue = primaryDamageIt->second; + // Munition wird erst nach Bestätigung des Angriffs reduziert + } + } + + return damageValue; +} + +void CombatEngine::takeDamage(Unit& attacker, Unit& target, int damage) +{ + int health = attacker.getHealth(); + int maxHealth = attacker.getMaxHealth(); + // int effectiveDamage = damage * (static_cast<float>(health) / maxHealth); + int effectiveDamage = (damage * health) / maxHealth; + target.setHealth(std::clamp(target.getHealth() - effectiveDamage, 0, target.getMaxHealth())); +} + +std::vector<Unit*> +CombatEngine::getUnitsInRangeWithDamagePotential(Unit& attacker, std::vector<Unit*>& allUnits) +{ + std::vector<Unit*> unitsInRangeWithDamage; + + for (const auto& unitPtr : allUnits) + { + Unit* unit = unitPtr; // Zugriff auf das rohe Unit-Pointer-Objekt + if (unit->getFaction() == attacker.getFaction()) + { + continue; // Sich selbst nicht angreifen + } + + int distanceX = std::abs(unit->getXPosition() - attacker.getXPosition()); + int distanceY = std::abs(unit->getYPosition() - attacker.getYPosition()); + int distance = distanceX + distanceY; + + if (distance >= attacker.getMinRange() && distance <= attacker.getMaxRange()) + { + auto primaryDamageIt = + attacker.getPrimaryWeapon().getDamage().find(unit->getUnitTypeId()); + auto secondaryDamageIt = + attacker.getSecondaryWeapon().getDamage().find(unit->getUnitTypeId()); + + bool canDealDamage = false; + + if (primaryDamageIt != attacker.getPrimaryWeapon().getDamage().end() && + attacker.getAmmo() > 0) + { + canDealDamage = true; + } + if (secondaryDamageIt != attacker.getSecondaryWeapon().getDamage().end()) + { + canDealDamage = true; + } + + if (canDealDamage) + { + unitsInRangeWithDamage.push_back(unit); + } + } + } + + return unitsInRangeWithDamage; +} + +void CombatEngine::handleAttackingEvents(Engine& engine, SDL_Event& event, Level& m_level) +{ + if (m_level.getAttackableUnitIds().empty()) + { + std::cout << "No units are within attack range." << "\n"; + m_level.setState(LevelState::MENUACTIVE_STATE); + return; // Early exit if no units to attack + } + switch (event.type) + { + case SDL_KEYDOWN: + m_level.handlePositionMarker(engine, event); + if (event.key.keysym.sym == SDLK_ESCAPE) + { + m_level.setState(LevelState::MENUACTIVE_STATE); + } + if (event.key.keysym.sym == SDLK_RETURN) + { + handleAttack(m_level.getTilemarker().getPosition(), m_level); + } + break; + default: + break; + } +} + +void CombatEngine::handleAttack(std::pair<int, int> tilePos, Level& m_level) +{ + std::unordered_map<int, std::unique_ptr<Unit>>& units = m_level.getUnits(); + int selectedUnit = m_level.getSelectedUnit(); + int targetedUnit = m_level.selectUnit(tilePos.first, tilePos.second); + if (targetedUnit >= 0) + { + if (units.at(m_level.getSelectedUnit())->getFaction() == + units.at(targetedUnit)->getFaction()) + { + std::cout << "You cannot attack your allies!" << "\n"; + return; + } + + auto itAttacker = units.find(selectedUnit); + auto itDefender = units.find(targetedUnit); + + if (itAttacker == units.end() || itDefender == units.end()) + { + std::cout << "Unit not found!" << "\n"; + return; + } + + std::unique_ptr<Unit>& attacking = itAttacker->second; + std::unique_ptr<Unit>& defending = itDefender->second; + std::unordered_set<int>& attackableUnitIds = m_level.getAttackableUnitIds(); + if (attackableUnitIds.find(targetedUnit) != attackableUnitIds.end()) + { + attack(*attacking, *defending); + // m_level.spawnBullet(*attacking, *defending); + // attacking->attack(defending); + if (attacking->getHealth() <= 0) + { + m_level.removeUnit(selectedUnit); + } + else + { + attacking->setState(UnitState::UNAVAILABLE); + } + if (defending->getHealth() <= 0) + { + m_level.removeUnit(targetedUnit); + } + m_level.setSelectedUnit(-1); + m_level.setShowAttackableTiles(false); + m_level.setShowReachableTiles(false); + m_level.setState(LevelState::SELECTING_STATE); + } + else + { + std::cout << "No target in range clicked!" << "\n"; + } + } + else + { + std::cout << "No valid target clicked" << "\n"; + } +} + +} // namespace advanced_wars diff --git a/src/game/combat/CombatEngine.hpp b/src/game/combat/CombatEngine.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4b26f1a0c6ccc4e1cbc9b93164f5ef57e1599774 --- /dev/null +++ b/src/game/combat/CombatEngine.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "../core/Engine.hpp" +#include "../entities/Unit.hpp" + +namespace advanced_wars +{ + +class Level; + +class CombatEngine +{ + public: + CombatEngine(); + ~CombatEngine() = default; + + void handleAttackingEvents(Engine& engine, SDL_Event& event, Level& m_level); + void handleAttack(std::pair<int, int> tilePos, Level& m_level); + static void attack(Unit& attacker, Unit& target); + static int calculateDamage(Unit& attacker, Unit& target); + static void takeDamage(Unit& attacker, Unit& target, int damage); + void update(); + void render(Engine& engine, int scale); + std::vector<Unit*> + getUnitsInRangeWithDamagePotential(Unit& attacker, std::vector<Unit*>& allUnits); + + private: +}; +} // namespace advanced_wars diff --git a/src/game/combat/Weapon.cpp b/src/game/combat/Weapon.cpp index 2e5035aa1708cd0b7c0558e86e8eebb587026cfc..ce89ef11dd42f230dc5c55a3239e311a7994c052 100644 --- a/src/game/combat/Weapon.cpp +++ b/src/game/combat/Weapon.cpp @@ -4,19 +4,20 @@ namespace advanced_wars { Weapon::Weapon() : m_name(""), m_damage() {} -Weapon::Weapon(const std::string& weaponName, const std::unordered_map<UnitId, int>& damageValues) +Weapon::Weapon( + const std::string& weaponName, const std::unordered_map<UnitTypeId, int>& damageValues) : m_name(weaponName), m_damage(damageValues) { } // Funktion zum Hinzufügen von Schadenswerten -void Weapon::addDamageValue(UnitId unitId, int value) +void Weapon::addDamageValue(UnitTypeId unitId, int value) { m_damage[unitId] = value; } // Funktion zum Abrufen eines Schadenswertes -int Weapon::getDamageValue(UnitId unitId) const +int Weapon::getDamageValue(UnitTypeId unitId) const { auto it = m_damage.find(unitId); if (it != m_damage.end()) @@ -26,9 +27,9 @@ int Weapon::getDamageValue(UnitId unitId) const return 0; // oder ein Fehlerwert } -std::unordered_map<UnitId, int>& Weapon::getDamage() +std::unordered_map<UnitTypeId, int>& Weapon::getDamage() { return m_damage; } -} // namespace advanced_wars \ No newline at end of file +} // namespace advanced_wars diff --git a/src/game/combat/Weapon.hpp b/src/game/combat/Weapon.hpp index c14cdd21c520345a3db871dec385c994e0e28483..90733feedfad5fa1e1fe056a44aa0580ec0d605b 100644 --- a/src/game/combat/Weapon.hpp +++ b/src/game/combat/Weapon.hpp @@ -6,29 +6,30 @@ namespace advanced_wars { -enum class UnitId; +enum class UnitTypeId; class Weapon { public: // Konstruktoren Weapon(); - Weapon(const std::string& weaponName, const std::unordered_map<UnitId, int>& damageValues); + Weapon( + const std::string& weaponName, const std::unordered_map<UnitTypeId, int>& damageValues); // Methode, um einen Schadenswert hinzuzufügen - void addDamageValue(UnitId unitId, int value); + void addDamageValue(UnitTypeId unitId, int value); // Methode, um einen Schadenswert abzurufen - int getDamageValue(UnitId unitId) const; + int getDamageValue(UnitTypeId unitId) const; - std::unordered_map<UnitId, int>& getDamage(); + std::unordered_map<UnitTypeId, int>& getDamage(); private: // Name der Waffe std::string m_name; // Schadenstabelle - std::unordered_map<UnitId, int> m_damage; + std::unordered_map<UnitTypeId, int> m_damage; }; } // namespace advanced_wars diff --git a/src/game/core/Config.cpp b/src/game/core/Config.cpp index 5d3b240eabde87ada58747b831d7ddb9ab499bba..8c5f23c1050ada6d24a90682cf52805e967f8c5e 100644 --- a/src/game/core/Config.cpp +++ b/src/game/core/Config.cpp @@ -27,7 +27,7 @@ Config::Config(std::string filename) std::string unit_key = unitData.get<std::string>("<xmlattr>.key"); try { - UnitId unitId = mapUnitKeyToID(unit_key); + UnitTypeId unitId = mapUnitKeyToID(unit_key); m_unitCosts[unitId] = unitData.get<int>("Cost"); m_unitMovementPoints[unitId] = unitData.get<int>("MovementPoints"); @@ -43,7 +43,7 @@ Config::Config(std::string filename) catch (const std::out_of_range& e) { std::cerr << "Unknown movement type: " << movement_type_str - << " for unit key: " << unit_key << std::endl; + << " for unit key: " << unit_key << "\n"; continue; } @@ -59,7 +59,7 @@ Config::Config(std::string filename) continue; std::string target_key = damage.second.get<std::string>("<xmlattr>.unitId"); - UnitId targetId = mapUnitKeyToID(target_key); + UnitTypeId targetId = mapUnitKeyToID(target_key); m_primaryWeaponDamage[unitId][targetId] = damage.second.get<int>("<xmlattr>.value"); } @@ -75,7 +75,7 @@ Config::Config(std::string filename) continue; std::string target_key = damage.second.get<std::string>("<xmlattr>.unitId"); - UnitId targetId = mapUnitKeyToID(target_key); + UnitTypeId targetId = mapUnitKeyToID(target_key); m_secondaryWeaponDamage[unitId][targetId] = damage.second.get<int>("<xmlattr>.value"); } @@ -84,34 +84,34 @@ Config::Config(std::string filename) } catch (const std::out_of_range& e) { - // std::cerr << "Unknown unit key: " << unit_key << std::endl; + // std::cerr << "Unknown unit key: " << unit_key << "\n"; continue; } } } -UnitId Config::mapUnitKeyToID(const std::string& unit_key) const +UnitTypeId Config::mapUnitKeyToID(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} + static const std::unordered_map<std::string, UnitTypeId> unit_map = { + { "infantry", UnitTypeId::INFANTERY}, + { "mechanized_infantry", UnitTypeId::MECHANIZED_INFANTERY}, + { "recon", UnitTypeId::RECON}, + { "apc", UnitTypeId::APC}, + { "anti_air_tank", UnitTypeId::ANTI_AIR_TANK}, + { "medium_tank", UnitTypeId::MEDIUM_TANK}, + { "heavy_tank", UnitTypeId::HEAVY_TANK}, + { "neotank", UnitTypeId::NEO_TANK}, + { "artillery", UnitTypeId::ARTILLERY}, + { "rocket_artillery", UnitTypeId::ROCKET_ARTILLERY}, + {"anti_air_missile_launcher", UnitTypeId::ANTI_AIR_MISSILE_LAUNCHER}, + { "lander", UnitTypeId::LANDER}, + { "cruiser", UnitTypeId::CRUISER}, + { "submarine", UnitTypeId::SUBMARINE}, + { "battleship", UnitTypeId::BATTLESHIP}, + { "transport_helicopter", UnitTypeId::TRANSPORT_HELICOPTER}, + { "battle_helicopter", UnitTypeId::BATTLE_HELICOPTER}, + { "fighter", UnitTypeId::FIGHTER}, + { "bomber", UnitTypeId::BOMBER} }; auto it = unit_map.find(unit_key); @@ -141,7 +141,7 @@ MovementType Config::mapMovementType(const std::string& movementTypeStr) const throw std::out_of_range("Unknown movement type: " + movementTypeStr); } -int Config::getUnitCost(UnitId id) const +int Config::getUnitCost(UnitTypeId id) const { auto it = m_unitCosts.find(id); if (it != m_unitCosts.end()) @@ -151,7 +151,7 @@ int Config::getUnitCost(UnitId id) const throw std::runtime_error("Cost for unit ID not found"); } -int Config::getUnitMovementPoints(UnitId id) const +int Config::getUnitMovementPoints(UnitTypeId id) const { auto it = m_unitMovementPoints.find(id); if (it != m_unitMovementPoints.end()) @@ -161,7 +161,7 @@ int Config::getUnitMovementPoints(UnitId id) const throw std::runtime_error("Movement points for unit ID not found"); } -MovementType Config::getUnitMovementType(UnitId id) const +MovementType Config::getUnitMovementType(UnitTypeId id) const { auto it = m_unitMovementType.find(id); if (it != m_unitMovementType.end()) @@ -171,7 +171,7 @@ MovementType Config::getUnitMovementType(UnitId id) const throw std::runtime_error("Movement type for unit ID not found"); } -int Config::getUnitAmmo(UnitId id) const +int Config::getUnitAmmo(UnitTypeId id) const { auto it = m_unitAmmo.find(id); if (it != m_unitAmmo.end()) @@ -181,7 +181,7 @@ int Config::getUnitAmmo(UnitId id) const throw std::runtime_error("Ammo for unit ID not found"); } -int Config::getUnitMinRange(UnitId id) const +int Config::getUnitMinRange(UnitTypeId id) const { auto it = m_unitMinRange.find(id); if (it != m_unitMinRange.end()) @@ -191,7 +191,7 @@ int Config::getUnitMinRange(UnitId id) const throw std::runtime_error("Min range for unit ID not found"); } -int Config::getUnitMaxRange(UnitId id) const +int Config::getUnitMaxRange(UnitTypeId id) const { auto it = m_unitMaxRange.find(id); if (it != m_unitMaxRange.end()) @@ -201,7 +201,7 @@ int Config::getUnitMaxRange(UnitId id) const throw std::runtime_error("Max range for unit ID not found"); } -std::string Config::getUnitPrimaryWeapon(UnitId id) const +std::string Config::getUnitPrimaryWeapon(UnitTypeId id) const { auto it = m_unitPrimaryWeapon.find(id); if (it != m_unitPrimaryWeapon.end()) @@ -211,7 +211,7 @@ std::string Config::getUnitPrimaryWeapon(UnitId id) const return ""; } -std::string Config::getUnitSecondaryWeapon(UnitId id) const +std::string Config::getUnitSecondaryWeapon(UnitTypeId id) const { auto it = m_unitSecondaryWeapon.find(id); if (it != m_unitSecondaryWeapon.end()) @@ -221,7 +221,8 @@ std::string Config::getUnitSecondaryWeapon(UnitId id) const return ""; } -std::optional<int> Config::getUnitPrimaryWeaponDamage(UnitId attackerId, UnitId targetId) const +std::optional<int> +Config::getUnitPrimaryWeaponDamage(UnitTypeId attackerId, UnitTypeId targetId) const { auto attackerMapIt = m_primaryWeaponDamage.find(attackerId); if (attackerMapIt != m_primaryWeaponDamage.end()) @@ -236,7 +237,8 @@ std::optional<int> Config::getUnitPrimaryWeaponDamage(UnitId attackerId, UnitId return std::nullopt; } -std::optional<int> Config::getUnitSecondaryWeaponDamage(UnitId attackerId, UnitId targetId) const +std::optional<int> +Config::getUnitSecondaryWeaponDamage(UnitTypeId attackerId, UnitTypeId targetId) const { auto attackerMapIt = m_secondaryWeaponDamage.find(attackerId); if (attackerMapIt != m_secondaryWeaponDamage.end()) diff --git a/src/game/core/Config.hpp b/src/game/core/Config.hpp index 136b068dde28115278965126880888aec3afee71..f9ba4897f75a26c8d1ab983bd56fb19412ee1b8f 100644 --- a/src/game/core/Config.hpp +++ b/src/game/core/Config.hpp @@ -9,7 +9,7 @@ namespace advanced_wars { /* ENUMS FOR GLOBAL USE*/ -enum class BuildingFaction +enum class Faction { URED = 0, UBLUE = 1, @@ -27,16 +27,7 @@ enum class BuildingId PORT = 4, }; -enum class UnitFaction -{ - URED = 0, - UBLUE = 1, - UGREEN = 2, - UYELLOW = 3, - UPURPLE = 4, -}; - -enum class UnitId +enum class UnitTypeId { INFANTERY = 0, MECHANIZED_INFANTERY = 1, @@ -140,77 +131,79 @@ class Config Config(std::string filename); /** @brief Retrieves the cost of a given unit type. */ - int getUnitCost(UnitId id) const; + int getUnitCost(UnitTypeId id) const; /** @brief Retrieves the movement points of a given unit type. */ - int getUnitMovementPoints(UnitId id) const; + int getUnitMovementPoints(UnitTypeId id) const; /** @brief Retrieves the maximum ammunition capacity of a given unit type. */ - int getUnitAmmo(UnitId id) const; + int getUnitAmmo(UnitTypeId id) const; /** @brief Retrieves the minimum attack range of a given unit type. */ - int getUnitMinRange(UnitId id) const; + int getUnitMinRange(UnitTypeId id) const; /** @brief Retrieves the maximum attack range of a given unit type. */ - int getUnitMaxRange(UnitId id) const; + int getUnitMaxRange(UnitTypeId id) const; /** @brief Retrieves the name of the primary weapon of a given unit type. */ - std::string getUnitPrimaryWeapon(UnitId id) const; + std::string getUnitPrimaryWeapon(UnitTypeId id) const; /** @brief Retrieves the name of the secondary weapon of a given unit type. */ - std::string getUnitSecondaryWeapon(UnitId id) const; + std::string getUnitSecondaryWeapon(UnitTypeId id) const; /** @brief Retrieves the damage value of a unit's primary weapon against a target unit type. */ - std::optional<int> getUnitPrimaryWeaponDamage(UnitId attackerid, UnitId defenderid) const; + std::optional<int> + getUnitPrimaryWeaponDamage(UnitTypeId attackerid, UnitTypeId defenderid) const; /** @brief Retrieves the damage value of a unit's secondary weapon against a target unit * type. */ - std::optional<int> getUnitSecondaryWeaponDamage(UnitId attackerid, UnitId defenderid) const; + std::optional<int> + getUnitSecondaryWeaponDamage(UnitTypeId attackerid, UnitTypeId defenderid) const; /** @brief Retrieves the movement type of a given unit type. */ - MovementType getUnitMovementType(UnitId id) const; + MovementType getUnitMovementType(UnitTypeId id) const; private: /** @brief Maps unit IDs to their cost values. */ - std::unordered_map<UnitId, int> m_unitCosts; + std::unordered_map<UnitTypeId, int> m_unitCosts; /** @brief Maps unit IDs to their movement points. */ - std::unordered_map<UnitId, int> m_unitMovementPoints; + std::unordered_map<UnitTypeId, int> m_unitMovementPoints; /** @brief Maps unit IDs to their maximum ammunition capacity. */ - std::unordered_map<UnitId, int> m_unitAmmo; + std::unordered_map<UnitTypeId, int> m_unitAmmo; /** @brief Maps unit IDs to their minimum attack range. */ - std::unordered_map<UnitId, int> m_unitMinRange; + std::unordered_map<UnitTypeId, int> m_unitMinRange; /** @brief Maps unit IDs to their maximum attack range. */ - std::unordered_map<UnitId, int> m_unitMaxRange; + std::unordered_map<UnitTypeId, int> m_unitMaxRange; /** @brief Maps unit IDs to their primary weapon names. */ - std::unordered_map<UnitId, std::string> m_unitPrimaryWeapon; + std::unordered_map<UnitTypeId, std::string> m_unitPrimaryWeapon; /** @brief Maps unit IDs to their secondary weapon names. */ - std::unordered_map<UnitId, std::string> m_unitSecondaryWeapon; + std::unordered_map<UnitTypeId, std::string> m_unitSecondaryWeapon; /** @brief Stores primary weapon damage values for attacker-defender unit combinations. */ - std::unordered_map<UnitId, std::unordered_map<UnitId, int>> m_primaryWeaponDamage; + std::unordered_map<UnitTypeId, std::unordered_map<UnitTypeId, int>> m_primaryWeaponDamage; /** @brief Stores secondary weapon damage values for attacker-defender unit combinations. */ - std::unordered_map<UnitId, std::unordered_map<UnitId, int>> m_secondaryWeaponDamage; + std::unordered_map<UnitTypeId, std::unordered_map<UnitTypeId, int>> m_secondaryWeaponDamage; /** @brief Maps unit IDs to their movement types. */ - std::unordered_map<UnitId, MovementType> m_unitMovementType; + std::unordered_map<UnitTypeId, MovementType> m_unitMovementType; /** - * @brief Converts a unit key string from the XML file to its corresponding UnitId. + * @brief Converts a unit key string from the XML file to its corresponding UnitTypeId. * - * If the key is unknown, it returns UnitId::UNKNOWN. + * If the key is unknown, it returns UnitTypeId::UNKNOWN. * * @param unit_key The string key representing a unit type. - * @return The corresponding UnitId. + * @return The corresponding UnitTypeId. */ - UnitId mapUnitKeyToID(const std::string& unit_key) const; + UnitTypeId mapUnitKeyToID(const std::string& unit_key) const; /** * @brief Converts a movement type string from the XML file to its corresponding diff --git a/src/game/core/Engine.cpp b/src/game/core/Engine.cpp index 6a5f74abcfa3a7b4fdbf3a59f4d6bc1fa224b2bf..8f8ab437c50f100084fd1effe2235d1102d35982 100644 --- a/src/game/core/Engine.cpp +++ b/src/game/core/Engine.cpp @@ -1,7 +1,9 @@ #include "Engine.hpp" +#include "GameManager.hpp" #include "Scene.hpp" #include "Spritesheet.hpp" #include "Window.hpp" +#include <iostream> #include <SDL.h> #include <SDL_events.h> @@ -16,7 +18,9 @@ namespace advanced_wars { -Engine::Engine(Window& window) : m_window(window), m_quit(false), m_unitConfig("../config.xml") +Engine::Engine(Window& window) + : m_window(window), m_quit(false), m_unitConfig("../config.xml"), + m_gameManager(std::make_unique<GameManager>()) { this->m_SDLRenderer = SDL_CreateRenderer( @@ -132,6 +136,16 @@ SDL_Renderer* Engine::renderer() return this->m_SDLRenderer; } +void Engine::startGame(const std::string& levelPath) +{ + m_gameManager->startGame(*this, levelPath); +} + +Window& Engine::getWindow() +{ + return this->m_window; +} + Engine::~Engine() { SDL_DestroyRenderer(m_SDLRenderer); diff --git a/src/game/core/Engine.hpp b/src/game/core/Engine.hpp index 0325473153ca3ec6ee1dd757f3be6d14831b36df..370e495e885de42b5d6d98ed773b8ed87af3d2ae 100644 --- a/src/game/core/Engine.hpp +++ b/src/game/core/Engine.hpp @@ -16,6 +16,7 @@ namespace advanced_wars { // Forward declaration +class GameManager; class Scene; class Config; @@ -56,6 +57,10 @@ class Engine SDL_Renderer* renderer(); + void startGame(const std::string& levelPath); + + Window& getWindow(); + ~Engine(); private: @@ -68,7 +73,8 @@ class Engine bool m_quit; int m_stage; - Config m_unitConfig; + Config m_unitConfig; + std::unique_ptr<GameManager> m_gameManager; }; } // namespace advanced_wars diff --git a/src/game/core/GameManager.cpp b/src/game/core/GameManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dda9b65d76bfcb78b9adcd3887061649181a5f3c --- /dev/null +++ b/src/game/core/GameManager.cpp @@ -0,0 +1,22 @@ +#include "GameManager.hpp" +#include <iostream> +#include <memory> + +namespace advanced_wars +{ + +void GameManager::startGame(Engine& engine, const std::string& levelFilePath) +{ + std::cout << "Starting game with level file: " << levelFilePath << "\n"; + std::unique_ptr<Level> level = std::make_unique<Level>(levelFilePath, engine); + m_currentLevel = std::move(level); + engine.pushScene(m_currentLevel); +} + +void GameManager::returnToMenu(Engine& engine) +{ + m_currentLevel.reset(); + engine.returnToMenu(); +} + +} // namespace advanced_wars diff --git a/src/game/core/GameManager.hpp b/src/game/core/GameManager.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c27334523740933e603dd85d83f9e97542d58300 --- /dev/null +++ b/src/game/core/GameManager.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "../level/Level.hpp" +#include "Engine.hpp" + +#include <memory> +#include <string> + +namespace advanced_wars +{ + +class GameManager +{ + public: + GameManager() = default; + ~GameManager() = default; + + void startGame(Engine& engine, const std::string& levelFilePath); + void returnToMenu(Engine& engine); + + private: + std::shared_ptr<Level> m_currentLevel; +}; + +} // namespace advanced_wars diff --git a/src/game/core/Spritesheet.cpp b/src/game/core/Spritesheet.cpp index 70c8edb01ce5526545454b8e3cca0ddb364a285b..997b7766e8ab83f31ccf8fa7312a0bfb96f0d7e1 100644 --- a/src/game/core/Spritesheet.cpp +++ b/src/game/core/Spritesheet.cpp @@ -396,6 +396,47 @@ Spritesheet::Spritesheet(std::string path, Engine& engine) this->m_numberTextures = tmp; this->m_numberWidth = 8; this->m_numberHeight = 8; + + // Bullet + HighFive::DataSet bulletDs = file.getDataSet("/misc/bullet"); + + std::vector<std::vector<uint32_t>> bulletFrames; + bulletDs.read(bulletFrames); + + std::vector<uint32_t> bulletBuffer(8 * 8, 0); + + // every animation frame + + for (size_t y = 0; y < 8; y++) + { + for (size_t x = 0; x < 8; x++) + { + size_t index = (y * 8) + x; + + number_buffer.at(index) = bulletFrames.at(8 - y - 1).at(x); + } + } + + SDL_Texture* bulletTmp = SDL_CreateTexture( + engine.renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, 8, 8); + + SDL_SetTextureBlendMode(bulletTmp, SDL_BLENDMODE_BLEND); + + if (bulletTmp == nullptr) + { + throw std::runtime_error( + "Fehler beim Erstellen der Textur für die Effects: " + std::string(SDL_GetError())); + } + + if (SDL_UpdateTexture(bulletTmp, NULL, number_buffer.data(), 8 * sizeof(int32_t)) != 0) + { + throw std::runtime_error( + "Fehler beim updaten der Textur für die Tiles: " + std::string(SDL_GetError())); + } + + this->m_bulletTexture = bulletTmp; + this->m_bulletWidth = 8; + this->m_bulletHeight = 8; } // Tiles @@ -489,6 +530,22 @@ SDL_Texture* Spritesheet::getNumberTexture() return this->m_numberTextures; } +// Bullet +SDL_Texture* Spritesheet::getBulletTexture() +{ + return this->m_bulletTexture; +} + +int Spritesheet::getBulletWidth() +{ + return this->m_bulletWidth; +} + +int Spritesheet::getBulletHeight() +{ + return this->m_bulletHeight; +} + Spritesheet::~Spritesheet() { for (std::pair<SDL_Texture*, int> tile_texture : m_tileTextures) @@ -518,6 +575,8 @@ Spritesheet::~Spritesheet() } SDL_DestroyTexture(m_numberTextures); + + SDL_DestroyTexture(m_bulletTexture); } } // namespace advanced_wars diff --git a/src/game/core/Spritesheet.hpp b/src/game/core/Spritesheet.hpp index 00c84a2a6e60b596b412269e755e694242337270..e3c5e870f1067371bb3019ee8bf0afe1d2d9d6f4 100644 --- a/src/game/core/Spritesheet.hpp +++ b/src/game/core/Spritesheet.hpp @@ -116,7 +116,7 @@ class Spritesheet /** * Gets the hierarchical vector of all unit textures. * - * The vector groups the faction, the UnitId and the state for each unit. + * The vector groups the faction, the UnitTypeId 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]. @@ -173,6 +173,25 @@ class Spritesheet */ SDL_Texture* getNumberTexture(); + // Bullet + /** + * @return The width of a bullet in pixels + */ + int getBulletWidth(); + + /** + * @return The height of an bullet in pixels + */ + int getBulletHeight(); + + /** + * The texture represents the bullet texture. + * + * @return Bullet texture + */ + + SDL_Texture* getBulletTexture(); + private: // Tiles std::vector<std::pair<SDL_Texture*, int>> m_tileTextures; @@ -205,5 +224,11 @@ class Spritesheet int m_numberWidth; int m_numberHeight; + + // Bullet + SDL_Texture* m_bulletTexture; + + int m_bulletWidth; + int m_bulletHeight; }; } // namespace advanced_wars diff --git a/src/game/entities/Building.cpp b/src/game/entities/Building.cpp index 3052460396dd5495cd1173234d88d59aab094814..cfe72cb65c8f4f7be1de0188ab13b482ac82de8a 100644 --- a/src/game/entities/Building.cpp +++ b/src/game/entities/Building.cpp @@ -1,12 +1,13 @@ #include "Building.hpp" #include "../core/Spritesheet.hpp" +#include <algorithm> #include <iostream> namespace advanced_wars { -Building::Building(int x, int y, BuildingId id, BuildingFaction faction) +Building::Building(int x, int y, BuildingId id, Faction faction) : m_x(x), m_y(y), m_id(id), m_faction(faction) {}; void Building::render(Engine& engine, int scale) @@ -30,12 +31,12 @@ void Building::render(Engine& engine, int scale) &dst, 0, NULL, SDL_FLIP_NONE); } -BuildingFaction Building::getFaction() +Faction Building::getFaction() { return this->m_faction; } -bool Building::switch_faction(BuildingFaction faction) +bool Building::switch_faction(Faction faction) { this->m_faction = faction; @@ -50,16 +51,16 @@ bool Building::switch_faction(BuildingFaction faction) // implement call to UI to show available units void Building::on_click() { - std::cout << "A building is selected!" << std::endl; + std::cout << "A building is selected!" << "\n"; }; -bool Building::check_spawn(std::unordered_map<int, advanced_wars::Unit>& units) +bool Building::check_spawn(std::unordered_map<int, std::unique_ptr<Unit>>& units) { - for (auto& [id, unit] : units) { - if (unit.getXPosition() == this->getXPosition() && - unit.getYPosition() == this->getYPosition()) + Unit& unitRef = *unit; + if (unitRef.getXPosition() == this->getXPosition() && + unitRef.getYPosition() == this->getYPosition()) { return false; } @@ -74,35 +75,36 @@ bool Building::check_money(int price, int playerMoney) return (playerMoney >= price); } -std::vector<UnitId> Building::recruitableUnits() +std::vector<UnitTypeId> Building::recruitableUnits() { if (this->m_id == BuildingId::FACTORY) { return { - UnitId::INFANTERY, - UnitId::MECHANIZED_INFANTERY, - UnitId::RECON, - UnitId::APC, - UnitId::ARTILLERY, - UnitId::ANTI_AIR_TANK, - UnitId::ANTI_AIR_MISSILE_LAUNCHER, - UnitId::ROCKET_ARTILLERY, - UnitId::MEDIUM_TANK, - UnitId::NEO_TANK, - UnitId::HEAVY_TANK}; + UnitTypeId::INFANTERY, + UnitTypeId::MECHANIZED_INFANTERY, + UnitTypeId::RECON, + UnitTypeId::APC, + UnitTypeId::ARTILLERY, + UnitTypeId::ANTI_AIR_TANK, + UnitTypeId::ANTI_AIR_MISSILE_LAUNCHER, + UnitTypeId::ROCKET_ARTILLERY, + UnitTypeId::MEDIUM_TANK, + UnitTypeId::NEO_TANK, + UnitTypeId::HEAVY_TANK}; } if (this->m_id == BuildingId::PORT) { - return {UnitId::LANDER, UnitId::CRUISER, UnitId::SUBMARINE, UnitId::BATTLESHIP}; + return { + UnitTypeId::LANDER, UnitTypeId::CRUISER, UnitTypeId::SUBMARINE, UnitTypeId::BATTLESHIP}; } if (this->m_id == BuildingId::AIRPORT) { return { - UnitId::TRANSPORT_HELICOPTER, UnitId::BATTLE_HELICOPTER, UnitId::FIGHTER, - UnitId::BOMBER}; + UnitTypeId::TRANSPORT_HELICOPTER, UnitTypeId::BATTLE_HELICOPTER, UnitTypeId::FIGHTER, + UnitTypeId::BOMBER}; } return {}; diff --git a/src/game/entities/Building.hpp b/src/game/entities/Building.hpp index e7608a43a9f893b08c6eb25357d3c9e3549cd8e0..68f2cb5249268e2f1a9f3a8f0035ae976649fef2 100644 --- a/src/game/entities/Building.hpp +++ b/src/game/entities/Building.hpp @@ -12,7 +12,7 @@ namespace advanced_wars class Building { public: - Building(int x, int y, BuildingId id, BuildingFaction faction); + Building(int x, int y, BuildingId id, Faction faction); void render(Engine& engine, int scale); @@ -20,7 +20,7 @@ class Building int getYPosition(); - BuildingFaction getFaction(); + Faction getFaction(); BuildingId getId(); @@ -31,12 +31,12 @@ class Building @return true if building was a headquarter */ - bool switch_faction(BuildingFaction faction); + bool switch_faction(Faction faction); /* checks if the tile ontop of the building is free */ - bool check_spawn(std::unordered_map<int, advanced_wars::Unit>& units); + bool check_spawn(std::unordered_map<int, std::unique_ptr<Unit>>& units); /* checks if the player has enough money for the unit to be recruited @@ -53,13 +53,13 @@ class Building * Provides a vector of recruitable units, depending on the building id * */ - std::vector<UnitId> recruitableUnits(); + std::vector<UnitTypeId> recruitableUnits(); private: - int m_x; - int m_y; - BuildingId m_id; - BuildingFaction m_faction; + int m_x; + int m_y; + BuildingId m_id; + Faction m_faction; }; } // namespace advanced_wars diff --git a/src/game/entities/Bullet.cpp b/src/game/entities/Bullet.cpp new file mode 100644 index 0000000000000000000000000000000000000000..737ac546c77f830f3a20b6bccad5b0b43dad9b66 --- /dev/null +++ b/src/game/entities/Bullet.cpp @@ -0,0 +1,108 @@ +#include "Bullet.hpp" +#include "../core/Engine.hpp" +#include "../physics/PhysicsBody.hpp" +#include "../physics/PhysicsEngine.hpp" +#include "box2d/b2_settings.h" +#include <iostream> +#include <stdexcept> + +namespace advanced_wars +{ + +Bullet::Bullet( + b2World* world, float startX, float startY, float velocityX, float velocityY, Unit& target) + : m_renderX(0), m_renderY(0), m_target(target) +{ + // Erstelle einen PhysicsBody für die Bullet + m_physicsBody = std::make_unique<PhysicsBody>( + world, startX, startY, 4.0F, 4.0F, 1.0, 0.3, true, BodyType::PROJECTILE); + + if (m_physicsBody && m_physicsBody->getBody()) + { + BodyUserData* bud = new BodyUserData(); + bud->type = BodyUserData::Type::Bullet; + bud->id = target.getUnitId(); + bud->data = this; + + m_physicsBody->getBody()->GetUserData().pointer = reinterpret_cast<uintptr_t>(bud); + } + m_physicsBody->setVelocity({velocityX, velocityY}); +} + +Bullet::~Bullet() +{ + // Der PhysicsBody wird automatisch durch `unique_ptr` zerstört. +} + +void Bullet::update() +{ + if (!m_physicsBody) + { + return; + } + + // if (m_destroyFlag) + // { + // destroy(); + // return; + // } + + b2Vec2 velocity = m_physicsBody->getVelocity(); + b2Vec2 pos = m_physicsBody->getPosition(); + + if (pos.x == 0 && pos.y == 0) + { + std::cerr << "❌ WARNUNG: Bullet-Position ist (0,0)!\n"; + } + + m_renderX = pos.x * 16 + (8 / 2); + m_renderY = pos.y * 16 + (8 / 2); + + m_rotation = std::atan2(velocity.y, velocity.x) * 180.0F / M_PI; +} + +void Bullet::render(Engine& engine, int scale) +{ + // Falls die Textur noch nicht gesetzt wurde, laden wir sie jetzt + if (!m_texture) + { + Spritesheet* spritesheet = engine.getSpritesheet(); + m_texture = spritesheet->getBulletTexture(); // Textur aus HDF5-File laden + m_width = spritesheet->getBulletWidth(); + m_height = spritesheet->getBulletHeight(); + } + + // Ziel-Rect setzen (angepasst für Skalierung) + SDL_Rect destRect = { + static_cast<int>(m_renderX * scale), static_cast<int>(m_renderY * scale), m_width * scale, + m_height * scale}; + + SDL_Point center = {m_width * scale / 2, m_height * scale / 2}; + + // Textur rendern + SDL_RenderCopyEx( + engine.renderer(), m_texture, nullptr, &destRect, m_rotation, ¢er, SDL_FLIP_NONE); +} + +void Bullet::destroy() +{ + if (m_physicsBody) + { + m_physicsBody->getBody()->GetUserData().pointer = 0; + m_physicsBody->destroy(); + m_physicsBody.reset(); + } + m_destroyFlag = true; +} + +PhysicsBody& Bullet::getBody() const +{ + return *m_physicsBody; +} + +void Bullet::setDestroyFlag(bool flag) +{ + m_destroyFlag = flag; +} + +} // namespace advanced_wars diff --git a/src/game/entities/Bullet.hpp b/src/game/entities/Bullet.hpp new file mode 100644 index 0000000000000000000000000000000000000000..41a8de96cc1d91ccb13ab5b5e757412e2ad9116c --- /dev/null +++ b/src/game/entities/Bullet.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "../core/Engine.hpp" +#include "../entities/Unit.hpp" +#include "../physics/PhysicsBody.hpp" +#include <SDL.h> +#include <box2d/box2d.h> + +namespace advanced_wars +{ + +class Bullet +{ + public: + /** + * Erzeugt eine neue Bullet. + * + * @param world Pointer auf die Box2D-Welt, in der der Body angelegt wird. + * @param startX Startposition X in Pixeln. + * @param startY Startposition Y in Pixeln. + * @param velocityX Anfangsgeschwindigkeit X (in m/s, Box2D-Einheiten). + * @param velocityY Anfangsgeschwindigkeit Y (in m/s, Box2D-Einheiten). + */ + Bullet( + b2World* world, float startX, float startY, float velocityX, float velocityY, + Unit& target); + + ~Bullet(); + + /// Update: Liest die Position des Box2D-Bodies und bereitet die Renderkoordinaten vor. + void update(); + + /// Rendert die Bullet mit SDL. + void render(Engine& engine, int scale); + + /// Gibt den Box2D-Body zurück (nützlich z.B. für Kollisionsbehandlung) + PhysicsBody& getBody() const; + + void destroy(); + + void setDestroyFlag(bool flag); + + bool shouldDestroy() const { return m_destroyFlag; } + + Unit& getTarget() { return m_target; } + + private: + std::unique_ptr<PhysicsBody> m_physicsBody; + SDL_Texture* m_texture = nullptr; + int m_width = 0; + int m_height = 0; + double m_rotation = 0; + bool m_destroyFlag = false; + Unit& m_target; + + // Gerenderte Position in Pixeln (berechnet aus der Box2D-Position) + float m_renderX = 0; + float m_renderY = 0; +}; +} // namespace advanced_wars diff --git a/src/game/entities/Unit.cpp b/src/game/entities/Unit.cpp index 580e1819a8f634550254fa6cff0761669e403b22..3977ae8a926b234f02b776678d9c34f62b0276fc 100644 --- a/src/game/entities/Unit.cpp +++ b/src/game/entities/Unit.cpp @@ -1,284 +1,161 @@ #include "Unit.hpp" #include "../core/Config.hpp" +#include "../physics/PhysicsBody.hpp" +#include "../physics/PhysicsEngine.hpp" +#include "box2d/box2d.h" #include <iostream> +#include <memory> namespace advanced_wars { -Unit::Unit(int x, int y, UnitFaction faction, UnitId id, UnitState state, Config& config) - : m_x(x), m_y(y), m_faction(faction), m_id(id), m_state(state), m_maxHealth(100) +Unit::Unit( + int unitId, b2World* world, int tileX, int tileY, Faction faction, UnitTypeId unitTypeId, + UnitState state, Config& config) + : m_unitId(unitId), m_tileX(tileX), m_tileY(tileY), m_health(100), + m_cost(config.getUnitCost(unitTypeId)), m_ammo(config.getUnitAmmo(unitTypeId)), + m_minRange(config.getUnitMinRange(unitTypeId)), + m_maxRange(config.getUnitMaxRange(unitTypeId)), m_maxHealth(100), + m_movementPoints(config.getUnitMovementPoints(unitTypeId)), + m_movementType(config.getUnitMovementType(unitTypeId)), m_faction(faction), + m_unitTypeId(unitTypeId), m_state(state), m_world(world), m_animX(tileX), m_animY(tileY) { - // Allgemeine Einheiteneinstellungen aus Konfiguration holen - m_cost = config.getUnitCost(id); - m_movementPoints = config.getUnitMovementPoints(id); - m_ammo = config.getUnitAmmo(id); - m_minRange = config.getUnitMinRange(id); - m_maxRange = config.getUnitMaxRange(id); - m_health = m_maxHealth; + m_physicsBody = std::make_unique<PhysicsBody>( + m_world, tileX, tileY, 8.0F, 8.0F, 1.0, 0.3, true, BodyType::UNIT); - m_movementType = config.getUnitMovementType(id); + if (m_physicsBody && m_physicsBody->getBody()) + { + BodyUserData* bud = new BodyUserData(); + bud->type = BodyUserData::Type::Unit; + bud->id = m_unitId; + bud->data = this; + + m_physicsBody->getBody()->GetUserData().pointer = reinterpret_cast<uintptr_t>(bud); + } // Initialisieren der Primär- und Sekundärwaffe - std::unordered_map<UnitId, int> primaryDamage; - std::unordered_map<UnitId, int> secondaryDamage; + std::unordered_map<UnitTypeId, int> primaryDamage; + std::unordered_map<UnitTypeId, int> secondaryDamage; - for (int targetIt = static_cast<int>(UnitId::FIRST); targetIt <= static_cast<int>(UnitId::LAST); - ++targetIt) + for (int targetIt = static_cast<int>(UnitTypeId::FIRST); + targetIt <= static_cast<int>(UnitTypeId::LAST); ++targetIt) { - UnitId targetId = static_cast<UnitId>(targetIt); + auto targetId = static_cast<UnitTypeId>(targetIt); - // Prüfen, ob ein gültiger Schadenswert vorhanden ist, und nur dann hinzufügen - if (auto damage = config.getUnitPrimaryWeaponDamage(id, targetId)) + if (auto damage = config.getUnitPrimaryWeaponDamage(m_unitTypeId, targetId)) { primaryDamage[targetId] = *damage; } - if (auto damage = config.getUnitSecondaryWeaponDamage(id, targetId)) + if (auto damage = config.getUnitSecondaryWeaponDamage(m_unitTypeId, targetId)) { secondaryDamage[targetId] = *damage; } } - m_primaryWeapon = Weapon(config.getUnitPrimaryWeapon(id), primaryDamage); - m_secondaryWeapon = Weapon(config.getUnitSecondaryWeapon(id), secondaryDamage); + m_primaryWeapon = + std::make_unique<Weapon>(Weapon(config.getUnitPrimaryWeapon(m_unitTypeId), primaryDamage)); + m_secondaryWeapon = std::make_unique<Weapon>( + Weapon(config.getUnitSecondaryWeapon(m_unitTypeId), secondaryDamage)); } -void Unit::render(Engine& engine, int scale) +void Unit::update(float deltaTime) { - Spritesheet* spritesheet = engine.getSpritesheet(); - - int step = engine.getStage() % spritesheet->getUnitTextures() - .at(static_cast<int>(m_faction)) - .at(static_cast<int>(m_id)) - .at(static_cast<int>(m_state)) - .second; - - if (m_state == UnitState::IDLE || m_state == UnitState::UNAVAILABLE) - { - - SDL_Rect src; - src.x = step * spritesheet->getUnitWidth(); - src.y = 0; - src.w = spritesheet->getUnitWidth(); - src.h = spritesheet->getUnitHeight(); - - SDL_Rect dst; - dst.x = m_x * spritesheet->getUnitWidth() * scale; - dst.y = m_y * spritesheet->getUnitHeight() * scale; - dst.w = spritesheet->getUnitWidth() * scale; - dst.h = spritesheet->getUnitHeight() * scale; - - SDL_RenderCopyEx( - engine.renderer(), - spritesheet->getUnitTextures() - .at(static_cast<int>(m_faction)) - .at(static_cast<int>(m_id)) - .at(static_cast<int>(m_state)) - .first, - &src, &dst, 0, NULL, SDL_FLIP_NONE); - } - else + if (!m_physicsBody) { - // The moving states have a resolution of 24x24 instead of 16x16 and need to - // be handled separately - SDL_Rect src; - src.x = step * spritesheet->getUnitMovingWidth(); - src.y = 0; - src.w = spritesheet->getUnitMovingWidth(); - src.h = spritesheet->getUnitMovingHeight(); - - SDL_Rect dst; - dst.x = ((m_x * spritesheet->getUnitWidth()) - 4) * scale; - dst.y = ((m_y * spritesheet->getUnitHeight()) - 8) * scale; - dst.w = spritesheet->getUnitMovingWidth() * scale; - dst.h = spritesheet->getUnitMovingHeight() * scale; - - SDL_RenderCopyEx( - engine.renderer(), - spritesheet->getUnitTextures() - .at(static_cast<int>(m_faction)) - .at(static_cast<int>(m_id)) - .at(static_cast<int>(m_state)) - .first, - &src, &dst, 0, NULL, SDL_FLIP_NONE); + return; } - renderHP(engine, scale); -} - -void Unit::attack(Unit& enemy) -{ - int attacker_damage_value = calculateDamage(enemy); - if (attacker_damage_value > 0) - { - performAttack(enemy, attacker_damage_value); - std::cout << "Enemy health after attack: " << enemy.m_health << std::endl; + m_physicsBody->update(deltaTime); - // Check if the enemy is still alive for counter-attack - if (enemy.m_health > 0) - { - // Check if the enemy is within attack range - int distanceX = std::abs(enemy.m_x - m_x); - int distanceY = std::abs(enemy.m_y - m_y); - int distance = distanceX + distanceY; - - if (distance >= enemy.m_minRange && distance <= enemy.m_maxRange) - { - // Now, they are reversed for the counter-attack - int defender_damage_value = enemy.calculateDamage(*this); - if (defender_damage_value > 0) - { - enemy.performAttack(*this, defender_damage_value); - std::cout << "Ally health after retaliation: " << this->m_health << std::endl; - } - } - else - { - std::cout << "Enemy out of range for counter-attack." << std::endl; - } - } - } - else - { - std::cout << "No damage value found for attack from unit " << static_cast<int>(m_id) - << " against unit " << static_cast<int>(enemy.m_id) << std::endl; - } + b2Vec2 pos = m_physicsBody->getPosition(); + calcState(static_cast<int>(pos.x), static_cast<int>(pos.y)); + m_animX = static_cast<int>(pos.x); + m_animY = static_cast<int>(pos.y); } -int Unit::calculateDamage(Unit& target) +void Unit::moveTo(int posX, int posY) { - // Pointers to Weapon objects - Weapon* primaryWeapon = &m_primaryWeapon; - Weapon* secondaryWeapon = &m_secondaryWeapon; - - // Find the corresponding damage values - auto primary_damage_it = primaryWeapon->getDamage().find(target.m_id); - auto secondary_damage_it = secondaryWeapon->getDamage().find(target.m_id); - - int damage_value = 0; - - // Calculate damage using secondary weapon if available - if (secondary_damage_it != secondaryWeapon->getDamage().end()) + if (!m_physicsBody) { - damage_value = secondary_damage_it->second; - } - - // Calculate damage using primary weapon if higher and ammo is available - if (primary_damage_it != primaryWeapon->getDamage().end()) - { - // Check ammo correctly - int& ammo = m_ammo; - - if (primary_damage_it->second > damage_value && ammo > 0) - { - ammo -= 1; - damage_value = primary_damage_it->second; - std::cout << " ammo = " << ammo << std::endl; - } + return; } - return damage_value; -} - -void Unit::performAttack(Unit& target, int damage) -{ - int effective_damage = damage * (static_cast<float>(m_health) / m_maxHealth); - target.m_health -= effective_damage; - target.m_health = std::max(0, target.m_health); -} - -void Unit::updatePosition(int posX, int posY) -{ + m_physicsBody->setTargetPosition(posX, posY); calcState(posX, posY); - - this->m_x = posX; - this->m_y = posY; + m_tileX = posX; + m_tileY = posY; } void Unit::calcState(int posX, int posY) { - int deltaX = this->m_x - posX; - int deltaY = this->m_y - posY; + int deltaX = this->m_animX - posX; + int deltaY = this->m_animY - posY; if (deltaX == 0 && deltaY == 0) { - // Unit is already at the target position + if (m_hasMoved) + { + m_state = UnitState::UNAVAILABLE; + } return; } if (abs(deltaX) >= abs(deltaY)) { - if (deltaX > 0) - { - this->m_state = advanced_wars::UnitState::MOVEMENTLEFT; - } - else - { - this->m_state = advanced_wars::UnitState::MOVEMENTRIGHT; - } + this->m_state = (deltaX > 0) ? UnitState::MOVEMENTLEFT : UnitState::MOVEMENTRIGHT; } else { - if (deltaY > 0) - { - this->m_state = advanced_wars::UnitState::MOVEMENTUP; - } - else - { - this->m_state = advanced_wars::UnitState::MOVEMENTDOWN; - } + this->m_state = (deltaY > 0) ? UnitState::MOVEMENTUP : UnitState::MOVEMENTDOWN; } } -void Unit::on_left_click(SDL_Event event) +void Unit::render(Engine& engine, int scale) { + Spritesheet* spritesheet = engine.getSpritesheet(); - std::cout << "Left-button pressed on unit: " << this->m_health << std::endl; -} - -std::vector<Unit*> Unit::getUnitsInRangeWithDamagePotential(const std::vector<Unit*>& allUnits) -{ - std::vector<Unit*> unitsInRangeWithDamage; + int step = engine.getStage() % spritesheet->getUnitTextures() + .at(static_cast<int>(m_faction)) + .at(static_cast<int>(m_unitTypeId)) + .at(static_cast<int>(m_state)) + .second; - for (Unit* unit : allUnits) - { // Iterate over all units - // except itself - if (unit->getFaction() == this->m_faction) - { - continue; - } + SDL_Rect src, dst; + if (m_state == UnitState::IDLE || m_state == UnitState::UNAVAILABLE) + { + src.x = step * spritesheet->getUnitWidth(); + src.y = 0; + src.w = spritesheet->getUnitWidth(); + src.h = spritesheet->getUnitHeight(); - int distanceX = std::abs(unit->m_x - m_x); - int distanceY = std::abs(unit->m_y - m_y); + dst.x = m_animX * spritesheet->getUnitWidth() * scale; + dst.y = m_animY * spritesheet->getUnitHeight() * scale; + dst.w = spritesheet->getUnitWidth() * scale; + dst.h = spritesheet->getUnitHeight() * scale; + } + else + { + src.x = step * spritesheet->getUnitMovingWidth(); + src.y = 0; + src.w = spritesheet->getUnitMovingWidth(); + src.h = spritesheet->getUnitMovingHeight(); - int distance = distanceX + distanceY; - if (distance >= m_minRange && distance <= m_maxRange) - { - // Prüfen ob Schaden möglich ist - auto primaryDamageIt = m_primaryWeapon.getDamage().find(unit->m_id); - auto secondaryDamageIt = m_secondaryWeapon.getDamage().find(unit->m_id); - - bool canDealDamage = false; - - // Prüfen, ob Primärwaffe Schaden machen kann - if (primaryDamageIt != m_primaryWeapon.getDamage().end() && m_ammo > 0) - { - canDealDamage = true; - } - // Prüfen, ob Sekundärwaffe Schaden machen kann - if (secondaryDamageIt != m_secondaryWeapon.getDamage().end()) - { - canDealDamage = true; - } - - if (canDealDamage) - { - unitsInRangeWithDamage.push_back(unit); - } - } + dst.x = ((m_animX * spritesheet->getUnitWidth()) - 4) * scale; + dst.y = ((m_animY * spritesheet->getUnitHeight()) - 8) * scale; + dst.w = spritesheet->getUnitMovingWidth() * scale; + dst.h = spritesheet->getUnitMovingHeight() * scale; } + SDL_RenderCopyEx( + engine.renderer(), + spritesheet->getUnitTextures() + .at(static_cast<int>(m_faction)) + .at(static_cast<int>(m_unitTypeId)) + .at(static_cast<int>(m_state)) + .first, + &src, &dst, 0, nullptr, SDL_FLIP_NONE); - return unitsInRangeWithDamage; + renderHP(engine, scale); } void Unit::renderHP(Engine& engine, int scale) @@ -289,69 +166,39 @@ void Unit::renderHP(Engine& engine, int scale) int numberWidth = spritesheet->getNumberWidth(); int numberHeight = spritesheet->getNumberHeight(); - int hp = ceil((double)m_health / 10); + int unitHp = ceil((double)m_health / 10); - SDL_Rect src; - src.x = hp % 10 * numberWidth; + SDL_Rect src, dest; + src.x = unitHp % 10 * numberWidth; src.y = 0; src.w = numberWidth; src.h = numberHeight; - SDL_Rect dest; - dest.x = (m_x * spritesheet->getTileWidth() + 8) * scale; - dest.y = (m_y * spritesheet->getTileHeight() + 12) * scale; + dest.x = (m_animX * spritesheet->getTileWidth() + 8) * scale; + dest.y = (m_animY * spritesheet->getTileHeight() + 12) * scale; dest.w = numberWidth * scale; dest.h = numberHeight * scale; SDL_RenderCopy(engine.renderer(), numbers, &src, &dest); - if (hp == 10) + if (unitHp == 10) { src.x = 8; - - dest.x = (m_x * spritesheet->getTileWidth() + 1) * scale; - + dest.x = (m_animX * spritesheet->getTileWidth() + 1) * scale; SDL_RenderCopy(engine.renderer(), numbers, &src, &dest); } } -UnitFaction Unit::getFaction() -{ - return this->m_faction; -} - -void Unit::setState(UnitState state) -{ - this->m_state = state; -} - -bool Unit::hasAttacked() +void Unit::destroyBody() { - return this->m_hasAttacked; -} - -bool Unit::hasMoved() -{ - return this->m_hasMoved; -} - -int Unit::getXPosition() -{ - return m_x; -} - -int Unit::getYPosition() -{ - return m_y; + if (m_physicsBody) + { + m_physicsBody->getBody()->GetUserData().pointer = 0; + m_physicsBody->destroy(); + m_physicsBody.reset(); + } } -int Unit::getMovementPoints() -{ - return m_movementPoints; -} -MovementType Unit::getMovementType() -{ - return m_movementType; -} +Unit::~Unit() {} } // namespace advanced_wars diff --git a/src/game/entities/Unit.hpp b/src/game/entities/Unit.hpp index 9d2adad61cbb6e2265440be2ecbaf949d5927392..73e74bb66716a449093b154872593d3eb98a523b 100644 --- a/src/game/entities/Unit.hpp +++ b/src/game/entities/Unit.hpp @@ -3,8 +3,10 @@ #include "../combat/Weapon.hpp" #include "../core/Config.hpp" #include "../core/Engine.hpp" +#include "../physics/PhysicsBody.hpp" #include <SDL_events.h> +#include <memory> #include <unordered_map> #include <vector> @@ -14,165 +16,91 @@ namespace advanced_wars class Engine; class Config; -using MatchupTable = std::unordered_map<UnitId, std::unordered_map<UnitId, int>>; - class Unit { public: /** - * Constructor for Unit. - * Initializes the unit's position, faction, identifier, state, and configuration settings. + * Konstruktor für eine Einheit. + * Initialisiert die Position, Fraktion, Einheitentyp, Zustand und Konfigurationswerte. */ - Unit(int x, int y, UnitFaction faction, UnitId id, UnitState state, Config& config); - - int getXPosition(); - - int getYPosition(); + Unit( + int unitId, b2World* world, int tileX, int tileY, Faction faction, + UnitTypeId unitTypeId, UnitState state, Config& config); - /** - * Destructor for Unit. - * Handles any cleanup necessary when a unit is destroyed. - * (Currently assumes this triggers certain game events, though not explicitly detailed - * here.) - */ - ~Unit() {}; + ~Unit(); - /** - * Renders the unit on the game screen. - * Uses the engine's rendering capabilities to draw the unit based on its state and faction. - * - * @param engine The game engine responsible for rendering. - * @param scale Scaling factor for rendering the unit. - */ - void render(Engine& engine, int scale); + /// Aktualisiert die Einheit (Bewegung, Physik, etc.). + void update(float deltaTime); - /** - * Determines if another unit (enemy) is within attack range. - * Checks for the range based on the unit's weapon capabilities. - * - * @param enemy The enemy unit to check range against. - * @return true if the enemy is in range, false otherwise. - */ - bool inRange(Unit& enemy); + /// Bewegt die Einheit zu einer neuen Position. + void moveTo(int posX, int posY); - /** - * Initiates an attack on a specified enemy unit. - * Calculates damage and updates health values for both the attacker and defender. - * Attacker damages the enemy first; if the enemy survives, a counter-attack may occur. - * - * @param enemy The unit being attacked. - */ - void attack(Unit& enemy); - - /** - * Performs the calculated damage on a target unit, adjusting its health accordingly. - * Considers this unit's current health for scaling damage. - * - * @param target The target unit receiving damage. - * @param damage The amount of damage calculated to be applied. - */ - void performAttack(Unit& target, int damage); - - /** - * Calculates the potential damage this unit can inflict on a target. - * Considers primary and secondary weapons' damage tables, and checks ammunition - * availability. - * - * @param target The unit to calculate damage against. - * @return The calculated damage value. - */ - int calculateDamage(Unit& target); - - /** - * Updates this unit's position on the game field. - * Changes the internal position and recalculates the unit's state (e.g., direction it's - * facing) based on movement to a new position. - * - * @param posX The new x-coordinate for the unit. - * @param posY The new y-coordinate for the unit. - */ - void updatePosition(int posX, int posY); - - /** - * Determines potential movement paths based on the unit's movement type and current - * terrain. (Intended to use Dijkstra's algorithm, though implementation details are omitted - * here.) - */ - void calculateMovement(); - - /** - * Recalculates and updates the unit's state based on movement direction. - * Ensures that the unit faces the correct direction after a move. - * - * @param posX The x-coordinate of the desired position. - * @param posY The y-coordinate of the desired position. - */ + /// Berechnet den neuen Zustand basierend auf der Bewegungsrichtung. void calcState(int posX, int posY); - /** - * Processes a left-click event on this unit. - * Typically triggers display of unit information (e.g., UI and movement range). - * - * @param event SDL event captured, specifically mouse click event. - */ - void on_left_click(SDL_Event event); + /// Zerstört den Physics-Body der Einheit. + void destroyBody(); - UnitFaction getFaction(); - - int getAmmo() const { return m_ammo; } - int getHealth() const { return m_health; } - int getCost() const { return m_cost; } - UnitId getId() const { return m_id; } - - int getMovementPoints(); - MovementType getMovementType(); - - void setState(UnitState state); - - inline UnitState getState() const { return m_state; } + /// Rendert die Einheit auf dem Bildschirm. + void render(Engine& engine, int scale); - /** - * Retrieves units within range that this unit can deal damage to. - * Considers all units provided in 'allUnits', excluding itself, and checks movement and - * weapon range. - * - * @param allUnits Vector of pointers to all units on the field to check against. - * @return Vector of pointers to units in range that can be engaged. - */ - std::vector<Unit*> getUnitsInRangeWithDamagePotential(const std::vector<Unit*>& allUnits); + /// Rendert die HP-Anzeige über der Einheit. + void renderHP(Engine& engine, int scale); - bool hasMoved(); - bool hasAttacked(); + // Getter-Methoden + int getXPosition() const { return m_tileX; }; + int getYPosition() const { return m_tileY; }; + Faction getFaction() { return this->m_faction; } + + void setState(UnitState state) { this->m_state = state; } + int getAmmo() const { return m_ammo; } + int getHealth() const { return m_health; } + int getCost() const { return m_cost; } + UnitTypeId getUnitTypeId() const { return m_unitTypeId; } + int getMovementPoints() const { return m_movementPoints; } + MovementType getMovementType() const { return m_movementType; } + UnitState getState() const { return m_state; } + bool hasMoved() const { return this->m_hasMoved; } + void setMoved(bool moved) { m_hasMoved = moved; } + bool hasAttacked() const { return this->m_hasAttacked; } + int getMaxHealth() const { return m_maxHealth; } + int getMinRange() const { return m_minRange; } + int getMaxRange() const { return m_maxRange; } + Weapon& getPrimaryWeapon() { return *m_primaryWeapon; } + Weapon& getSecondaryWeapon() { return *m_secondaryWeapon; } + + int getUnitId() { return m_unitId; } + + void setHealth(int health) { m_health = health; } private: - int m_x; - int m_y; - int m_health; // Current health of the unit, initialized to max health at construction. - int m_price; - - int m_movementPoints; // The number of tiles this unit can move per turn. - MovementType m_movementType; // The type of movement this unit has (e.g., foot, wheeled). - - UnitFaction m_faction; // The faction to which this unit belongs. - UnitId m_id; // The identifier for the unit type. - UnitState m_state; // The current state of the unit (idle, moving, etc.). - - int m_maxHealth; // The maximum health of the unit. - int m_range; // Possible range for future use, depending on specific unit abilities. - - bool m_hasMoved; // Indicates whether the unit has moved this turn. - bool m_hasAttacked; // Indicates whether the unit has attacked this turn. - bool m_isSelected; // Indicates whether the unit is currently selected. - bool m_isTargeted; // Indicates whether the unit is currently targeted by an enemy. - - Weapon m_secondaryWeapon; // The unit's secondary weapon. - Weapon m_primaryWeapon; // The unit's primary weapon. - - int m_cost; // The cost associated with deploying this unit. - int m_ammo; // The amount of available ammo for attacks. - int m_minRange; // The minimum range of the unit's attack capability. - int m_maxRange; // The maximum range of the unit's attack capability. - - void renderHP(Engine& engine, int scale); + int m_unitId; + int m_tileX; + int m_tileY; + int m_health; + int m_cost; + int m_ammo; + int m_minRange; + int m_maxRange; + int m_maxHealth; + int m_movementPoints; + MovementType m_movementType; + Faction m_faction; + UnitTypeId m_unitTypeId; + UnitState m_state; + + bool m_hasMoved = false; + bool m_hasAttacked = false; + bool m_isSelected = false; + bool m_isTargeted = false; + + std::unique_ptr<Weapon> m_primaryWeapon; + std::unique_ptr<Weapon> m_secondaryWeapon; + + std::unique_ptr<PhysicsBody> m_physicsBody; + b2World* m_world; + + int m_animX; + int m_animY; }; } // namespace advanced_wars diff --git a/src/game/level/Level.cpp b/src/game/level/Level.cpp index 0527b9374367217627ae511e1ccb2efa46bb3093..d38ba277b81d284fbe5e050915b37c26112f372c 100644 --- a/src/game/level/Level.cpp +++ b/src/game/level/Level.cpp @@ -5,16 +5,20 @@ #include "../effect/Effect.hpp" #include "../entities/Building.hpp" #include "../entities/Unit.hpp" +#include "../physics/PhysicsEngine.hpp" #include "../ui/context/ContextMenu.hpp" #include "../ui/menu/EndScreen.hpp" #include "../ui/menu/PauseMenu.hpp" #include "../ui/modals/HelpMenu.hpp" +#include "SDL_timer.h" +#include "box2d/box2d.h" #include <SDL.h> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/xml_parser.hpp> #include <highfive/H5File.hpp> #include <iostream> +#include <memory> #include <string> namespace advanced_wars @@ -22,45 +26,15 @@ namespace advanced_wars const int RENDERING_SCALE = 3; -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, - std::queue<Player> turnQ) - : m_name(name), m_width(width), m_height(height), m_tiles(tiles), m_selectedUnit(-1), - m_selectedBuilding(-1), m_contextMenu(ContextMenu()), m_id(0), - m_state(LevelState::SELECTING_STATE), - m_currentPos(TileMarker(RENDERING_SCALE, 1, 1, m_width, m_height)), m_turnQ(turnQ), - m_gameOver(false) +Level::Level(const std::string& path, Engine& engine) + : m_selectedUnit(-1), m_selectedBuilding(-1), m_contextMenu(ContextMenu()), m_id(0), + m_state(LevelState::SELECTING_STATE) { m_contextMenu.setOptions({"Move", "Info", "Wait"}); - for (Building building : buildings) - { - this->addBuilding(building); - } - - for (Unit unit : units) - { - this->addUnit(unit); - } - - for (Effect effect : effects) - { - this->addEffect(effect); - } - - if ((size_t)(m_width * m_height) != tiles.size()) - { - throw std::runtime_error("level tile mismatch"); - } - - m_selectedBuilding = -1; - m_selectedUnit = -1; -}; - -std::shared_ptr<Level> Level::loadLevel(const std::string& path, Engine& engine) -{ + m_physicsEngine = std::make_unique<PhysicsEngine>(); + m_combatEngine = std::make_unique<CombatEngine>(); HighFive::File file(path, HighFive::File::ReadOnly); // read level metadata @@ -70,92 +44,109 @@ std::shared_ptr<Level> Level::loadLevel(const std::string& path, Engine& engine) // read tilesarray std::vector<uint8_t> level_tilesarray; file.getDataSet("tilesarray").read(level_tilesarray); - + std::cout << "tilesarray size: " << level_tilesarray.size() << "\n"; // extract metadata from xml std::istringstream xmlStream(level_metadata); boost::property_tree::ptree pt; boost::property_tree::read_xml(xmlStream, pt); - int width = pt.get<int>("level.width"); - int height = pt.get<int>("level.height"); + m_width = pt.get<int>("level.width"); + m_height = pt.get<int>("level.height"); std::string name = pt.get<std::string>("level.name"); // if level is smaler than 20x20 surround with water tiles - if (width < 20 || height < 20) + if (m_width < 20 || m_height < 20) { - int x_start = (20 - width) / 2; - int y_start = (20 - height) / 2; + int x_start = (20 - m_width) / 2; + int y_start = (20 - m_height) / 2; std::vector<uint8_t> transformed_tiles_array; transformed_tiles_array.reserve(20 * 20); for (int y = 0; y < 20; y++) { for (int x = 0; x < 20; x++) { - if (x < x_start || y < y_start || x >= x_start + width || y >= y_start + height) + if (x < x_start || y < y_start || x >= x_start + m_width || y >= y_start + m_height) { transformed_tiles_array.push_back(1); } else { transformed_tiles_array.push_back( - level_tilesarray[x - x_start + (y - y_start) * width]); + level_tilesarray[x - x_start + (y - y_start) * m_width]); } } } level_tilesarray = std::move(transformed_tiles_array); - width = 20; - height = 20; + m_width = 20; + m_height = 20; } + m_currentPos = TileMarker(RENDERING_SCALE, 1, 1, m_width, m_height); + // create tiles, buildings and units vector from tiles array std::vector<Tile> tiles; std::vector<Building> buildings; - std::vector<Unit> units; - tiles.reserve(width * height); + tiles.reserve(m_width * m_height); bool has_factions[] = {false, false, false, false, false}; for (size_t i = 0; i < level_tilesarray.size(); i++) { - int x = i % width; - int y = i / width; + int tileX = i % m_width; + int tileY = i / m_width; if (level_tilesarray[i] >= 50) { // tile id >= 50 -> building -> have to add Plain Tile and Building - tiles.push_back(Tile(TileId(TileId::PLAIN), x, y)); - BuildingId building_id = static_cast<BuildingId>((level_tilesarray[i] - 50) % 5); - BuildingFaction faction_id = - static_cast<BuildingFaction>((level_tilesarray[i] - 50) / 5); + tiles.push_back(Tile(TileId(TileId::PLAIN), tileX, tileY)); + BuildingId building_id = static_cast<BuildingId>((level_tilesarray[i] - 50) % 5); + Faction faction_id = static_cast<Faction>((level_tilesarray[i] - 50) / 5); if (building_id == BuildingId::HEADQUARTER) { // an infantery unit should be added onto every HQ - units.push_back(Unit( - x, y, static_cast<UnitFaction>(faction_id), UnitId::INFANTERY, - UnitState::UNAVAILABLE, engine.getUnitConfig())); + int index = static_cast<int>(faction_id); + if (!has_factions[index]) + { + addUnit( + tileX, tileY, faction_id, UnitTypeId::INFANTERY, UnitState::UNAVAILABLE, + engine.getUnitConfig()); + } + has_factions[static_cast<int>(faction_id)] = true; // collect existing factions // for later building turnQ } - buildings.push_back(Building(x, y, building_id, faction_id)); + buildings.push_back(Building(tileX, tileY, building_id, faction_id)); } else { // if tile id belongs to terrain tile, just a tile needs to added TileId tile_id = static_cast<TileId>(level_tilesarray[i]); - tiles.push_back(Tile(tile_id, x, y)); + tiles.push_back(Tile(tile_id, tileX, tileY)); } } + m_tiles = std::move(tiles); + + for (Building building : buildings) + { + this->addBuilding(building); + } + + if ((size_t)(m_width * m_height) != m_tiles.size()) + { + throw std::runtime_error("level tile mismatch"); + } + // create turnQ from has_factions array std::queue<Player> turnQ; for (int i = 0; i < 5; i++) { if (has_factions[i]) { - turnQ.push(Player(2000, static_cast<UnitFaction>(i))); + std::cout << "Faction: " << i << "\n"; + turnQ.push(Player(2000, static_cast<Faction>(i))); } } - Level level(name, width, height, tiles, buildings, units, std::vector<Effect>{}, turnQ); + m_turnQ = turnQ; - level.m_turnQ.front().startTurn(level.m_units, level.m_buildings); - return std::make_shared<Level>(level); + m_turnQ.front().startTurn(m_units, m_buildings); } std::pair<int, int> Level::calcTilePos(int mouseX, int mouseY) @@ -176,7 +167,7 @@ void Level::selectEntity(int x, int y) auto it = m_units.find(m_selectedUnit); if (it != m_units.end()) { - Unit& unit = it->second; + Unit& unit = *it->second; m_unitInfoMenu.setUnit(unit); // Position das Menu rechts neben der ausgewählten Einheit m_unitInfoMenu.update( @@ -197,7 +188,8 @@ int Level::selectUnit(int tileX, int tileY) { for (auto& [id, unit] : m_units) { - if (unit.getXPosition() == tileX && unit.getYPosition() == tileY) + Unit& unitRef = *unit; + if (unitRef.getXPosition() == tileX && unitRef.getYPosition() == tileY) { return id; } @@ -247,7 +239,8 @@ void Level::handleEvent(Engine& engine, SDL_Event& event) handleMovementEvents(engine, event); break; case LevelState::ATTACKING_STATE: - handleAttackingEvents(engine, event); + m_combatEngine->handleAttackingEvents(engine, event, *this); + // handleAttackingEvents(engine, event); break; case LevelState::RECRUITING_STATE: handleRecruitingEvent(engine, event); @@ -280,8 +273,8 @@ std::vector<std::pair<int, int>> Level::calculateMovementRange(Unit& unit) bool isOccupied = false; for (auto& [id, otherUnit] : m_units) { - if (otherUnit.getXPosition() == x && otherUnit.getYPosition() == y && - id != m_selectedUnit) + Unit& unitRef = *otherUnit; + if (unitRef.getXPosition() == x && unitRef.getYPosition() == y && id != m_selectedUnit) { isOccupied = true; break; @@ -326,6 +319,8 @@ int Level::getMoveCost(TileId tileId, MovementType movementType) void Level::render(Engine& engine) { + + float deltaTime = 1.0F / 60.0F; // Tiles for (Tile& tile : m_tiles) { @@ -371,7 +366,10 @@ void Level::render(Engine& engine) // Units for (auto& [id, unit] : m_units) { - unit.render(engine, RENDERING_SCALE); + Unit& unitRef = *unit; + m_physicsEngine->step(deltaTime); + unitRef.update(deltaTime); + unitRef.render(engine, RENDERING_SCALE); } // Effects @@ -388,6 +386,20 @@ void Level::render(Engine& engine) } } + if (m_bullet) + { + m_bullet->update(); + + if (m_bullet->shouldDestroy()) + { + m_bullet.reset(); + } + else + { + m_bullet->render(engine, RENDERING_SCALE); + } + } + // Remove finished effects after iteration for (int id : effects_to_remove) { @@ -433,20 +445,29 @@ Building Level::removeBuilding(int id) return value; } -int Level::addUnit(Unit unit) +void Level::addUnit( + int tileX, int tileY, Faction factionId, UnitTypeId unitTypeId, UnitState unitState, + Config& config) { - m_units.insert({m_id, unit}); - m_id += 1; + auto unit = std::make_unique<Unit>( + m_unitIdIterator, m_physicsEngine->getWorld(), tileX, tileY, factionId, unitTypeId, + unitState, config); - return m_id - 1; + m_units.insert({m_unitIdIterator, std::move(unit)}); + m_unitIdIterator += 1; } -Unit Level::removeUnit(int id) +void Level::removeUnit(int id) { - Unit value = m_units.at(id); - m_units.erase(id); + auto it = m_units.find(id); + if (it != m_units.end()) + { + // Erst das PhysicsBody explizit zerstören + it->second->destroyBody(); - return value; + // Danach die Unit aus der Map entfernen (Unique_ptr wird freigegeben) + m_units.erase(it); + } } int Level::addEffect(Effect effect) @@ -465,8 +486,35 @@ Effect Level::removeEffect(int id) return value; } +void Level::spawnBullet(Unit& attacker, Unit& target) +{ + float startX = attacker.getXPosition() + 0.5F; // Mitte des Tiles + float startY = attacker.getYPosition() + 0.5F; + float targetX = target.getXPosition() + 0.5F; + float targetY = target.getYPosition() + 0.5F; + + float deltaX = targetX - startX; + float deltaY = targetY - startY; + float distance = std::sqrt(deltaX * deltaX + deltaY * deltaY); + + if (distance == 0.0F) + { + return; // Keine Bullet spawnen, wenn Attacker und Ziel gleiche Position haben + } + + // Richtung normalisieren + float velocityX = (deltaX / distance) * .2F; // Geschwindigkeit anpassen + float velocityY = (deltaY / distance) * .2F; + + // Erstelle ein neues Bullet-Objekt mit der neuen `b2World*`-Referenz + m_bullet = std::make_unique<Bullet>( + m_physicsEngine->getWorld(), startX, startY, velocityX, velocityY, target); +} + void Level::changeTurn() { + std::cout << "First player: " << static_cast<int>(m_turnQ.front().getFaction()) + << "Last player: " << static_cast<int>(m_turnQ.back().getFaction()) << "\n"; Player temp = m_turnQ.front(); temp.endTurn(m_units); @@ -477,6 +525,8 @@ void Level::changeTurn() m_turnQ.front().startTurn(m_units, m_buildings); m_currentPos.setMarkerColor(m_turnQ.front().getFaction()); + + // print all players from queue } void Level::handleRecruitingEvent(Engine& engine, SDL_Event& event) @@ -495,18 +545,18 @@ void Level::handleRecruitingEvent(Engine& engine, SDL_Event& event) } if (event.key.keysym.sym == SDLK_RETURN) { - Building& b = m_buildings.at(m_selectedBuilding); - UnitFaction u_faction = static_cast<UnitFaction>(b.getFaction()); - UnitId unit_id = m_recruitingMenu.getSelectedOption(); - int cost = engine.getUnitConfig().getUnitCost(unit_id); + Building& b = m_buildings.at(m_selectedBuilding); + Faction factionId = static_cast<Faction>(b.getFaction()); + UnitTypeId unit_id = m_recruitingMenu.getSelectedOption(); + int cost = engine.getUnitConfig().getUnitCost(unit_id); if (b.check_money(cost, m_turnQ.front().getMoney())) { if (b.check_spawn(m_units)) { - addUnit(Unit( - b.getXPosition(), b.getYPosition(), u_faction, unit_id, UnitState::IDLE, - engine.getUnitConfig())); + addUnit( + b.getXPosition(), b.getYPosition(), factionId, unit_id, UnitState::IDLE, + engine.getUnitConfig()); m_state = LevelState::SELECTING_STATE; m_turnQ.front().spendMoney(cost); m_selectedBuilding = -1; @@ -515,8 +565,7 @@ void Level::handleRecruitingEvent(Engine& engine, SDL_Event& event) else { std::cout << "You dont have enough money, current money: " - << m_turnQ.front().getMoney() << " || needed money: " << cost - << std::endl; + << m_turnQ.front().getMoney() << " || needed money: " << cost << "\n"; } } } @@ -524,59 +573,15 @@ void Level::handleRecruitingEvent(Engine& engine, SDL_Event& event) //*******************helper functions for event Handling************************************* -void Level::handleAttack(std::pair<int, int> tilePos) -{ - int targetedUnit = selectUnit(tilePos.first, tilePos.second); - if (targetedUnit >= 0) - { - if (m_units.at(m_selectedUnit).getFaction() == m_units.at(targetedUnit).getFaction()) - { - std::cout << "You cannot attack your allies!" << std::endl; - return; - } - - Unit& attacking = m_units.at(m_selectedUnit); - Unit& defending = m_units.at(targetedUnit); - - if (m_attackableUnitIds.find(targetedUnit) != m_attackableUnitIds.end()) - { - attacking.attack(defending); - if (attacking.getHealth() <= 0) - { - removeUnit(m_selectedUnit); - } - else - { - attacking.setState(UnitState::UNAVAILABLE); - } - if (defending.getHealth() <= 0) - { - removeUnit(targetedUnit); - } - m_selectedUnit = -1; - m_showAttackableTiles = false; - m_showReachableTiles = false; - m_state = LevelState::SELECTING_STATE; - } - else - { - std::cout << "No target in range clicked!" << std::endl; - } - } - else - { - std::cout << "No valid target clicked" << std::endl; - } -} - void Level::handleMovement(std::pair<int, int> tilePos) { for (auto& [id, unit] : m_units) { - if (unit.getXPosition() == tilePos.first && unit.getYPosition() == tilePos.second) + Unit& unitRef = *unit; + if (unitRef.getXPosition() == tilePos.first && unitRef.getYPosition() == tilePos.second) { // unit already at clicked position (maybe even selected unit) - std::cout << "Unit already at clicked position" << std::endl; + std::cout << "Unit already at clicked position" << "\n"; return; } } @@ -594,7 +599,7 @@ void Level::handleMovement(std::pair<int, int> tilePos) if (isReachable) { - m_units.at(m_selectedUnit).updatePosition(tilePos.first, tilePos.second); + m_units.at(m_selectedUnit)->moveTo(tilePos.first, tilePos.second); m_contextMenu.update( (tilePos.first * 16 + 15) * RENDERING_SCALE, @@ -604,11 +609,11 @@ void Level::handleMovement(std::pair<int, int> tilePos) for (auto& [id, unit] : m_units) { - allUnits.push_back(&unit); + allUnits.push_back(unit.get()); } - std::vector<Unit*> attackableTargets = - m_units.at(m_selectedUnit).getUnitsInRangeWithDamagePotential(allUnits); + std::vector<Unit*> attackableTargets = m_combatEngine->getUnitsInRangeWithDamagePotential( + *m_units.at(m_selectedUnit), allUnits); m_attackableTiles.clear(); m_showAttackableTiles = true; @@ -622,7 +627,7 @@ void Level::handleMovement(std::pair<int, int> tilePos) // Angreifbaren Einheits-ID setzen for (auto& [id, unit] : m_units) { - if (&unit == target) + if (unit.get() == target) { m_attackableUnitIds.insert(id); break; @@ -638,7 +643,7 @@ void Level::handleMovement(std::pair<int, int> tilePos) } else { - std::cout << "Unglültige Bewegunsposition!" << std::endl; + std::cout << "Unglültige Bewegunsposition!" << "\n"; } } @@ -671,26 +676,37 @@ void Level::handleSelectingEvents(Engine& engine, SDL_Event& event) std::pair<int, int> tilePos = m_currentPos.getPosition(); selectEntity( tilePos.first * 16 * RENDERING_SCALE, tilePos.second * 16 * RENDERING_SCALE); + if (m_selectedUnit >= 0 || m_selectedBuilding >= 0) { m_contextMenu.update( (tilePos.first * 16 + 15) * RENDERING_SCALE, (tilePos.second * 16 + 15) * RENDERING_SCALE); + if (m_selectedUnit >= 0) { - m_reachableTiles = calculateMovementRange(m_units.at(m_selectedUnit)); - m_units.at(m_selectedUnit).on_left_click(event); - m_showReachableTiles = true; + Unit& selectedUnit = *m_units.at(m_selectedUnit); + if (selectedUnit.getFaction() == m_turnQ.front().getFaction()) + { + m_reachableTiles = calculateMovementRange(selectedUnit); + m_showReachableTiles = true; + } + else + { + m_showReachableTiles = + false; // Falls es eine gegnerische Unit ist, deaktiviere es + } + m_reachableTiles = calculateMovementRange(*m_units.at(m_selectedUnit)); std::vector<Unit*> allUnits; - for (auto& [id, unit] : m_units) { - allUnits.push_back(&unit); + allUnits.push_back(unit.get()); } std::vector<Unit*> attackableTargets = - m_units.at(m_selectedUnit).getUnitsInRangeWithDamagePotential(allUnits); + m_combatEngine->getUnitsInRangeWithDamagePotential( + *m_units.at(m_selectedUnit), allUnits); m_attackableTiles.clear(); m_showAttackableTiles = true; @@ -698,8 +714,8 @@ void Level::handleSelectingEvents(Engine& engine, SDL_Event& event) // Set Fallback_position if movement will be canceled unit_fallback_position = std::make_pair( - m_units.at(m_selectedUnit).getXPosition(), - m_units.at(m_selectedUnit).getYPosition()); + m_units.at(m_selectedUnit)->getXPosition(), + m_units.at(m_selectedUnit)->getYPosition()); for (Unit* target : attackableTargets) { @@ -710,7 +726,7 @@ void Level::handleSelectingEvents(Engine& engine, SDL_Event& event) // Angreifbaren Einheits-ID setzen for (auto& [id, unit] : m_units) { - if (&unit == target) + if (unit.get() == target) { m_attackableUnitIds.insert(id); break; @@ -718,10 +734,9 @@ void Level::handleSelectingEvents(Engine& engine, SDL_Event& event) } } - Unit& u = m_units.at(m_selectedUnit); - - if (m_units.at(m_selectedUnit).getFaction() == m_turnQ.front().getFaction() && - m_units.at(m_selectedUnit).getState() != UnitState::UNAVAILABLE) + Unit& u = *m_units.at(m_selectedUnit); + if (u.getFaction() == m_turnQ.front().getFaction() && + u.getState() != UnitState::UNAVAILABLE) { m_captureBuilding = -1; for (auto& [id, building] : m_buildings) @@ -729,8 +744,7 @@ void Level::handleSelectingEvents(Engine& engine, SDL_Event& event) if (building.getXPosition() == u.getXPosition() && building.getYPosition() == u.getYPosition()) { - if (building.getFaction() != - static_cast<BuildingFaction>(u.getFaction())) + if (static_cast<Faction>(building.getFaction()) != u.getFaction()) { m_captureBuilding = id; m_contextMenu.setOptions( @@ -754,10 +768,12 @@ void Level::handleSelectingEvents(Engine& engine, SDL_Event& event) } else { - BuildingId b_id = m_buildings.at(m_selectedBuilding).getId(); - BuildingFaction b_faction = m_buildings.at(m_selectedBuilding).getFaction(); + BuildingId b_id = m_buildings.at(m_selectedBuilding).getId(); + Faction b_faction = + static_cast<Faction>(m_buildings.at(m_selectedBuilding).getFaction()); + if (b_id == BuildingId::CITY || b_id == BuildingId::HEADQUARTER || - b_faction == static_cast<BuildingFaction>(5)) + b_faction == static_cast<Faction>(5)) { m_contextMenu.setOptions({"Info", "End Turn"}); } @@ -765,8 +781,7 @@ void Level::handleSelectingEvents(Engine& engine, SDL_Event& event) { // Show according menu options if building has same/different faction than // current player - if (m_buildings.at(m_selectedBuilding).getFaction() == - static_cast<BuildingFaction>(m_turnQ.front().getFaction())) + if (b_faction == m_turnQ.front().getFaction()) { m_contextMenu.setOptions({"Train", "Info", "End Turn"}); } @@ -794,11 +809,11 @@ void Level::handleMenuActiveEvents(Engine& engine, SDL_Event& event) { if (m_selectedUnit > -1 && unit_fallback_position != std::make_pair( - m_units.at(m_selectedUnit).getXPosition(), - m_units.at(m_selectedUnit).getYPosition())) + m_units.at(m_selectedUnit)->getXPosition(), + m_units.at(m_selectedUnit)->getYPosition())) { m_units.at(m_selectedUnit) - .updatePosition(unit_fallback_position.first, unit_fallback_position.second); + ->moveTo(unit_fallback_position.first, unit_fallback_position.second); } m_selectedUnit = -1; m_selectedBuilding = -1; @@ -824,8 +839,9 @@ void Level::handleMenuActiveEvents(Engine& engine, SDL_Event& event) auto it = m_units.find(m_selectedUnit); if (it != m_units.end()) { - it->second.setState(UnitState::UNAVAILABLE); - std::cout << "Unit state set to UNAVAILABLE." << std::endl; + it->second->setState(UnitState::UNAVAILABLE); + it->second->setMoved(true); + std::cout << "Unit state set to UNAVAILABLE." << "\n"; m_state = LevelState::SELECTING_STATE; m_selectedUnit = -1; m_selectedBuilding = -1; @@ -834,7 +850,7 @@ void Level::handleMenuActiveEvents(Engine& engine, SDL_Event& event) } else { - std::cerr << "Selected unit id is invalid: " << m_selectedUnit << std::endl; + std::cerr << "Selected unit id is invalid: " << m_selectedUnit << "\n"; } } if (cmd == "Move") @@ -853,16 +869,15 @@ void Level::handleMenuActiveEvents(Engine& engine, SDL_Event& event) // TODO: Hier Informationen zur Einheit darstellen if (m_selectedUnit > -1) { - Unit& u = m_units.at(m_selectedUnit); - std::cout << "Health: " << u.getHealth() << std::endl; + Unit* u = m_units.at(m_selectedUnit).get(); + std::cout << "Health: " << u->getHealth() << "\n"; m_showUnitInfoMenu = !m_showUnitInfoMenu; } if (m_selectedBuilding > -1) { Building b = m_buildings.at(m_selectedBuilding); std::cout << "Building ID: " << static_cast<int>(b.getId()) << " || " - << "Building Faction: " << static_cast<int>(b.getFaction()) - << std::endl; + << "Building Faction: " << static_cast<int>(b.getFaction()) << "\n"; } } if (cmd == "Train") @@ -873,18 +888,18 @@ void Level::handleMenuActiveEvents(Engine& engine, SDL_Event& event) (tilePos.first * 16 + 15) * RENDERING_SCALE, (tilePos.second * 16 + 15) * RENDERING_SCALE); m_recruitingMenu.setOptions(m_buildings.at(m_selectedBuilding).recruitableUnits()); - std::cout << "no training here" << std::endl; + std::cout << "no training here" << "\n"; } if (cmd == "Capture") { - Building& b = m_buildings.at(m_captureBuilding); - UnitFaction u_f = m_units.at(m_selectedUnit).getFaction(); + Building& b = m_buildings.at(m_captureBuilding); + Faction u_f = m_units.at(m_selectedUnit)->getFaction(); - BuildingFaction b_f = static_cast<BuildingFaction>(u_f); + auto b_f = static_cast<Faction>(u_f); m_gameOver = b.switch_faction(b_f); - m_units.at(m_selectedUnit).setState(UnitState::UNAVAILABLE); + m_units.at(m_selectedUnit)->setState(UnitState::UNAVAILABLE); m_state = LevelState::SELECTING_STATE; m_selectedBuilding = -1; m_selectedUnit = -1; @@ -925,32 +940,6 @@ void Level::handleMovementEvents(Engine& engine, SDL_Event& event) break; } } - -void Level::handleAttackingEvents(Engine& engine, SDL_Event& event) -{ - if (m_attackableUnitIds.empty()) - { - std::cout << "No units are within attack range." << std::endl; - m_state = LevelState::MENUACTIVE_STATE; - return; // Early exit if no units to attack - } - switch (event.type) - { - case SDL_KEYDOWN: - handlePositionMarker(engine, event); - if (event.key.keysym.sym == SDLK_ESCAPE) - { - m_state = LevelState::MENUACTIVE_STATE; - } - if (event.key.keysym.sym == SDLK_RETURN) - { - handleAttack(m_currentPos.getPosition()); - } - break; - default: - break; - } -} //************end event handler delegates for different level // states***************************** diff --git a/src/game/level/Level.hpp b/src/game/level/Level.hpp index 2462564546f2c9e6fba7c1bff423bc2a1d8f7170..ddca6329d4cf5a952b3fec0f1ccf0ce77d0933f6 100644 --- a/src/game/level/Level.hpp +++ b/src/game/level/Level.hpp @@ -1,11 +1,14 @@ #pragma once +#include "../combat/CombatEngine.hpp" #include "../core/Engine.hpp" #include "../core/Scene.hpp" #include "../core/Tile.hpp" #include "../effect/Effect.hpp" #include "../entities/Building.hpp" +#include "../entities/Bullet.hpp" #include "../entities/Unit.hpp" +#include "../physics/PhysicsEngine.hpp" #include "../player/Player.hpp" #include "../ui/TileMarker.hpp" #include "../ui/context/ContextMenu.hpp" @@ -15,6 +18,7 @@ #include <SDL.h> #include <array> +#include <memory> #include <queue> #include <string> #include <unordered_map> @@ -79,12 +83,12 @@ enum class LevelState 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> effects, - std::queue<Player> turnQ); + Level(const std::string& path, Engine& engine); - static std::shared_ptr<Level> loadLevel(const std::string& path, Engine& engine); + // std::shared_ptr<Level> loadLevel( + // std::string name, int width, int height, std::vector<Tile> tiles, + // std::vector<Building> buildings, std::vector<Effect> effects, std::queue<Player> + // turnQ); void render(Engine& engine); @@ -109,9 +113,11 @@ class Level : public Scene Building removeBuilding(int id); - int addUnit(Unit unit); + void addUnit( + int tileX, int tileY, Faction factionId, UnitTypeId unitTypeId, UnitState unitState, + Config& config); - Unit removeUnit(int id); + void removeUnit(int id); int addEffect(Effect effect); @@ -125,34 +131,11 @@ class Level : public Scene std::vector<std::pair<int, int>> m_attackableTiles; - private: - bool m_gameOver; - std::string m_name; - int m_width; - int m_height; - std::vector<Tile> m_tiles; - std::unordered_map<int, Building> m_buildings; - std::unordered_map<int, Unit> m_units; - std::unordered_map<int, Effect> m_effects; - std::queue<Player> m_turnQ; - - int m_selectedUnit; - int m_selectedBuilding; - int m_captureBuilding; - ContextMenu m_contextMenu; - RecruitingMenu m_recruitingMenu; - bool toggle_Helpmenu = false; - HelpMenu m_helpMenu; - int m_id; - LevelState m_state; - std::pair<int, int> calcTilePos(int mouseX, int mouseY); void selectEntity(int x, int y); int selectUnit(int tileX, int tileY); int selectBuilding(int tileX, int tileY); - TileMarker m_currentPos; - void handleSelectingEvents(Engine& engine, SDL_Event& event); void handleMenuActiveEvents(Engine& engine, SDL_Event& event); void handleMovementEvents(Engine& engine, SDL_Event& event); @@ -164,20 +147,58 @@ class Level : public Scene void changeTurn(); - bool m_showReachableTiles; - bool m_showAttackableTiles; - - std::unordered_set<int> m_attackableUnitIds; - void handleAttack(std::pair<int, int> tilePos); void handleMovement(std::pair<int, int> tilePos); void handlePositionMarker(Engine& engine, SDL_Event& event); - UnitInfoMenu m_unitInfoMenu; + std::unordered_map<int, std::unique_ptr<Unit>>& getUnits() { return m_units; } + int getSelectedUnit() const { return m_selectedUnit; } + void setSelectedUnit(int id) { m_selectedUnit = id; } + void setShowAttackableTiles(bool show) { m_showAttackableTiles = show; } + void setShowReachableTiles(bool show) { m_showReachableTiles = show; } + void setState(LevelState state) { m_state = state; } - bool m_showUnitInfoMenu = false; + std::unordered_set<int>& getAttackableUnitIds() { return m_attackableUnitIds; } - std::pair<int, int> unit_fallback_position; + LevelState getState() { return m_state; } + + TileMarker getTilemarker() { return m_currentPos; } + + void spawnBullet(Unit& attacker, Unit& target); + + private: + bool m_gameOver = false; + std::string m_name; + int m_width = 0; + int m_height = 0; + std::vector<Tile> m_tiles; + std::unordered_map<int, Building> m_buildings; + std::unordered_map<int, std::unique_ptr<Unit>> m_units; + int m_unitIdIterator = 5; + // std::unordered_map<int, Unit> m_units; + std::unordered_map<int, Effect> m_effects; + std::unique_ptr<Bullet> m_bullet; + std::queue<Player> m_turnQ; + + int m_selectedUnit; + int m_selectedBuilding; + int m_captureBuilding; + ContextMenu m_contextMenu; + RecruitingMenu m_recruitingMenu; + bool toggle_Helpmenu = false; + HelpMenu m_helpMenu; + int m_id; + LevelState m_state; + TileMarker m_currentPos; + bool m_showReachableTiles = false; + bool m_showAttackableTiles = false; + std::unordered_set<int> m_attackableUnitIds; + UnitInfoMenu m_unitInfoMenu; + bool m_showUnitInfoMenu = false; + std::pair<int, int> unit_fallback_position; + std::unique_ptr<PhysicsEngine> m_physicsEngine; + std::unique_ptr<CombatEngine> m_combatEngine; + Uint32 m_lastFrameTime = 0; }; } // namespace advanced_wars diff --git a/src/game/physics/PhysicsBody.cpp b/src/game/physics/PhysicsBody.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c7b31c34276ee9f3395afd3e129f40656283ba7b --- /dev/null +++ b/src/game/physics/PhysicsBody.cpp @@ -0,0 +1,108 @@ +#include "PhysicsBody.hpp" +#include "../../util/Dimensions.hpp" +#include <box2d/box2d.h> + +namespace advanced_wars +{ + +PhysicsBody::PhysicsBody( + b2World* world, int x, int y, float width, float height, float density, float friction, + bool isSensor, BodyType type) + : m_world(world), m_xMeter(tileToWorld(x)), m_yMeter(tileToWorld(y)), m_targetX(tileToWorld(x)), + m_targetY(tileToWorld(y)), m_width(width), m_height(height), m_density(density), + m_friction(friction), m_isSensor(isSensor), m_type(type) +{ + b2BodyDef bodyDef; + bodyDef.type = b2_dynamicBody; + bodyDef.position.Set(m_xMeter, m_yMeter); + + m_body = m_world->CreateBody(&bodyDef); + b2PolygonShape hitbox; + hitbox.SetAsBox(pixelToWorld(width), pixelToWorld(height)); + + b2FixtureDef fixtureDef; + fixtureDef.shape = &hitbox; + fixtureDef.density = density; + fixtureDef.friction = friction; + fixtureDef.isSensor = isSensor; + + m_fixture = m_body->CreateFixture(&fixtureDef); +} + +void PhysicsBody::update(float deltaTime) +{ + if (!m_body) + { + return; + } + + // Korrekte Mitte des Tiles als Ziel setzen + float worldTargetX = m_targetX + 0.5F; + float worldTargetY = m_targetY + 0.5F; + + b2Vec2 currentPos = getPosition(); + float deltaX = worldTargetX - currentPos.x; + float deltaY = worldTargetY - currentPos.y; + float distance = std::sqrt(deltaX * deltaX + deltaY * deltaY); + + if (distance < 0.01F) // Exakte Prüfung auf Tile-Zentrum + { + // Direkt zentrieren, falls sehr nahe am Ziel + m_body->SetTransform({worldTargetX, worldTargetY}, 0); + m_body->SetLinearVelocity({0, 0}); + return; + } + + // Richtung berechnen (normieren) + float directionX = deltaX / distance; + float directionY = deltaY / distance; + + // Geschwindigkeit setzen + float speed = 1.2F; + b2Vec2 velocity = {directionX * speed, directionY * speed}; + + m_body->SetLinearVelocity(velocity); +} + +void PhysicsBody::setTargetPosition(int targetTileX, int targetTileY) +{ + m_targetX = tileToWorld(targetTileX); + m_targetY = tileToWorld(targetTileY); +} + +b2Vec2 PhysicsBody::getPosition() const +{ + if (m_body) + { + return m_body->GetPosition(); + } + return {0.0F, 0.0F}; +} + +b2Vec2 PhysicsBody::getVelocity() const +{ + if (m_body) + { + return m_body->GetLinearVelocity(); + } + return {0.0F, 0.0F}; +} + +void PhysicsBody::destroy() +{ + if (m_body && m_world) + { + m_world->DestroyBody(m_body); + m_body = nullptr; + } +} + +void PhysicsBody::setVelocity(b2Vec2 velocity) +{ + if (m_body) + { + m_body->SetLinearVelocity(velocity); + } +} + +} // namespace advanced_wars diff --git a/src/game/physics/PhysicsBody.hpp b/src/game/physics/PhysicsBody.hpp new file mode 100644 index 0000000000000000000000000000000000000000..5bb6d69bfa3c47ff22f66de87d23015b6fa6c746 --- /dev/null +++ b/src/game/physics/PhysicsBody.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "box2d/b2_body.h" + +namespace advanced_wars +{ + +enum class BodyType +{ + UNIT, + PROJECTILE +}; + +class PhysicsBody +{ + public: + PhysicsBody( + b2World* world, int x, int y, float width, float height, float density, float friction, + bool isSensor, BodyType type); + void update(float deltaTime); + void setTargetPosition(int targetTileX, int targetTileY); + b2Vec2 getPosition() const; + b2Vec2 getVelocity() const; + void destroy(); + void setVelocity(b2Vec2 velocity); + bool isProjectile() const { return m_type == BodyType::PROJECTILE; } + b2Body* getBody() const { return m_body; } + + private: + b2World* m_world; + b2Body* m_body; + b2Fixture* m_fixture; + float m_xMeter, m_yMeter; + float m_targetX, m_targetY; + float m_width, m_height; + float m_density, m_friction; + bool m_isSensor; + BodyType m_type; +}; + +} // namespace advanced_wars diff --git a/src/game/physics/PhysicsEngine.cpp b/src/game/physics/PhysicsEngine.cpp index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..da8bdb2bed20fb3711af73dcb8df35c9a24bfc19 100644 --- a/src/game/physics/PhysicsEngine.cpp +++ b/src/game/physics/PhysicsEngine.cpp @@ -0,0 +1,83 @@ +#include "PhysicsEngine.hpp" +#include "../entities/Bullet.hpp" +#include "../entities/Unit.hpp" + +namespace advanced_wars +{ + +PhysicsEngine::PhysicsEngine() +{ + b2Vec2 gravity(0.0f, 0.0f); + m_world = new b2World(gravity); + m_world->SetContactListener(this); +} + +PhysicsEngine::~PhysicsEngine() +{ + delete m_world; +} + +void PhysicsEngine::step(float deltaTime) +{ + m_world->Step(deltaTime, 6, 2); +} + +void PhysicsEngine::BeginContact(b2Contact* contact) +{ + + b2Fixture* fixtureA = contact->GetFixtureA(); + b2Fixture* fixtureB = contact->GetFixtureB(); + + void* bodyUserDataA = reinterpret_cast<void*>(fixtureA->GetBody()->GetUserData().pointer); + void* bodyUserDataB = reinterpret_cast<void*>(fixtureB->GetBody()->GetUserData().pointer); + + BodyUserData* budA = reinterpret_cast<BodyUserData*>(bodyUserDataA); + BodyUserData* budB = reinterpret_cast<BodyUserData*>(bodyUserDataB); + + if (!budA || !budB) + { + return; + } + + // **Bullet- und Unit-Identifizierung** + Bullet* bullet = nullptr; + Unit* unit = nullptr; + + if (budA->type == BodyUserData::Type::Bullet) + { + bullet = static_cast<Bullet*>(budA->data); + } + else if (budA->type == BodyUserData::Type::Unit) + { + unit = static_cast<Unit*>(budA->data); + } + + if (budB->type == BodyUserData::Type::Bullet) + { + bullet = static_cast<Bullet*>(budB->data); + } + else if (budB->type == BodyUserData::Type::Unit) + { + unit = static_cast<Unit*>(budB->data); + } + + if (!bullet || !unit) + { + return; + } + int bulletTargetId = bullet->getTarget().getUnitId(); + int unitId = unit->getUnitId(); + + if (unitId != bulletTargetId) + { + return; + } + + // **Bullet soll zerstört werden** + if (bullet) + { + bullet->setDestroyFlag(true); + } +} + +} // namespace advanced_wars diff --git a/src/game/physics/PhysicsEngine.hpp b/src/game/physics/PhysicsEngine.hpp index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1183f738021761ea04d468b32a7af707dfe2cfc1 100644 --- a/src/game/physics/PhysicsEngine.hpp +++ b/src/game/physics/PhysicsEngine.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "PhysicsBody.hpp" +#include "box2d/box2d.h" + +#include <vector> + +namespace advanced_wars +{ + +struct BodyUserData +{ + enum class Type + { + Unit = 0, + Bullet = 1, + }; + Type type; + int id; + void* data; +}; + +class PhysicsEngine : public b2ContactListener +{ + public: + PhysicsEngine(); + ~PhysicsEngine(); + void step(float deltaTime); + b2World* getWorld() { return m_world; } + + void BeginContact(b2Contact* contact) override; + + private: + b2World* m_world; +}; + +} // namespace advanced_wars diff --git a/src/game/player/Player.cpp b/src/game/player/Player.cpp index 4c8c718696f4c2a656c4f7e27f615381f5fdb2b3..84a081edfc0e1eab3a9ac8035d792c67238dce67 100644 --- a/src/game/player/Player.cpp +++ b/src/game/player/Player.cpp @@ -4,7 +4,7 @@ namespace advanced_wars { -Player::Player(int money, UnitFaction faction) +Player::Player(int money, Faction faction) : m_money(money), m_alive(true), m_activeTurn(false), m_faction(faction) { } @@ -12,13 +12,16 @@ Player::Player(int money, UnitFaction faction) Player::~Player() {} void Player::startTurn( - std::unordered_map<int, Unit>& lvUnits, std::unordered_map<int, Building>& lvBuildings) + std::unordered_map<int, std::unique_ptr<Unit>>& lvUnits, + std::unordered_map<int, Building>& lvBuildings) { for (auto& [id, unit] : lvUnits) { - if (unit.getFaction() == m_faction) + Unit& unitRef = *unit; + if (unitRef.getFaction() == m_faction) { - unit.setState(UnitState::IDLE); + unitRef.setState(UnitState::IDLE); + unitRef.setMoved(false); } } @@ -28,32 +31,32 @@ void Player::startTurn( { switch (m_faction) { - case UnitFaction::URED: - if (building.getFaction() == BuildingFaction::URED) + case Faction::URED: + if (building.getFaction() == Faction::URED) { underControl++; } break; - case UnitFaction::UBLUE: - if (building.getFaction() == BuildingFaction::UBLUE) + case Faction::UBLUE: + if (building.getFaction() == Faction::UBLUE) { underControl++; } break; - case UnitFaction::UYELLOW: - if (building.getFaction() == BuildingFaction::UYELLOW) + case Faction::UYELLOW: + if (building.getFaction() == Faction::UYELLOW) { underControl++; } break; - case UnitFaction::UGREEN: - if (building.getFaction() == BuildingFaction::UGREEN) + case Faction::UGREEN: + if (building.getFaction() == Faction::UGREEN) { underControl++; } break; - case UnitFaction::UPURPLE: - if (building.getFaction() == BuildingFaction::UPURPLE) + case Faction::UPURPLE: + if (building.getFaction() == Faction::UPURPLE) { underControl++; } @@ -69,19 +72,22 @@ void Player::startTurn( m_activeTurn = true; } -void Player::endTurn(std::unordered_map<int, Unit>& lvUnits) +void Player::endTurn(std::unordered_map<int, std::unique_ptr<Unit>>& lvUnits) { + std::cout << "Ending turn for player with faction: " << static_cast<int>(m_faction) << "\n"; for (auto& [id, unit] : lvUnits) { - if (unit.getFaction() == m_faction) + Unit& unitRef = *unit; + if (unitRef.getFaction() == m_faction) { - unit.setState(UnitState::UNAVAILABLE); + unitRef.setState(UnitState::UNAVAILABLE); + unitRef.setMoved(true); } } m_activeTurn = false; } -UnitFaction Player::getFaction() +Faction Player::getFaction() { return m_faction; } @@ -95,4 +101,4 @@ void Player::spendMoney(int toSpend) { m_money -= toSpend; } -} // namespace advanced_wars \ No newline at end of file +} // namespace advanced_wars diff --git a/src/game/player/Player.hpp b/src/game/player/Player.hpp index 9a99b5ba688a9d4d65bdeba374b91d1821c0d820..83b0d27aa034d9253ec9ffb833994906c8853a00 100644 --- a/src/game/player/Player.hpp +++ b/src/game/player/Player.hpp @@ -12,7 +12,7 @@ class Player { public: - Player(int money, UnitFaction faction); + Player(int money, Faction faction); ~Player(); /** @@ -23,7 +23,8 @@ class Player * @param lvBuildings All buildings of a level */ void startTurn( - std::unordered_map<int, Unit>& lvUnits, std::unordered_map<int, Building>& lvBuildings); + std::unordered_map<int, std::unique_ptr<Unit>>& lvUnits, + std::unordered_map<int, Building>& lvBuildings); /** * Sets all units of the players faction to unavailable and sets them as no longer being the @@ -31,19 +32,19 @@ class Player * * @param lvUnits All current units of a level */ - void endTurn(std::unordered_map<int, Unit>& lvUnits); + void endTurn(std::unordered_map<int, std::unique_ptr<Unit>>& lvUnits); - UnitFaction getFaction(); + Faction getFaction(); int getMoney(); void spendMoney(int toSpend); private: - int m_money; - bool m_alive; - bool m_activeTurn; - UnitFaction m_faction; + int m_money; + bool m_alive; + bool m_activeTurn; + Faction m_faction; }; } // namespace advanced_wars diff --git a/src/game/ui/TileMarker.cpp b/src/game/ui/TileMarker.cpp index 755b78166cdc3d0b73d3aac2ceb3ee39536ccaf7..e6e6122a75c4b25ec137a85dc34a8f5c3741cb1a 100644 --- a/src/game/ui/TileMarker.cpp +++ b/src/game/ui/TileMarker.cpp @@ -22,6 +22,12 @@ void TileMarker::render(Engine& engine) SDL_RenderFillRect(engine.renderer(), &box); } +void TileMarker::setBounds(int width, int height) +{ + m_levelWidth = width; + m_levelHeight = height; +} + void TileMarker::handleEvent(Engine& engine, SDL_Event& event) { if (event.type == SDL_KEYDOWN) @@ -32,7 +38,7 @@ void TileMarker::handleEvent(Engine& engine, SDL_Event& event) { case SDLK_UP: newY = m_y - 16 * m_renderingScale; - std::cout << "New Y: " << newY << std::endl; + std::cout << "New Y: " << newY << "\n"; if (newY <= 0) { break; @@ -41,7 +47,7 @@ void TileMarker::handleEvent(Engine& engine, SDL_Event& event) break; case SDLK_DOWN: newY = m_y + 16 * m_renderingScale; - std::cout << "New Y: " << newY << std::endl; + std::cout << "New Y: " << newY << "\n"; if (newY >= m_levelHeight) @@ -52,7 +58,7 @@ void TileMarker::handleEvent(Engine& engine, SDL_Event& event) break; case SDLK_RIGHT: newX = m_x + 16 * m_renderingScale; - std::cout << "New X: " << newX << std::endl; + std::cout << "New X: " << newX << "\n"; if (newX >= m_levelWidth) { @@ -62,7 +68,7 @@ void TileMarker::handleEvent(Engine& engine, SDL_Event& event) break; case SDLK_LEFT: newX = m_x - 16 * m_renderingScale; - std::cout << "New X: " << newX << std::endl; + std::cout << "New X: " << newX << "\n"; if (newX < 0) { @@ -90,23 +96,23 @@ void TileMarker::setPosition(int tileX, int tileY) m_y = tileY * 16 * m_renderingScale + (16 * m_renderingScale - m_height); } -void TileMarker::setMarkerColor(UnitFaction faction) +void TileMarker::setMarkerColor(Faction faction) { switch (faction) { - case UnitFaction::URED: + case Faction::URED: m_markerColor = {255, 0, 0, 255}; break; - case UnitFaction::UBLUE: + case Faction::UBLUE: m_markerColor = {0, 0, 255, 255}; break; - case UnitFaction::UGREEN: + case Faction::UGREEN: m_markerColor = {0, 255, 0, 255}; break; - case UnitFaction::UYELLOW: + case Faction::UYELLOW: m_markerColor = {255, 255, 0, 255}; break; - case UnitFaction::UPURPLE: + case Faction::UPURPLE: m_markerColor = {255, 0, 255, 255}; break; default: diff --git a/src/game/ui/TileMarker.hpp b/src/game/ui/TileMarker.hpp index d394c64739bc51f29bc0e5efc353c0b71f6f1ef6..0c06fb54aecce239698f48a800b2651317652f0e 100644 --- a/src/game/ui/TileMarker.hpp +++ b/src/game/ui/TileMarker.hpp @@ -13,6 +13,7 @@ class TileMarker : public Scene { public: TileMarker(int renderingScale, int tileX, int tileY, int levelWidth, int levelHeight); + TileMarker() = default; void render(Engine& engine) override; @@ -22,7 +23,9 @@ class TileMarker : public Scene void setPosition(int x, int y); - void setMarkerColor(UnitFaction faction); + void setMarkerColor(Faction faction); + + void setBounds(int width, int height); private: int m_x; diff --git a/src/game/ui/context/ContextMenu.cpp b/src/game/ui/context/ContextMenu.cpp index fce89e85375e816d68f90af38e0132bd8bf21356..7d350d0e27528cf165eb8a5009b7422b564a8b72 100644 --- a/src/game/ui/context/ContextMenu.cpp +++ b/src/game/ui/context/ContextMenu.cpp @@ -21,7 +21,7 @@ void ContextMenu::render(Engine& engine) if (TTF_Init() == -1) { - std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << "\n"; return; } @@ -37,7 +37,7 @@ void ContextMenu::render(Engine& engine) TTF_Font* font = TTF_OpenFont(fullPath.c_str(), 16); if (!font) { - std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; + std::cerr << "Failed to load font: " << TTF_GetError() << "\n"; return; } diff --git a/src/game/ui/context/RecruitingMenu.cpp b/src/game/ui/context/RecruitingMenu.cpp index b69feaaf9a07adf2993b980e901f9badfd09bca1..6172c4f5f8ededa2659b244ab70f4e201534b7ba 100644 --- a/src/game/ui/context/RecruitingMenu.cpp +++ b/src/game/ui/context/RecruitingMenu.cpp @@ -7,30 +7,30 @@ namespace advanced_wars { RecruitingMenu::RecruitingMenu() : m_selectedOption(0), unitNames({ - { UnitId::INFANTERY, "Infantry"}, - { UnitId::MECHANIZED_INFANTERY, "Bazooka"}, - { UnitId::RECON, "Recon"}, - { UnitId::APC, "APC"}, - { UnitId::ARTILLERY, "Artillery"}, - { UnitId::ANTI_AIR_TANK, "AA Tank"}, - {UnitId::ANTI_AIR_MISSILE_LAUNCHER, "Rocket AA"}, - { UnitId::ROCKET_ARTILLERY, "MLRS"}, - { UnitId::MEDIUM_TANK, "Medium Tank"}, - { UnitId::NEO_TANK, "Neo Tank"}, - { UnitId::HEAVY_TANK, "Heavy Tank"}, - { UnitId::LANDER, "Lander"}, - { UnitId::CRUISER, "Cruiser"}, - { UnitId::SUBMARINE, "Submarine"}, - { UnitId::BATTLESHIP, "Battleship"}, - { UnitId::TRANSPORT_HELICOPTER, "Chinook"}, - { UnitId::BATTLE_HELICOPTER, "Helicopter"}, - { UnitId::FIGHTER, "Fighter"}, - { UnitId::BOMBER, "Bomber"} + { UnitTypeId::INFANTERY, "Infantry"}, + { UnitTypeId::MECHANIZED_INFANTERY, "Bazooka"}, + { UnitTypeId::RECON, "Recon"}, + { UnitTypeId::APC, "APC"}, + { UnitTypeId::ARTILLERY, "Artillery"}, + { UnitTypeId::ANTI_AIR_TANK, "AA Tank"}, + {UnitTypeId::ANTI_AIR_MISSILE_LAUNCHER, "Rocket AA"}, + { UnitTypeId::ROCKET_ARTILLERY, "MLRS"}, + { UnitTypeId::MEDIUM_TANK, "Medium Tank"}, + { UnitTypeId::NEO_TANK, "Neo Tank"}, + { UnitTypeId::HEAVY_TANK, "Heavy Tank"}, + { UnitTypeId::LANDER, "Lander"}, + { UnitTypeId::CRUISER, "Cruiser"}, + { UnitTypeId::SUBMARINE, "Submarine"}, + { UnitTypeId::BATTLESHIP, "Battleship"}, + { UnitTypeId::TRANSPORT_HELICOPTER, "Chinook"}, + { UnitTypeId::BATTLE_HELICOPTER, "Helicopter"}, + { UnitTypeId::FIGHTER, "Fighter"}, + { UnitTypeId::BOMBER, "Bomber"} }) { } -void RecruitingMenu::setOptions(const std::vector<UnitId> recruitableUnits) +void RecruitingMenu::setOptions(const std::vector<UnitTypeId> recruitableUnits) { m_options = recruitableUnits; @@ -44,7 +44,7 @@ void RecruitingMenu::render(Engine& engine) if (TTF_Init() == -1) { - std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << "\n"; return; } @@ -60,7 +60,7 @@ void RecruitingMenu::render(Engine& engine) TTF_Font* font = TTF_OpenFont(fullPath.c_str(), 16); if (!font) { - std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; + std::cerr << "Failed to load font: " << TTF_GetError() << "\n"; return; } @@ -76,7 +76,7 @@ void RecruitingMenu::render(Engine& engine) SDL_SetRenderDrawColor(engine.renderer(), 0, 0, 0, 255); int i = 0; - for (UnitId id : m_options) + for (UnitTypeId id : m_options) { // std::pair<std::string, int> unit_option = unitNames.at(cost2UnitId.at(cost)); if (i == m_selectedOption) @@ -97,7 +97,7 @@ void RecruitingMenu::render(Engine& engine) SDL_RenderCopy(engine.renderer(), textTexture, nullptr, &textRect); SDL_Texture* unit_texture = spritesheet->getUnitTextures() - .at(static_cast<int>(UnitFaction::URED)) + .at(static_cast<int>(Faction::URED)) .at(static_cast<int>(id)) .at(static_cast<int>(UnitState::IDLE)) .first; @@ -154,7 +154,7 @@ void RecruitingMenu::update(int x, int y) this->m_y = y; } -UnitId RecruitingMenu::getSelectedOption() +UnitTypeId RecruitingMenu::getSelectedOption() { return m_selectedId; } diff --git a/src/game/ui/context/RecruitingMenu.hpp b/src/game/ui/context/RecruitingMenu.hpp index a5d988c309d754d98a19605be9255afc7cde775b..b37e60d45e9e57da26b7bc22bb5326ffcde7ffe2 100644 --- a/src/game/ui/context/RecruitingMenu.hpp +++ b/src/game/ui/context/RecruitingMenu.hpp @@ -10,7 +10,7 @@ class RecruitingMenu : public Scene { public: - UnitId getSelectedOption(); + UnitTypeId getSelectedOption(); void handleEvent(Engine& engine, SDL_Event& event); @@ -18,18 +18,18 @@ class RecruitingMenu : public Scene RecruitingMenu(); - void setOptions(const std::vector<UnitId> recruitableUnits); + void setOptions(const std::vector<UnitTypeId> recruitableUnits); void render(Engine& engine) override; private: - size_t m_selectedOption; - std::vector<UnitId> m_options; - int m_x; - int m_y; - const std::unordered_map<UnitId, std::string> unitNames; - std::unordered_map<int, UnitId> cost2UnitId; - UnitId m_selectedId; + size_t m_selectedOption; + std::vector<UnitTypeId> m_options; + int m_x; + int m_y; + const std::unordered_map<UnitTypeId, std::string> unitNames; + std::unordered_map<int, UnitTypeId> cost2UnitId; + UnitTypeId m_selectedId; void selectSprite(); }; diff --git a/src/game/ui/menu/EndScreen.cpp b/src/game/ui/menu/EndScreen.cpp index 5c3f7a33f66f06f2eaa9f6178d359b7464598c6f..ad33b22394cbca09522aa69ff75c8114c2eb76eb 100644 --- a/src/game/ui/menu/EndScreen.cpp +++ b/src/game/ui/menu/EndScreen.cpp @@ -8,26 +8,26 @@ namespace advanced_wars { Endscreen::Endscreen(Player& player) : m_moenyLeft(player.getMoney()) { - std::cout << "Player faction: " << static_cast<int>(player.getFaction()) << std::endl; + std::cout << "Player faction: " << static_cast<int>(player.getFaction()) << "\n"; switch (player.getFaction()) { - case UnitFaction::UBLUE: + case Faction::UBLUE: m_color = {0, 0, 255, 255}; m_playerString = "Blue"; break; - case UnitFaction::UGREEN: + case Faction::UGREEN: m_color = {0, 255, 0, 255}; m_playerString = "Green"; break; - case UnitFaction::UPURPLE: + case Faction::UPURPLE: m_color = {255, 0, 255, 255}; m_playerString = "Purple"; break; - case UnitFaction::URED: + case Faction::URED: m_color = {255, 0, 0, 255}; m_playerString = "Red"; break; - case UnitFaction::UYELLOW: + case Faction::UYELLOW: m_color = {255, 255, 0, 255}; m_playerString = "Yellow"; break; @@ -44,7 +44,7 @@ void Endscreen::render(Engine& engine) if (TTF_Init() == -1) { - std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << "\n"; return; } // Draw Background @@ -54,7 +54,7 @@ void Endscreen::render(Engine& engine) SDL_Surface* backgroundSurface = IMG_Load(fullPath.c_str()); if (!backgroundSurface) { - std::cerr << "Failed to load background image: " << IMG_GetError() << std::endl; + std::cerr << "Failed to load background image: " << IMG_GetError() << "\n"; return; } diff --git a/src/game/ui/menu/Menu.cpp b/src/game/ui/menu/Menu.cpp index 3efb242b4547040f7a544be4760f67b49e090b8b..efdbb448af97afa3229f58c6a15564fed1636992 100644 --- a/src/game/ui/menu/Menu.cpp +++ b/src/game/ui/menu/Menu.cpp @@ -33,10 +33,12 @@ void Menu::render(Engine& engine) { if (TTF_Init() == -1) { - std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << "\n"; return; } + int windowWidth = engine.getWindow().w(); + if (m_backgroundTexture) { SDL_RenderCopy(engine.renderer(), m_backgroundTexture, nullptr, nullptr); @@ -53,7 +55,7 @@ void Menu::render(Engine& engine) TTF_Font* titleFont = TTF_OpenFont(fullPath.c_str(), 48); if (!titleFont) { - std::cerr << "Failed to load title font: " << fullPath << TTF_GetError() << std::endl; + std::cerr << "Failed to load title font: " << fullPath << TTF_GetError() << "\n"; return; } @@ -61,7 +63,7 @@ void Menu::render(Engine& engine) if (!menuFont) { TTF_CloseFont(titleFont); - std::cerr << "Failed to load menu font: " << fullPath << TTF_GetError() << std::endl; + std::cerr << "Failed to load menu font: " << fullPath << TTF_GetError() << "\n"; return; } @@ -73,7 +75,8 @@ void Menu::render(Engine& engine) { SDL_Texture* titleTexture = SDL_CreateTextureFromSurface(engine.renderer(), titleSurface); SDL_Rect titleRect = { - static_cast<int>((800 - titleSurface->w) / 2), 50, titleSurface->w, titleSurface->h}; + static_cast<int>((windowWidth - titleSurface->w) / 2), 50, titleSurface->w, + titleSurface->h}; SDL_RenderCopy(engine.renderer(), titleTexture, nullptr, &titleRect); SDL_DestroyTexture(titleTexture); SDL_FreeSurface(titleSurface); @@ -87,13 +90,11 @@ void Menu::render(Engine& engine) { 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), + static_cast<int>((windowWidth - 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); } @@ -120,93 +121,17 @@ void Menu::handleEvent(Engine& engine, SDL_Event& event) { if (m_options[m_selectedOption] == "Exit") { - std::cout << "Exiting game..." << std::endl; + std::cout << "Exiting game..." << "\n"; engine.exit(); } else if (m_options[m_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)); - } - } - - Config config = Config("../config.xml"); - // 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), config)); - } - } - - 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, std::queue<Player>{}); - - engine.pushScene(Level::loadLevel(m_level_filepath, engine)); + std::cout << "Starting game..." << "\n"; + engine.startGame(m_level_filepath); } else if (m_options[m_selectedOption] == "Options") { - std::cout << "Opening options..." << std::endl; + std::cout << "Opening options..." << "\n"; } } } @@ -218,7 +143,7 @@ void Menu::loadBackground(Engine& engine, const std::string& imagePath) SDL_Surface* backgroundSurface = IMG_Load(imagePath.c_str()); if (!backgroundSurface) { - std::cerr << "Failed to load background image: " << IMG_GetError() << std::endl; + std::cerr << "Failed to load background image: " << IMG_GetError() << "\n"; return; } @@ -230,7 +155,7 @@ void Menu::loadBackground(Engine& engine, const std::string& imagePath) if (!m_backgroundTexture) { - std::cerr << "Failed to create background texture: " << SDL_GetError() << std::endl; + std::cerr << "Failed to create background texture: " << SDL_GetError() << "\n"; } } diff --git a/src/game/ui/menu/PauseMenu.cpp b/src/game/ui/menu/PauseMenu.cpp index 026d206214acc6eb8824f00ace595be0152e196c..3bf5679dd17b81be64b301e9658834bc98ddd507 100644 --- a/src/game/ui/menu/PauseMenu.cpp +++ b/src/game/ui/menu/PauseMenu.cpp @@ -14,7 +14,7 @@ PauseMenu::PauseMenu(int selectedOption, SDL_Texture* backgroundTexture) // Initialize SDL_ttf if (TTF_Init() == -1) { - std::cerr << "Failed to initialize SDL_ttf: " << TTF_GetError() << std::endl; + std::cerr << "Failed to initialize SDL_ttf: " << TTF_GetError() << "\n"; } if (!m_backgroundTexture) @@ -37,7 +37,7 @@ void PauseMenu::render(Engine& engine) { if (TTF_Init() == -1) { - std::cerr << "Failed to initialize SDL_ttf: " << TTF_GetError() << std::endl; + std::cerr << "Failed to initialize SDL_ttf: " << TTF_GetError() << "\n"; } SDL_Renderer* renderer = engine.renderer(); @@ -59,7 +59,7 @@ void PauseMenu::render(Engine& engine) TTF_Font* font = TTF_OpenFont(fullPath.c_str(), 24); if (!font) { - std::cerr << "Failed to load menu font: " << fullPath << " " << TTF_GetError() << std::endl; + std::cerr << "Failed to load menu font: " << fullPath << " " << TTF_GetError() << "\n"; return; } @@ -96,7 +96,7 @@ void PauseMenu::handleEvent(Engine& engine, SDL_Event& event) } else if (event.key.keysym.sym == SDLK_ESCAPE) { - std::cout << "Resuming game..." << std::endl; + std::cout << "Resuming game..." << "\n"; engine.popScene(); } else if (event.key.keysym.sym == SDLK_RETURN) @@ -104,13 +104,13 @@ void PauseMenu::handleEvent(Engine& engine, SDL_Event& event) if (m_options[m_selectedOption] == "Exit") { // exit into main menu - std::cout << "Exiting game..." << std::endl; + std::cout << "Exiting game..." << "\n"; engine.returnToMenu(); } else if (m_options[m_selectedOption] == "Resume") { // resume game - std::cout << "Resuming game..." << std::endl; + std::cout << "Resuming game..." << "\n"; engine.popScene(); } } @@ -123,7 +123,7 @@ void PauseMenu::loadBackground(Engine& engine, const std::string& imagePath) SDL_Surface* surface = IMG_Load(imagePath.c_str()); if (!surface) { - std::cerr << "Failed to load image: " << IMG_GetError() << std::endl; + std::cerr << "Failed to load image: " << IMG_GetError() << "\n"; return; } m_backgroundTexture = SDL_CreateTextureFromSurface(engine.renderer(), surface); diff --git a/src/game/ui/modals/HelpMenu.cpp b/src/game/ui/modals/HelpMenu.cpp index 9902bb50bba1b0559638f76f2648bf1772ce4eba..dc62236b4510821e51a636afe5c0f9a9f877a4c8 100644 --- a/src/game/ui/modals/HelpMenu.cpp +++ b/src/game/ui/modals/HelpMenu.cpp @@ -33,7 +33,7 @@ void HelpMenu::render(advanced_wars::Engine& engine) { if (TTF_Init() == -1) { - std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << "\n"; return; } @@ -43,7 +43,7 @@ void HelpMenu::render(advanced_wars::Engine& engine) TTF_Font* font = TTF_OpenFont(fullPath.c_str(), 16); if (!font) { - std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; + std::cerr << "Failed to load font: " << TTF_GetError() << "\n"; return; } diff --git a/src/game/ui/modals/UnitInfoMenu.cpp b/src/game/ui/modals/UnitInfoMenu.cpp index db84ebfe6cd23bdd23b6ef1572a000a1dce8b073..3fc2284b143d7b1fafe00e6319529489d2a44cc9 100644 --- a/src/game/ui/modals/UnitInfoMenu.cpp +++ b/src/game/ui/modals/UnitInfoMenu.cpp @@ -31,30 +31,33 @@ std::string UnitInfoMenu::getMovementTypeString(MovementType type) } } -std::unordered_map<UnitId, std::string> unitDescriptions = { - { UnitId::INFANTERY,"Infanterie Kostenguenstig und vielseitig einsetzbar" }, - { UnitId::MECHANIZED_INFANTERY, - "Mech-Infanterie Stark gegen Panzer langsam aber effizient" }, - { UnitId::RECON, "Aufklaerung Schnell und ideal für frühe Aufklaerung"}, - { UnitId::MEDIUM_TANK, "Mittlerer Panzer Guter Allrounder stark und ausgewogen"}, - { UnitId::HEAVY_TANK, "Schwerer Panzer Langsam aber sehr stark und beschützend"}, - { UnitId::NEO_TANK, "Neo Tank Einer der besten Panzer stark und vielseitig"}, - { UnitId::APC, "Transporter Traeger fuer Infanterie keine Offensivkraefte"}, - { UnitId::ANTI_AIR_TANK, "FlugabwehrPanzer Ideal zur Luftverteidigung"}, - { UnitId::ARTILLERY, "Artillerie Kann aus Distanz zufuegen aber verletzbar im Nahkampf"}, - { UnitId::ROCKET_ARTILLERY, - "Raketenartillerie Grosse Reichweite ideal fuer defensive Taktiken" }, - {UnitId::ANTI_AIR_MISSILE_LAUNCHER, - "Raketenwerfer Kann Flugeinheiten auf grosse Distanz angreifen" }, - { UnitId::FIGHTER, "Jaeger Ideal fuer Luftueberlegenheit"}, - { UnitId::BOMBER, - "Bomber Stark gegen Boden- und Seeziele aber verletzbar gegen Luft-und Flak" }, - { UnitId::BATTLE_HELICOPTER, "Kampfhubschrauber Stark gegen Bodenfahrzeuge und Infanterie"}, - { UnitId::TRANSPORT_HELICOPTER, "Transporthubschrauber Kann Einheiten schnell transportieren"}, - { UnitId::BATTLESHIP, "Schlachtschiff Langreichweitenangriff auf See und Land"}, - { UnitId::CRUISER, "Kreuzer Verteidigung gegen Luft und U-Boot-Einheiten"}, - { UnitId::LANDER, "Landungsschiff Transport und Versorgung"}, - { UnitId::SUBMARINE, "U-Boot Versteckt sich und kann Ueberwasserziele angreifen"} +std::unordered_map<UnitTypeId, std::string> unitDescriptions = { + { UnitTypeId::INFANTERY,"Infanterie Kostenguenstig und vielseitig einsetzbar" }, + { UnitTypeId::MECHANIZED_INFANTERY, + "Mech-Infanterie Stark gegen Panzer langsam aber effizient" }, + { UnitTypeId::RECON, "Aufklaerung Schnell und ideal für frühe Aufklaerung"}, + { UnitTypeId::MEDIUM_TANK, "Mittlerer Panzer Guter Allrounder stark und ausgewogen"}, + { UnitTypeId::HEAVY_TANK, + "Schwerer Panzer Langsam aber sehr stark und beschützend" }, + { UnitTypeId::NEO_TANK, "Neo Tank Einer der besten Panzer stark und vielseitig"}, + { UnitTypeId::APC, "Transporter Traeger fuer Infanterie keine Offensivkraefte"}, + { UnitTypeId::ANTI_AIR_TANK, "FlugabwehrPanzer Ideal zur Luftverteidigung"}, + { UnitTypeId::ARTILLERY, "Artillerie Kann aus Distanz zufuegen aber verletzbar im Nahkampf"}, + { UnitTypeId::ROCKET_ARTILLERY, + "Raketenartillerie Grosse Reichweite ideal fuer defensive Taktiken" }, + {UnitTypeId::ANTI_AIR_MISSILE_LAUNCHER, + "Raketenwerfer Kann Flugeinheiten auf grosse Distanz angreifen" }, + { UnitTypeId::FIGHTER, "Jaeger Ideal fuer Luftueberlegenheit"}, + { UnitTypeId::BOMBER, + "Bomber Stark gegen Boden- und Seeziele aber verletzbar gegen Luft-und Flak" }, + { UnitTypeId::BATTLE_HELICOPTER, + "Kampfhubschrauber Stark gegen Bodenfahrzeuge und Infanterie" }, + { UnitTypeId::TRANSPORT_HELICOPTER, + "Transporthubschrauber Kann Einheiten schnell transportieren" }, + { UnitTypeId::BATTLESHIP, "Schlachtschiff Langreichweitenangriff auf See und Land"}, + { UnitTypeId::CRUISER, "Kreuzer Verteidigung gegen Luft und U-Boot-Einheiten"}, + { UnitTypeId::LANDER, "Landungsschiff Transport und Versorgung"}, + { UnitTypeId::SUBMARINE, "U-Boot Versteckt sich und kann Ueberwasserziele angreifen"} }; void UnitInfoMenu::handleEvent(Engine& engine, SDL_Event& event) @@ -78,7 +81,7 @@ void UnitInfoMenu::render(Engine& engine) // TTF Initialisierung if (TTF_Init() == -1) { - std::cerr << "TTF konnte nicht initialisiert werden: " << TTF_GetError() << std::endl; + std::cerr << "TTF konnte nicht initialisiert werden: " << TTF_GetError() << "\n"; return; } @@ -88,13 +91,13 @@ void UnitInfoMenu::render(Engine& engine) if (!font) { - std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; + std::cerr << "Failed to load font: " << TTF_GetError() << "\n"; return; } - SDL_Color yellow = {255, 255, 0, 255}; // Gelb für den Text - int spacing = 10; // Abstand zwischen den Textzeilen - UnitId unitId = m_currentUnit->getId(); + SDL_Color yellow = {255, 255, 0, 255}; // Gelb für den Text + int spacing = 10; // Abstand zwischen den Textzeilen + UnitTypeId unitId = m_currentUnit->getUnitTypeId(); // Textzeilen, einschließlich der Beschreibung std::vector<std::string> info_lines = { diff --git a/src/util/Dimensions.hpp b/src/util/Dimensions.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ffc679de95fd1d5722d5d2a1bcd78abf50db62fb --- /dev/null +++ b/src/util/Dimensions.hpp @@ -0,0 +1,42 @@ +namespace advanced_wars +{ + +constexpr float PIXELS_PER_METER = 16.0f; + +/// Tile → Pixel +inline int tileToPixel(int tile) +{ + return tile * 16; +} + +/// Pixel → Tile +inline int pixelToTile(int pixel) +{ + return pixel / 16; +} + +/// Box2D (Meter) → Pixel +inline float worldToPixel(float world) +{ + return world * PIXELS_PER_METER; +} + +/// Pixel → Box2D (Meter) +inline float pixelToWorld(float pixel) +{ + return pixel / PIXELS_PER_METER; +} + +/// Tile → Box2D (Meter) (Oberer linker Punkt des Tiles) +inline float tileToWorld(int tile) +{ + return pixelToWorld(tileToPixel(tile)); +} + +/// Box2D (Meter) → Tile (Berechnung über Pixel) +inline int worldToTile(float world) +{ + return pixelToTile(worldToPixel(world)); +} + +} // namespace advanced_wars