Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
Loading items

Target

Select target project
  • david.maul/cpp-project
1 result
Select Git revision
Loading items
Show changes

Commits on Source 23

......@@ -8,6 +8,8 @@ project(ADVANCED_WARS
)
include(FetchContent)
# HighFive einbinden
FetchContent_Declare(
highfive
GIT_REPOSITORY https://github.com/highfive-devs/highfive.git
......@@ -19,13 +21,13 @@ set(HIGHFIVE_UNIT_TESTS OFF)
FetchContent_MakeAvailable(highfive)
# Box2D einbinden
FetchContent_Declare(
box2d
GIT_REPOSITORY https://github.com/erincatto/box2d.git
GIT_TAG v3.0.0
)
# Box2D Build-Optionen konfigurieren
set(BOX2D_BUILD_TESTBED OFF CACHE BOOL "" FORCE)
set(BOX2D_BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE)
set(BOX2D_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
......@@ -72,43 +74,12 @@ file(GLOB HDF5_FILES ${RES_DIR}/*.h5)
# Executable erstellen
add_executable(advanced_wars ${ADVANCED_WARS_SOURCES})
target_include_directories(advanced_wars
PRIVATE
${highfive_SOURCE_DIR}/include
${Boost_INCLUDE_DIRS}
)
foreach(FONT ${FONT_FILES})
add_custom_command(
TARGET advanced_wars PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${FONT} ${OUTPUT_RES_DIR}
COMMENT "Kopiere Font: ${FONT} nach ${OUTPUT_RES_DIR}"
)
endforeach()
foreach(IMAGE ${IMAGE_FILES})
add_custom_command(
TARGET advanced_wars PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${IMAGE} ${OUTPUT_RES_DIR}
COMMENT "Kopiere Image: ${IMAGE} nach ${OUTPUT_RES_DIR}"
)
endforeach()
foreach(H5_FILE ${HDF5_FILES})
add_custom_command(
TARGET advanced_wars PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${H5_FILE} ${OUTPUT_RES_DIR}
COMMENT "Kopiere HDF5 File: ${H5_FILE} nach ${OUTPUT_RES_DIR}"
)
endforeach()
set(CMAKE_MODULE_PATH ${ADVANCED_WARS_SOURCE_DIR}/cmake/ ${CMAKE_MODULE_PATH})
# Plattform-spezifische Konfiguration
if(APPLE)
# SDL2 Frameworks für macOS
set(SDL2_PATH "/Library/Frameworks")
set(CMAKE_OSX_ARCHITECTURES "arm64")
target_include_directories(
advanced_wars PRIVATE
${SDL2_PATH}/SDL2.framework/Headers
......@@ -125,18 +96,26 @@ if(APPLE)
box2d
)
# HDF5 für macOS korrekt einbinden
find_package(HDF5 REQUIRED COMPONENTS CXX HL)
target_include_directories(advanced_wars PRIVATE ${HDF5_INCLUDE_DIRS} ${highfive_SOURCE_DIR}/include)
target_link_libraries(advanced_wars PRIVATE ${HDF5_LIBRARIES})
message(STATUS "Include Dir (SDL2): ${SDL2_PATH}/SDL2.framework/Headers")
message(STATUS "Include Dir (SDL2_image): ${SDL2_PATH}/SDL2_image.framework/Headers")
message(STATUS "Include Dir (SDL2_ttf): ${SDL2_PATH}/SDL2_ttf.framework/Headers")
message(STATUS "HDF5 Include Dirs: ${HDF5_INCLUDE_DIRS}")
message(STATUS "HDF5 Libraries: ${HDF5_LIBRARIES}")
message(STATUS "Boost Include Dirs: ${Boost_INCLUDE_DIRS}")
else()
# Linux Konfiguration
find_package(SDL2 REQUIRED)
find_package(SDL2_IMAGE REQUIRED)
find_package(SDL2_ttf REQUIRED)
find_package(HDF5 REQUIRED COMPONENTS CXX)
find_package(HDF5 REQUIRED COMPONENTS CXX HL)
include_directories(/usr/include/SDL2)
include_directories(${SDL2_INCLUDE_DIR})
include_directories(${SDL2_IMG_INCLUDE_DIR})
include_directories(${SDL2_TTF_INCLUDE_DIR})
......@@ -151,4 +130,33 @@ else()
box2d
m
)
target_include_directories(advanced_wars PRIVATE ${highfive_SOURCE_DIR}/include)
endif()
# Ressourcen kopieren (plattformübergreifend)
foreach(FONT ${FONT_FILES})
add_custom_command(
TARGET advanced_wars PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${FONT} ${OUTPUT_RES_DIR}
COMMENT "Kopiere Font: ${FONT} nach ${OUTPUT_RES_DIR}"
)
endforeach()
foreach(IMAGE ${IMAGE_FILES})
add_custom_command(
TARGET advanced_wars PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${IMAGE} ${OUTPUT_RES_DIR}
COMMENT "Kopiere Image: ${IMAGE} nach ${OUTPUT_RES_DIR}"
)
endforeach()
foreach(H5_FILE ${HDF5_FILES})
add_custom_command(
TARGET advanced_wars PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${H5_FILE} ${OUTPUT_RES_DIR}
COMMENT "Kopiere HDF5 File: ${H5_FILE} nach ${OUTPUT_RES_DIR}"
)
endforeach()
set(CMAKE_MODULE_PATH ${ADVANCED_WARS_SOURCE_DIR}/cmake/ ${CMAKE_MODULE_PATH})
\ No newline at end of file
No preview for this file type
#include "Building.hpp"
#include "Spritesheet.hpp"
#include <iostream>
namespace advanced_wars
{
......@@ -28,4 +29,79 @@ void Building::render(Engine& engine, int scale)
&dst, 0, NULL, SDL_FLIP_NONE);
}
void Building::switch_faction(BuildingFaction faction)
{
this->m_faction = faction;
if (this->m_id == BuildingId::HEADQUARTER)
{
std::cout << "The game is over!" << std::endl;
}
}
// implement call to UI to show available units
void Building::on_click()
{
std::cout << "A building is selected!" << std::endl;
};
bool Building::check_spawn(std::unordered_map<int, advanced_wars::Unit>& units)
{
for (auto& [id, unit] : units)
{
if (unit.m_x == this->m_x && unit.m_y == this->m_y)
{
return false;
}
}
return true;
}
// can be added as soon as the playerobject is available
bool Building::check_money(int price)
{
// replace 400 with player.money and replace price with chosenUnit.price
if (400 > price)
{
return false;
}
return true;
}
std::vector<UnitId> 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};
}
if (this->m_id == BuildingId::PORT)
{
return {UnitId::LANDER, UnitId::CRUISER, UnitId::SUBMARINE, UnitId::BATTLESHIP};
}
if (this->m_id == BuildingId::AIRPORT)
{
return {
UnitId::TRANSPORT_HELICOPTER, UnitId::BATTLE_HELICOPTER, UnitId::FIGHTER,
UnitId::BOMBER};
}
return {};
}
} // namespace advanced_wars
\ No newline at end of file
......@@ -2,18 +2,19 @@
#include "Engine.hpp"
#include "Scene.hpp"
#include "Unit.hpp"
#include <unordered_map>
namespace advanced_wars
{
enum class BuildingFaction
{
RED = 0,
BLUE = 1,
YELLOW = 2,
GREEN = 3,
PURPLE = 4,
NEUTRAL = 5,
URED = 0,
UBLUE = 1,
UGREEN = 2,
UYELLOW = 3,
UPURPLE = 4,
};
enum class BuildingId
......@@ -21,8 +22,8 @@ enum class BuildingId
HEADQUARTER = 0,
CITY = 1,
FACTORY = 2,
PORT = 3,
SATELLITE = 4,
AIRPORT = 3,
PORT = 4,
};
class Building
......@@ -36,6 +37,41 @@ class Building
BuildingFaction m_faction;
void render(Engine& engine, int scale);
/**
Changes the faction to the specified one
@param faction The new faction the unit will belong to
*/
void switch_faction(BuildingFaction faction);
/*
checks if the tile ontop of the building is free
*/
bool check_spawn(std::unordered_map<int, advanced_wars::Unit>& units);
/*
checks if the player has enough money for the unit to be recruited
*/
bool check_money(int price);
/*
When the building is selected, the player should have the ability to recruit a selection of
units They should be displayed by the UI On_click();
*/
void recruit_unit();
/**
If the building is clicked, it shows information to the player, here it will be a list of
all available units
*/
void on_click();
/**
* Provides a vector of recruitable units, depending on the building id
*
*/
std::vector<UnitId> recruitableUnits();
};
} // namespace advanced_wars
\ No newline at end of file
......@@ -22,8 +22,10 @@ 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)
: m_name(name), m_width(width), m_height(height), m_tiles(tiles), m_contextMenu(ContextMenu()),
m_contextMenuActive(false), m_id(0)
: 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_contextMenu.setOptions({"Move", "Info", "Wait"});
......@@ -47,6 +49,9 @@ Level::Level(
{
throw std::runtime_error("level tile mismatch");
}
m_selectedBuilding = -1;
m_selectedUnit = -1;
};
Level Level::loadLevel(std::string path)
......@@ -95,199 +100,83 @@ Level Level::loadLevel(std::string path)
return Level(name, width, height, tiles, buildings, {}, {});
};
bool Level::clickCheckLeft(int tileX, int tileY)
{
if (selectUnit(tileX, tileY))
std::pair<int, int> Level::calcTilePos(int mouseX, int mouseY)
{
return true;
}
if (selectBuilding(tileX, tileY))
{
return true;
}
int tileSize = (16 * RENDERING_SCALE);
int tileX = mouseX / tileSize;
int tileY = mouseY / tileSize;
return false;
return {tileX, tileY};
}
bool Level::clickCheckRight(int tileX, int tileY)
void Level::selectEntity(int x, int y)
{
std::pair<int, int> tilePos = calcTilePos(x, y);
if (targetUnit(tileX, tileY))
if ((m_selectedUnit = selectUnit(tilePos.first, tilePos.second)) >= 0)
{
return true;
return;
}
return false;
}
bool Level::selectUnit(int tileX, int tileY)
{
// std::cout << "tileX:" << tileX << "tileX:" << tileY << std::endl;
for (auto& [id, unit] : m_units)
{
if (unit.m_x == tileX && unit.m_y == tileY)
if ((m_selectedBuilding = selectBuilding(tilePos.first, tilePos.second)) >= 0)
{
// std::cout << "unitX:" << unit.x << "unitY:" << unit.y << std::endl;
m_selectedUnit = id;
return true;
}
return;
}
return false;
}
bool Level::targetUnit(int tileX, int tileY)
int Level::selectUnit(int tileX, int tileY)
{
// std::cout << "tileX:" << tileX << "tileX:" << tileY << std::endl;
for (auto& [id, unit] : m_units)
{
if (unit.m_x == tileX && unit.m_y == tileY)
{
// std::cout << "unitX:" << unit.x << "unitY:" << unit.y << std::endl;
m_targetedUnit = id;
return true;
return id;
}
}
return false;
return -1;
}
bool Level::selectBuilding(int tileX, int tileY)
int Level::selectBuilding(int tileX, int tileY)
{
for (auto& [id, building] : m_buildings)
{
if (building.m_x == tileX && building.m_y == tileY)
{
// std::cout << "X:" << unit.x << "Y:" << unit.y << std::endl;
m_selectedBuilding = id;
return true;
return id;
}
}
return false;
return -1;
}
void Level::handleEvent(Engine& engine, SDL_Event& event)
{
switch (event.type)
{
case SDL_MOUSEBUTTONDOWN:
m_contextMenu.update(event.button.x, event.button.y);
m_contextMenuActive = true;
// the current unit debug combat should be handled by the contextmenu with its menu options
if (event.button.button == SDL_BUTTON_LEFT)
{
int tileX = event.button.x / (16 * RENDERING_SCALE);
int tileY = event.button.y / (16 * RENDERING_SCALE);
if (clickCheckLeft(tileX, tileY))
{
if (m_selectedUnit > -1)
{
m_units.at(m_selectedUnit).on_left_click(event);
}
if (m_selectedBuilding > -1)
{
// building stuff
}
}
else
{
std::cout << "Neither building nor unit clicked!" << std::endl;
m_selectedUnit = -1;
m_selectedBuilding = -1;
}
}
else if (event.button.button == SDL_BUTTON_RIGHT)
{
if (m_selectedUnit > -1)
{
int tileX = event.button.x / (16 * RENDERING_SCALE);
int tileY = event.button.y / (16 * RENDERING_SCALE);
if (clickCheckRight(tileX, tileY))
{
m_units.at(m_selectedUnit).attack((m_units.at(m_targetedUnit)));
if (m_units.at(m_selectedUnit).m_health <= 0)
{
removeUnit(m_selectedUnit);
}
}
else
void Level::handleEvent(Engine& engine, SDL_Event& event)
{
m_units.at(m_selectedUnit).updatePosition(tileX, tileY);
}
}
else
switch (m_state)
{
std::cout << "No unit selected! " << std::endl;
}
}
case LevelState::MENUACTIVE_STATE:
handleMenuActiveEvents(engine, event);
break;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_ESCAPE)
{
// Pause the game
std::cout << "Pausing game..." << std::endl;
SDL_Texture* currentTexture = SDL_CreateTexture(
engine.renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 800, 600);
PauseMenu pauseMenu(0, currentTexture);
engine.pushScene(std::make_shared<PauseMenu>(pauseMenu));
}
if (m_contextMenuActive)
{
if (event.key.keysym.sym == SDLK_RETURN)
{
if (m_contextMenu.getSelectedOption() == "Wait")
{
m_contextMenuActive = false;
}
}
else
{
m_contextMenu.handleEvent(engine, event);
}
}
case LevelState::SELECTING_STATE:
handleSelectingEvents(engine, event);
break;
case LevelState::ANIMATING_STATE:
// maybe do nothing
break;
case LevelState::MOVEMENT_STATE:
handleMovementEvents(engine, event);
break;
case LevelState::ATTACKING_STATE:
handleAttackingEvents(engine, event);
break;
case LevelState::RECRUITING_STATE:
handleRecruitingEvent(engine, event);
break;
default:
break;
}
}
void Level::render(Engine& engine)
{
// Iterate over all events
while (!engine.events().empty())
{
handleEvent(engine, engine.events().at(0));
engine.events().pop_front();
}
// Tiles
for (Tile& tile : m_tiles)
{
......@@ -326,10 +215,16 @@ void Level::render(Engine& engine)
this->removeEffect(id);
}
if (m_contextMenuActive)
if (m_state == LevelState::MENUACTIVE_STATE)
{
m_contextMenu.render(engine);
}
if (m_state == LevelState::RECRUITING_STATE)
{
m_recruitingMenu.render(engine);
}
m_currentPos.render(engine);
}
int Level::addBuilding(Building building)
......@@ -380,4 +275,292 @@ Effect Level::removeEffect(int id)
return value;
}
void Level::handleRecruitingEvent(Engine& engine, SDL_Event& event) {
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;
}
}
}
}}
//*******************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)
{
selectEntity(event.button.x, event.button.y);
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);
if (m_selectedUnit >= 0)
{
m_contextMenu.setOptions({"Move", "Attack", "Info", "Wait"});
}
else
{
m_contextMenu.setOptions({"Train", "Info", "Wait"});
}
m_state = LevelState::MENUACTIVE_STATE;
}
else
{
m_state = LevelState::SELECTING_STATE;
}
}
default:
break;
}
}
void Level::handleMenuActiveEvents(Engine& engine, SDL_Event& event)
{
switch (event.type)
{
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_ESCAPE)
{
m_selectedUnit = -1;
m_selectedBuilding = -1;
m_state = LevelState::SELECTING_STATE;
}
if (event.key.keysym.sym == SDLK_UP || event.key.keysym.sym == SDLK_DOWN)
{
m_contextMenu.handleEvent(engine, event);
}
if (event.key.keysym.sym == SDLK_RETURN)
{
std::string cmd = m_contextMenu.getSelectedOption();
if (cmd == "Wait")
{
m_state = LevelState::SELECTING_STATE;
}
if (cmd == "Move")
{
m_state = LevelState::MOVEMENT_STATE;
// Hier Pathfinding einsetzen
}
if (cmd == "Attack")
{
m_state = LevelState::ATTACKING_STATE;
}
if (cmd == "Info")
{
// TODO: Hier Informationen zur Einheit darstellen
if (m_selectedUnit > -1)
{
Unit& u = m_units.at(m_selectedUnit);
std::cout << "Health: " << u.m_health << std::endl;
}
if (m_selectedBuilding > -1)
{
Building b = m_buildings.at(m_selectedBuilding);
std::cout << "Building ID: " << static_cast<int>(b.m_id) << " || "
<< "Building Faction: " << static_cast<int>(b.m_faction) << std::endl;
}
}
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;
}
}
break;
default:
break;
}
}
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)
{
m_state = LevelState::MENUACTIVE_STATE;
}
break;
case SDL_MOUSEBUTTONDOWN:
if (event.button.button == SDL_BUTTON_LEFT)
{
// Bei Movement animation in ANIMATING_STATE gehen
std::pair<int, int> tilePos = calcTilePos(event.button.x, event.button.y);
m_currentPos.setPosition(tilePos.first, tilePos.second);
handleMovement(tilePos);
}
break;
default:
break;
}
}
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);
m_currentPos.setPosition(tilePos.first, tilePos.second);
handleAttack(tilePos);
}
break;
default:
break;
}
}
//************end event handler delegates for different level states*****************************
} // namespace advanced_wars
......@@ -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>
......@@ -15,6 +17,16 @@
namespace advanced_wars
{
enum class LevelState
{
SELECTING_STATE,
MOVEMENT_STATE,
ANIMATING_STATE,
MENUACTIVE_STATE,
ATTACKING_STATE,
RECRUITING_STATE,
};
/**
* @brief The main window of the game
*/
......@@ -29,6 +41,21 @@ class Level : public Scene
void render(Engine& engine);
/*
on event
key down
escape -> deselect/ open pause menu
key left -> move one tile left
key right -> move one tile right
key up -> move one tile up / change context menu selection up
key down -> move one tile down / change context menu selection down
key enter -> confirm selection in context menu /
confirm selected position(moving)/
select entity on tile
mousebutton down
button left -> select field/building/unit/
move to position
*/
void handleEvent(Engine& engine, SDL_Event& event);
int addBuilding(Building building);
......@@ -47,27 +74,33 @@ class Level : public Scene
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;
int m_selectedUnit;
int m_targetedUnit;
int m_selectedBuilding;
ContextMenu m_contextMenu;
bool m_contextMenuActive;
RecruitingMenu m_recruitingMenu;
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;
bool selectUnit(int tileX, int tileY);
bool targetUnit(int tileX, int tileY);
bool selectBuilding(int tileX, int tileY);
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);
bool clickCheckLeft(int mouseX, int mouseY);
bool clickCheckRight(int mouseX, int mouseY);
void handleAttack(std::pair<int, int> tilePos);
void handleMovement(std::pair<int, int> tilePos);
void handlePositionMarker(Engine& engine, SDL_Event& event);
};
} // namespace advanced_wars
......@@ -357,6 +357,44 @@ Spritesheet::Spritesheet(std::string path, Engine& engine)
this->m_effectWidth = 32;
this->m_effectHeight = 32;
// Numbers
HighFive::DataSet number_ds = file.getDataSet("/misc/numbers");
std::vector<std::vector<uint32_t>> number_frames;
number_ds.read(number_frames);
std::vector<uint32_t> number_buffer(8 * 80, 0);
for (int y = 0; y < 8; y++)
{
for (int x = 0; x < 80; x++)
{
int index = y * 80 + x;
number_buffer.at(index) = number_frames.at(8 - y - 1).at(x);
}
}
SDL_Texture* tmp = SDL_CreateTexture(
engine.renderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, 80, 8);
SDL_SetTextureBlendMode(tmp, SDL_BLENDMODE_BLEND);
if (tmp == nullptr)
{
throw std::runtime_error(
"Fehler beim Erstellen der Textur für die Effects: " + std::string(SDL_GetError()));
}
if (SDL_UpdateTexture(tmp, NULL, number_buffer.data(), 80 * sizeof(int32_t)) != 0)
{
throw std::runtime_error(
"Fehler beim updaten der Textur für die Tiles: " + std::string(SDL_GetError()));
}
this->m_numberTextures = tmp;
this->m_numberWidth = 8;
this->m_numberHeight = 8;
}
// Tiles
......@@ -434,6 +472,22 @@ std::vector<std::pair<SDL_Texture*, int>>& Spritesheet::getEffectTextures()
return this->m_effectTextures;
}
// Numbers
int Spritesheet::getNumberWidth()
{
return this->m_numberWidth;
}
int Spritesheet::getNumberHeight()
{
return this->m_numberHeight;
}
SDL_Texture* Spritesheet::getNumberTexture()
{
return this->m_numberTextures;
}
Spritesheet::~Spritesheet()
{
for (std::pair<SDL_Texture*, int> tile_texture : m_tileTextures)
......@@ -456,6 +510,13 @@ Spritesheet::~Spritesheet()
}
}
}
for (auto& [texture, i] : m_effectTextures)
{
SDL_DestroyTexture(texture);
}
SDL_DestroyTexture(m_numberTextures);
}
} // namespace advanced_wars
\ No newline at end of file
......@@ -153,6 +153,26 @@ class Spritesheet
*/
std::vector<std::pair<SDL_Texture*, int>>& getEffectTextures();
// Numbers
/**
* @return The width of a number in pixels
*/
int getNumberWidth();
/**
* @return The height of a number in pixels
*/
int getNumberHeight();
/**
* The texture represents the individual texture for all numbers linearised.
*
* The order of the numbers is the following: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.
*
* @return Number texture
*/
SDL_Texture* getNumberTexture();
private:
// Tiles
std::vector<std::pair<SDL_Texture*, int>> m_tileTextures;
......@@ -179,5 +199,11 @@ class Spritesheet
int m_effectWidth;
int m_effectHeight;
// Numbers
SDL_Texture* m_numberTextures;
int m_numberWidth;
int m_numberHeight;
};
} // namespace advanced_wars
......@@ -64,7 +64,7 @@ void Unit::render(Engine& engine, int scale)
SDL_Rect dst;
dst.x = ((m_x * spritesheet->getUnitWidth()) - 4) * scale;
dst.y = ((m_y * spritesheet->getUnitHeight()) - 4) * scale;
dst.y = ((m_y * spritesheet->getUnitHeight()) - 8) * scale;
dst.w = spritesheet->getUnitMovingWidth() * scale;
dst.h = spritesheet->getUnitMovingHeight() * scale;
......@@ -77,6 +77,7 @@ void Unit::render(Engine& engine, int scale)
.first,
&src, &dst, 0, NULL, SDL_FLIP_NONE);
}
renderHP(engine, scale);
}
void Unit::attack(Unit& enemy)
......@@ -216,4 +217,43 @@ bool Unit::inRange(Unit& enemy)
return false;
}
UnitFaction Unit::getFaction()
{
return this->m_faction;
}
void Unit::renderHP(Engine& engine, int scale)
{
Spritesheet* spritesheet = engine.getSpritesheet();
SDL_Texture* numbers = spritesheet->getNumberTexture();
int numberWidth = spritesheet->getNumberWidth();
int numberHeight = spritesheet->getNumberHeight();
int hp = ceil((double)m_health / 10);
SDL_Rect src;
src.x = hp % 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.w = numberWidth * scale;
dest.h = numberHeight * scale;
SDL_RenderCopy(engine.renderer(), numbers, &src, &dest);
if (hp == 10)
{
src.x = 8;
dest.x = (m_x * spritesheet->getTileWidth() + 1) * scale;
SDL_RenderCopy(engine.renderer(), numbers, &src, &dest);
}
}
} // namespace advanced_wars
\ No newline at end of file
......@@ -68,6 +68,7 @@ class Unit
int m_x;
int m_y;
int m_health; // health equals max_health at construction
int m_price;
Unit(int x, int y, UnitFaction faction, UnitId id, UnitState state);
~Unit()
......@@ -116,6 +117,8 @@ class Unit
*/
void on_left_click(SDL_Event event);
UnitFaction getFaction();
private:
UnitFaction m_faction;
UnitId m_id;
......@@ -135,6 +138,8 @@ class Unit
Weapon m_primaryWeapon;
int m_ammo;
void renderHP(Engine& engine, int scale);
};
} // namespace advanced_wars
\ No newline at end of file
......@@ -99,7 +99,6 @@ void Menu::render(Engine& engine)
TTF_CloseFont(titleFont);
TTF_CloseFont(menuFont);
SDL_RenderPresent(engine.renderer());
TTF_Quit();
}
......
#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
#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
#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
#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