diff --git a/src/game/Level.cpp b/src/game/Level.cpp index df984b4e24bd6be17aeec86df814171e26912b49..9f6a4dfc3379abf5f1a7d19e0ff71af0997b5432 100644 --- a/src/game/Level.cpp +++ b/src/game/Level.cpp @@ -24,7 +24,8 @@ Level::Level( std::vector<Building> buildings, std::vector<Unit> units, std::vector<Effect> effects) : 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_state(LevelState::SELECTING_STATE), + m_currentPos(TileMarker(RENDERING_SCALE, 1, 1, m_width, m_height)) { m_contextMenu.setOptions({"Move", "Info", "Wait"}); @@ -218,6 +219,12 @@ void Level::render(Engine& engine) { m_contextMenu.render(engine); } + + if (m_state == LevelState::RECRUITING_STATE) + { + m_recruitingMenu.render(engine); + } + m_currentPos.render(engine); } int Level::addBuilding(Building building) @@ -270,31 +277,128 @@ Effect Level::removeEffect(int id) void Level::handleRecruitingEvent(Engine& engine, SDL_Event& event) { - Building& b = m_buildings.at(m_selectedBuilding); - UnitFaction u_faction = static_cast<UnitFaction> (b.m_faction); - - //show appropriate interface -> provide vector of UnitId - //select UnitId - UnitId u_id = UnitId::INFANTERY; + switch (event.type) + { + case SDL_KEYDOWN: + if (event.key.keysym.sym == SDLK_ESCAPE) + { + m_state = LevelState::MENUACTIVE_STATE; + } + if (event.key.keysym.sym == SDLK_UP || event.key.keysym.sym == SDLK_DOWN) + { + m_recruitingMenu.handleEvent(engine, event); + } + if (event.key.keysym.sym == SDLK_RETURN) + { + Building& b = m_buildings.at(m_selectedBuilding); + UnitFaction u_faction = static_cast<UnitFaction> (b.m_faction); + UnitId unit_id = m_recruitingMenu.getSelectedOption(); + + if(b.check_money(500)) { + if(b.check_spawn(m_units)){ + addUnit(Unit(b.m_x, b.m_y, u_faction, unit_id, UnitState::IDLE)); + m_state = LevelState::SELECTING_STATE; + m_selectedBuilding = -1; + } + } + } +}} - if(b.check_money(500)) { - if(b.check_spawn(m_units)){ - addUnit(Unit(b.m_x, b.m_y, u_faction, u_id, UnitState::IDLE)); - m_state = LevelState::SELECTING_STATE; - m_selectedBuilding = -1; +//*******************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); + attacking.attack(defending); + if (attacking.m_health <= 0) + { + removeUnit(m_selectedUnit); + } + if (defending.m_health <= 0) + { + removeUnit(targetedUnit); + } + m_selectedUnit = -1; + m_state = LevelState::SELECTING_STATE; + } + 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.m_x == tilePos.first && unit.m_y == tilePos.second) + { + // unit already at clicked position (maybe even selected unit) + std::cout << "Unit already at clicked position" << std::endl; + return; + } + } + m_units.at(m_selectedUnit).updatePosition(tilePos.first, tilePos.second); + m_selectedUnit = -1; + m_state = LevelState::SELECTING_STATE; +} + +void Level::handlePositionMarker(Engine& engine, SDL_Event& event) +{ + if (event.key.keysym.sym == SDLK_UP || event.key.keysym.sym == SDLK_DOWN || + event.key.keysym.sym == SDLK_LEFT || event.key.keysym.sym == SDLK_RIGHT) + { + m_currentPos.handleEvent(engine, event); + } +} + +//*******************end helper functions for event Handling********************************* + +//************event handler delegates for different level states***************************** + void Level::handleSelectingEvents(Engine& engine, SDL_Event& event) { switch (event.type) { case SDL_KEYDOWN: + handlePositionMarker(engine, event); if (event.key.keysym.sym == SDLK_ESCAPE) { engine.pushScene(std::make_shared<PauseMenu>(0, nullptr)); } + + if (event.key.keysym.sym == SDLK_RETURN) + { + + 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_contextMenu.setOptions({"Move", "Attack", "Info", "Wait"}); + } + else + { + m_contextMenu.setOptions({"Train", "Info", "Wait"}); + } + m_state = LevelState::MENUACTIVE_STATE; + } + } break; case SDL_MOUSEBUTTONDOWN: if (event.button.button == SDL_BUTTON_LEFT) @@ -303,6 +407,7 @@ void Level::handleSelectingEvents(Engine& engine, SDL_Event& event) if (m_selectedUnit >= 0 || m_selectedBuilding >= 0) { std::pair<int, int> tilePos = calcTilePos(event.button.x, event.button.y); + m_currentPos.setPosition(tilePos.first, tilePos.second); m_contextMenu.update( (tilePos.first * 16 + 15) * RENDERING_SCALE, (tilePos.second * 16 + 15) * RENDERING_SCALE); @@ -375,7 +480,23 @@ void Level::handleMenuActiveEvents(Engine& engine, SDL_Event& event) if (cmd == "Train") { m_state = LevelState::RECRUITING_STATE; - + std::pair<int, int> tilePos = m_currentPos.getPosition(); + m_recruitingMenu.update( + (tilePos.first * 16 + 15) * RENDERING_SCALE, + (tilePos.second * 16 + 15) * RENDERING_SCALE); + m_recruitingMenu.setOptions({ + 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}); + std::cout << "no training here" << std::endl; } } @@ -390,8 +511,10 @@ void Level::handleMovementEvents(Engine& engine, SDL_Event& event) switch (event.type) { case SDL_KEYDOWN: + handlePositionMarker(engine, event); if (event.key.keysym.sym == SDLK_RETURN) { + handleMovement(m_currentPos.getPosition()); } if (event.key.keysym.sym == SDLK_ESCAPE) { @@ -403,18 +526,8 @@ void Level::handleMovementEvents(Engine& engine, SDL_Event& event) { // Bei Movement animation in ANIMATING_STATE gehen std::pair<int, int> tilePos = calcTilePos(event.button.x, event.button.y); - for (auto& [id, unit] : m_units) - { - if (unit.m_x == tilePos.first && unit.m_y == tilePos.second) - { - // unit already at clicked position (maybe even selected unit) - std::cout << "Unit already at clicked position" << std::endl; - return; - } - } - m_units.at(m_selectedUnit).updatePosition(tilePos.first, tilePos.second); - m_selectedUnit = -1; - m_state = LevelState::SELECTING_STATE; + m_currentPos.setPosition(tilePos.first, tilePos.second); + handleMovement(tilePos); } break; default: @@ -427,49 +540,27 @@ void Level::handleAttackingEvents(Engine& engine, SDL_Event& event) 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; case SDL_MOUSEBUTTONDOWN: if (event.button.button == SDL_BUTTON_LEFT) { std::pair<int, int> tilePos = calcTilePos(event.button.x, event.button.y); - 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); - attacking.attack(defending); - if (attacking.m_health <= 0) - { - removeUnit(m_selectedUnit); - } - if (defending.m_health <= 0) - { - removeUnit(targetedUnit); - } - m_selectedUnit = -1; - m_state = LevelState::SELECTING_STATE; - } - else - { - std::cout << "No valid target clicked" << std::endl; - } + m_currentPos.setPosition(tilePos.first, tilePos.second); + handleAttack(tilePos); } break; default: break; } } - +//************end event handler delegates for different level states***************************** } // namespace advanced_wars diff --git a/src/game/Level.hpp b/src/game/Level.hpp index e8ba8a43528561ce5ee3854bdb3e875401401160..fa417a22c63146f431cd0016a2b38219b0784149 100644 --- a/src/game/Level.hpp +++ b/src/game/Level.hpp @@ -7,6 +7,8 @@ #include "Tile.hpp" #include "Unit.hpp" #include "ui/Contextmenu.hpp" +#include "ui/TileMarker.hpp" +#include "ui/Recruitingmenu.hpp" #include <SDL.h> #include <string> #include <unordered_map> @@ -22,7 +24,7 @@ enum class LevelState ANIMATING_STATE, MENUACTIVE_STATE, ATTACKING_STATE, - RECRUITING_STATE + RECRUITING_STATE, }; /** @@ -79,6 +81,7 @@ class Level : public Scene int m_selectedUnit; int m_selectedBuilding; ContextMenu m_contextMenu; + RecruitingMenu m_recruitingMenu; int m_id; LevelState m_state; @@ -87,11 +90,17 @@ class Level : public Scene 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); void handleAttackingEvents(Engine& engine, SDL_Event& event); void handleRecruitingEvent(Engine& engine, SDL_Event& event); + + void handleAttack(std::pair<int, int> tilePos); + void handleMovement(std::pair<int, int> tilePos); + void handlePositionMarker(Engine& engine, SDL_Event& event); }; } // namespace advanced_wars diff --git a/src/game/ui/Recruitingmenu.cpp b/src/game/ui/Recruitingmenu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1ca6289adddee22ad95bbbac612e0a434e19ae98 --- /dev/null +++ b/src/game/ui/Recruitingmenu.cpp @@ -0,0 +1,179 @@ +#include "Recruitingmenu.hpp" +#include <iostream> +#include <SDL_ttf.h> + +namespace advanced_wars +{ + RecruitingMenu::RecruitingMenu() : m_selectedOption(0), unitNames({ + {UnitId::INFANTERY, {"Infantry", 100}}, + {UnitId::MECHANIZED_INFANTERY, {"Bazooka", 200}}, + {UnitId::RECON, {"Recon", 300}}, + {UnitId::APC, {"APC", 400}}, + {UnitId::ARTILLERY, {"Artillery", 500}}, + {UnitId::ANTI_AIR_TANK, {"AA Tank", 600}}, + {UnitId::ANTI_AIR_MISSILE_LAUNCHER, {"Rocket AA", 700}}, + {UnitId::ROCKET_ARTILLERY, {"MLRS", 800}}, + {UnitId::MEDIUM_TANK, {"Medium Tank", 900}}, + {UnitId::NEO_TANK, {"Neo Tank", 1000}}, + {UnitId::HEAVY_TANK, {"Heavy Tank", 1100}}, + {UnitId::LANDER, {"Lander", 1200}}, + {UnitId::CRUISER, {"Cruiser", 1300}}, + {UnitId::SUBMARINE, {"Submarine", 1400}}, + {UnitId::BATTLESHIP, {"Battleship", 1500}}, + {UnitId::TRANSPORT_HELICOPTER, {"Chinook", 1600}}, + {UnitId::BATTLE_HELICOPTER, {"Helicopter", 1700}}, + {UnitId::FIGHTER, {"Fighter", 1800}}, + {UnitId::BOMBER, {"Bomber", 1900}} + }) { + + } + + void RecruitingMenu::setOptions(const std::vector<UnitId> recruitableUnits) { + + std::vector<std::pair<std::string, int>> options; + + for (UnitId id : recruitableUnits) { + options.push_back(unitNames.at(id)); + cost2UnitId.insert(std::make_pair(unitNames.at(id).second, id)); + + } + + m_options = options; + m_selectedOption = 0; + } + + void RecruitingMenu::render(Engine& engine) +{ + + Spritesheet* spritesheet = engine.getSpritesheet(); + + if (TTF_Init() == -1) + { + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + return; + } + + if (m_options.empty()) + { + // TODO handle somehow + return; + } + + std::string basePath = SDL_GetBasePath(); + std::string relativePath = "res/ARCADECLASSIC.TTF"; + std::string fullPath = basePath + relativePath; + TTF_Font* font = TTF_OpenFont(fullPath.c_str(), 16); + if (!font) + { + std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; + return; + } + + SDL_Color white = {255, 255, 255, 255}; + SDL_Color yellow = {192, 255, 0, 255}; + + int spacing = 20; // Abstand zwischen den Optionen + // box around options + SDL_SetRenderDrawColor(engine.renderer(), 0, 0, 255, 255); + SDL_Rect box = {m_x, m_y - 3, 150, static_cast<int>(m_options.size() * spacing)}; + SDL_RenderFillRect(engine.renderer(), &box); + + SDL_SetRenderDrawColor(engine.renderer(), 0, 0, 0, 255); + int i = 0; + + for (auto& [render_name, cost] : m_options) + { + //std::pair<std::string, int> unit_option = unitNames.at(cost2UnitId.at(cost)); + if(i == m_selectedOption) { + m_selectedId = cost2UnitId.at(cost); + } + + SDL_Surface* textSurface = TTF_RenderText_Solid( + font, render_name.c_str(), (i == m_selectedOption) ? yellow : white); + if (!textSurface) + { + continue; + } + + SDL_Texture* textTexture = SDL_CreateTextureFromSurface(engine.renderer(), textSurface); + SDL_Rect textRect = { + m_x + 10 + 16, m_y + static_cast<int>(i * spacing), textSurface->w, textSurface->h}; + SDL_RenderCopy(engine.renderer(), textTexture, nullptr, &textRect); + + + SDL_Texture* unit_texture = spritesheet->getUnitTextures() + .at(static_cast<int>(UnitFaction::URED)) + .at(static_cast<int>(cost2UnitId.at(cost))) + .at(static_cast<int>(UnitState::IDLE)) + .first; + + SDL_Rect trgt_rect = { + m_x + 5, + m_y + static_cast<int>(i * spacing), + 16, + 16 + }; + + SDL_Rect src_rect = { + 5, + 0, + 10, + 10 + }; + + SDL_RenderCopy(engine.renderer(), unit_texture, &src_rect, &trgt_rect); + + SDL_Surface* costSurface = TTF_RenderText_Solid( + font, std::to_string(cost).c_str(), (i == m_selectedOption) ? yellow : white); + if (!textSurface) + { + continue; + } + + SDL_Texture* costTexture = SDL_CreateTextureFromSurface(engine.renderer(), costSurface); + + SDL_Rect cost_rect { + m_x + 120 , + m_y + static_cast<int>(i * spacing), + costSurface->w, + costSurface->h + }; + SDL_RenderCopy(engine.renderer(), costTexture, nullptr, &cost_rect); + + SDL_DestroyTexture(costTexture); + SDL_FreeSurface(costSurface); + SDL_DestroyTexture(textTexture); + SDL_FreeSurface(textSurface); + i++; + } + + TTF_CloseFont(font); + TTF_Quit(); +} + +void RecruitingMenu::handleEvent(Engine& engine, SDL_Event& event) +{ + if (event.type == SDL_KEYDOWN) + { + if (event.key.keysym.sym == SDLK_DOWN) + { + m_selectedOption = (m_selectedOption + 1) % m_options.size(); + } + else if (event.key.keysym.sym == SDLK_UP) + { + m_selectedOption = (m_selectedOption - 1 + m_options.size()) % m_options.size(); + } + } +} + +void RecruitingMenu::update(int x, int y) +{ + this->m_x = x; + this->m_y = y; +} + +UnitId RecruitingMenu::getSelectedOption(){ + return m_selectedId; +} + +}//namespace advance_wars \ No newline at end of file diff --git a/src/game/ui/Recruitingmenu.hpp b/src/game/ui/Recruitingmenu.hpp new file mode 100644 index 0000000000000000000000000000000000000000..01bfdb83bd93b2881071bdc027e3b824ad2bcbc8 --- /dev/null +++ b/src/game/ui/Recruitingmenu.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "../Scene.hpp" +#include "../Unit.hpp" + +namespace advanced_wars +{ + + class RecruitingMenu : public Scene { + + private: + + size_t m_selectedOption; + std::vector<std::pair<std::string, int>> m_options; + int m_x; + int m_y; + const std::unordered_map <UnitId ,std::pair <std::string, int>> unitNames; + std::unordered_map<int, UnitId> cost2UnitId; + UnitId m_selectedId; + + void selectSprite(); + + public: + + UnitId getSelectedOption(); + + void handleEvent(Engine& engine, SDL_Event& event); + + void update(int x, int y); + + RecruitingMenu(); + + void setOptions(const std::vector<UnitId> recruitableUnits); + + void render(Engine& engine) override; + + + }; + +} //namespace advanced_wars \ No newline at end of file diff --git a/src/game/ui/TileMarker.cpp b/src/game/ui/TileMarker.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cfffcc6c157215a117ecf8989374bbef32b02a75 --- /dev/null +++ b/src/game/ui/TileMarker.cpp @@ -0,0 +1,90 @@ +#include "TileMarker.hpp" +#include <iostream> + +namespace advanced_wars +{ +TileMarker::TileMarker(int renderingScale, int tileX, int tileY, int levelWidth, int levelHeight) + : m_renderingScale(renderingScale), m_width(16), m_height(16) +{ + int tileSize = 16 * renderingScale; + m_x = tileX * tileSize; + m_y = tileY * tileSize + (tileSize - m_height); + m_levelWidth = levelWidth * tileSize; + m_levelHeight = levelHeight * tileSize; +} + +void TileMarker::render(Engine& engine) +{ + SDL_SetRenderDrawColor(engine.renderer(), 255, 0, 0, 255); + SDL_Rect box = {m_x, m_y, m_width, m_height}; + SDL_RenderFillRect(engine.renderer(), &box); +} + +void TileMarker::handleEvent(Engine& engine, SDL_Event& event) +{ + if (event.type == SDL_KEYDOWN) + { + int newX; + int newY; + switch (event.key.keysym.sym) + { + case SDLK_UP: + newY = m_y - 16 * m_renderingScale; + std::cout << "New Y: " << newY << std::endl; + if (newY <= 0) + { + break; + } + m_y = newY; + break; + case SDLK_DOWN: + newY = m_y + 16 * m_renderingScale; + std::cout << "New Y: " << newY << std::endl; + + if (newY >= m_levelHeight) + + { + break; + } + m_y = newY; + break; + case SDLK_RIGHT: + newX = m_x + 16 * m_renderingScale; + std::cout << "New X: " << newX << std::endl; + + if (newX >= m_levelWidth) + { + break; + } + m_x = newX; + break; + case SDLK_LEFT: + newX = m_x - 16 * m_renderingScale; + std::cout << "New X: " << newX << std::endl; + + if (newX <= 0) + { + break; + } + m_x = newX; + break; + + default: + break; + } + } +} + +std::pair<int, int> TileMarker::getPosition() +{ + int tileX = m_x / (16 * m_renderingScale); + int tileY = m_y / (16 * m_renderingScale); + return {tileX, tileY}; +} + +void TileMarker::setPosition(int tileX, int tileY){ + m_x = tileX * 16 * m_renderingScale; + m_y = tileY * 16 * m_renderingScale + (16 * m_renderingScale - m_height); +} + +} // namespace advanced_wars diff --git a/src/game/ui/TileMarker.hpp b/src/game/ui/TileMarker.hpp new file mode 100644 index 0000000000000000000000000000000000000000..5b0225d7611674fdeacc097e910dae8d0fd14a8b --- /dev/null +++ b/src/game/ui/TileMarker.hpp @@ -0,0 +1,33 @@ +#pragma once +#include "../Scene.hpp" + +namespace advanced_wars +{ +/** + * @class TileMarker + * @brief Renders a marker on top of a tile to mark current position + * + */ +class TileMarker : public Scene +{ + public: + TileMarker(int renderingScale, int tileX, int tileY, int levelWidth, int levelHeight); + + void render(Engine& engine) override; + + void handleEvent(Engine& engine, SDL_Event& event) override; + + std::pair<int, int> getPosition(); + + void setPosition(int x, int y); + + private: + int m_x; + int m_y; + int m_renderingScale; + int m_width; + int m_height; + int m_levelHeight; + int m_levelWidth; +}; +} // namespace advanced_wars \ No newline at end of file