diff --git a/.gitignore b/.gitignore index 259148f..19e6132 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,11 @@ *.exe *.out *.app + +# Notepad Backups +*.bak + +# Clion +.idea +cmake-build-release +CMakeLists.txt \ No newline at end of file diff --git a/Button.cpp b/Button.cpp new file mode 100644 index 0000000..8504634 --- /dev/null +++ b/Button.cpp @@ -0,0 +1,33 @@ +// +// Created by Benjamin on 1/25/2022. +// + +#include "Button.h" + +Button::Button(const sf::Texture &texture, const std::function &command) : Sprite(texture) { + this->command = command; +} + +Button::Button(const sf::Texture &texture, float x, float y, float scale, int origin, const std::function &command) : Sprite(texture, x, y, scale, origin) { + this->command = command; +} + +void Button::click() { + command(); +} + +void Button::hover() { + Sprite::hover(); + + sf::Vector2f pos = getPosition(); + setScale(getScale().x + 0.1, getScale().x + 0.1); + setPosition(pos.x, pos.y); +} + +void Button::unHover() { + Sprite::unHover(); + + sf::Vector2f pos = getPosition(); + setScale(getScale().x - 0.1, getScale().x - 0.1); + setPosition(pos.x, pos.y); +} diff --git a/Button.h b/Button.h new file mode 100644 index 0000000..d0d0c94 --- /dev/null +++ b/Button.h @@ -0,0 +1,24 @@ +// +// Created by Benjamin on 1/25/2022. +// + +#ifndef SFML_TEMPLATE_BUTTON_H +#define SFML_TEMPLATE_BUTTON_H + +#include +#include "Sprite.h" + +class Button : public Sprite { +public: + explicit Button(const sf::Texture &, const std::function &); + Button(const sf::Texture &, float, float, float, int, const std::function &); + + void click() override; + void hover() override; + void unHover() override; +protected: + std::function command; +}; + + +#endif //SFML_TEMPLATE_BUTTON_H diff --git a/Canopy.cpp b/Canopy.cpp new file mode 100644 index 0000000..2daf08e --- /dev/null +++ b/Canopy.cpp @@ -0,0 +1,19 @@ +// +// Created by Benjamin on 1/30/2022. +// + +#include "Canopy.h" + +Canopy::Canopy(const sf::Texture &texture, float x, float y, float angle, float scale, int origin) : Sprite(texture, x, y, angle, scale, origin) {} + +void Canopy::hover() { + Sprite::hover(); + + setColor(sf::Color(255, 255, 255, 128)); +} + +void Canopy::unHover() { + Sprite::unHover(); + + setColor(sf::Color(255, 255, 255, 255)); +} \ No newline at end of file diff --git a/Canopy.h b/Canopy.h new file mode 100644 index 0000000..0351f7e --- /dev/null +++ b/Canopy.h @@ -0,0 +1,20 @@ +// +// Created by Benjamin on 1/30/2022. +// + +#ifndef SFML_TEMPLATE_CANOPY_H +#define SFML_TEMPLATE_CANOPY_H + + +#include "Sprite.h" + +class Canopy : public Sprite { +public: + Canopy(const sf::Texture &, float, float, float, float, int); + + void hover() override; + void unHover() override; +}; + + +#endif //SFML_TEMPLATE_CANOPY_H diff --git a/Collision.cpp b/Collision.cpp new file mode 100644 index 0000000..567eef9 --- /dev/null +++ b/Collision.cpp @@ -0,0 +1,190 @@ +/* + * File: collision.cpp + * Author: Nick (original version), ahnonay (SFML2 compatibility) + */ + +#include +#include "Collision.h" + +namespace Collision +{ + class BitmaskManager + { + public: + ~BitmaskManager() { + std::map::const_iterator end = Bitmasks.end(); + for (std::map::const_iterator iter = Bitmasks.begin(); iter!=end; iter++) + delete [] iter->second; + } + + sf::Uint8 GetPixel (const sf::Uint8* mask, const sf::Texture* tex, unsigned int x, unsigned int y) { + if (x>tex->getSize().x||y>tex->getSize().y) + return 0; + + return mask[x+y*tex->getSize().x]; + } + + sf::Uint8* GetMask (const sf::Texture* tex) { + sf::Uint8* mask; + std::map::iterator pair = Bitmasks.find(tex); + if (pair==Bitmasks.end()) + { + sf::Image img = tex->copyToImage(); + mask = CreateMask (tex, img); + } + else + mask = pair->second; + + return mask; + } + + sf::Uint8* CreateMask (const sf::Texture* tex, const sf::Image& img) { + sf::Uint8* mask = new sf::Uint8[tex->getSize().y*tex->getSize().x]; + + for (unsigned int y = 0; ygetSize().y; y++) + { + for (unsigned int x = 0; xgetSize().x; x++) + mask[x+y*tex->getSize().x] = img.getPixel(x,y).a; + } + + Bitmasks.insert(std::pair(tex,mask)); + + return mask; + } + private: + std::map Bitmasks; + }; + + BitmaskManager Bitmasks; + + bool PixelPerfectTest(const sf::Sprite& Object1, const sf::Sprite& Object2, sf::Uint8 AlphaLimit) { + sf::FloatRect Intersection; + if (Object1.getGlobalBounds().intersects(Object2.getGlobalBounds(), Intersection)) { + sf::IntRect O1SubRect = Object1.getTextureRect(); + sf::IntRect O2SubRect = Object2.getTextureRect(); + + sf::Uint8* mask1 = Bitmasks.GetMask(Object1.getTexture()); + sf::Uint8* mask2 = Bitmasks.GetMask(Object2.getTexture()); + + // Loop through our pixels + for (int i = Intersection.left; i < Intersection.left+Intersection.width; i++) { + for (int j = Intersection.top; j < Intersection.top+Intersection.height; j++) { + + sf::Vector2f o1v = Object1.getInverseTransform().transformPoint(i, j); + sf::Vector2f o2v = Object2.getInverseTransform().transformPoint(i, j); + + // Make sure pixels fall within the sprite's subrect + if (o1v.x > 0 && o1v.y > 0 && o2v.x > 0 && o2v.y > 0 && + o1v.x < O1SubRect.width && o1v.y < O1SubRect.height && + o2v.x < O2SubRect.width && o2v.y < O2SubRect.height) { + + if (Bitmasks.GetPixel(mask1, Object1.getTexture(), (int)(o1v.x)+O1SubRect.left, (int)(o1v.y)+O1SubRect.top) > AlphaLimit && + Bitmasks.GetPixel(mask2, Object2.getTexture(), (int)(o2v.x)+O2SubRect.left, (int)(o2v.y)+O2SubRect.top) > AlphaLimit) + return true; + + } + } + } + } + return false; + } + + bool CreateTextureAndBitmask(sf::Texture &LoadInto, const std::string& Filename) + { + sf::Image img; + if (!img.loadFromFile(Filename)) + return false; + if (!LoadInto.loadFromImage(img)) + return false; + + Bitmasks.CreateMask(&LoadInto, img); + return true; + } + + sf::Vector2f GetSpriteCenter (const sf::Sprite& Object) + { + sf::FloatRect AABB = Object.getGlobalBounds(); + return sf::Vector2f (AABB.left+AABB.width/2.f, AABB.top+AABB.height/2.f); + } + + sf::Vector2f GetSpriteSize (const sf::Sprite& Object) + { + sf::IntRect OriginalSize = Object.getTextureRect(); + sf::Vector2f Scale = Object.getScale(); + return sf::Vector2f (OriginalSize.width*Scale.x, OriginalSize.height*Scale.y); + } + + bool CircleTest(const sf::Sprite& Object1, const sf::Sprite& Object2) { + sf::Vector2f Obj1Size = GetSpriteSize(Object1); + sf::Vector2f Obj2Size = GetSpriteSize(Object2); + float Radius1 = (Obj1Size.x + Obj1Size.y) / 4; + float Radius2 = (Obj2Size.x + Obj2Size.y) / 4; + + sf::Vector2f Distance = GetSpriteCenter(Object1)-GetSpriteCenter(Object2); + + return (Distance.x * Distance.x + Distance.y * Distance.y <= (Radius1 + Radius2) * (Radius1 + Radius2)); + } + + class OrientedBoundingBox // Used in the BoundingBoxTest + { + public: + OrientedBoundingBox (const sf::Sprite& Object) // Calculate the four points of the OBB from a transformed (scaled, rotated...) sprite + { + sf::Transform trans = Object.getTransform(); + sf::IntRect local = Object.getTextureRect(); + Points[0] = trans.transformPoint(0.f, 0.f); + Points[1] = trans.transformPoint(local.width, 0.f); + Points[2] = trans.transformPoint(local.width, local.height); + Points[3] = trans.transformPoint(0.f, local.height); + } + + sf::Vector2f Points[4]; + + void ProjectOntoAxis (const sf::Vector2f& Axis, float& Min, float& Max) // Project all four points of the OBB onto the given axis and return the dotproducts of the two outermost points + { + Min = (Points[0].x*Axis.x+Points[0].y*Axis.y); + Max = Min; + for (int j = 1; j<4; j++) + { + float Projection = (Points[j].x*Axis.x+Points[j].y*Axis.y); + + if (ProjectionMax) + Max=Projection; + } + } + }; + + bool BoundingBoxTest(const sf::Sprite& Object1, const sf::Sprite& Object2) { + OrientedBoundingBox OBB1 (Object1); + OrientedBoundingBox OBB2 (Object2); + + // Create the four distinct axes that are perpendicular to the edges of the two rectangles + sf::Vector2f Axes[4] = { + sf::Vector2f (OBB1.Points[1].x-OBB1.Points[0].x, + OBB1.Points[1].y-OBB1.Points[0].y), + sf::Vector2f (OBB1.Points[1].x-OBB1.Points[2].x, + OBB1.Points[1].y-OBB1.Points[2].y), + sf::Vector2f (OBB2.Points[0].x-OBB2.Points[3].x, + OBB2.Points[0].y-OBB2.Points[3].y), + sf::Vector2f (OBB2.Points[0].x-OBB2.Points[1].x, + OBB2.Points[0].y-OBB2.Points[1].y) + }; + + for (int i = 0; i<4; i++) // For each axis... + { + float MinOBB1, MaxOBB1, MinOBB2, MaxOBB2; + + // ... project the points of both OBBs onto the axis ... + OBB1.ProjectOntoAxis(Axes[i], MinOBB1, MaxOBB1); + OBB2.ProjectOntoAxis(Axes[i], MinOBB2, MaxOBB2); + + // ... and check whether the outermost projected points of both OBBs overlap. + // If this is not the case, the Separating Axis Theorem states that there can be no collision between the rectangles + if (!((MinOBB2<=MaxOBB1)&&(MaxOBB2>=MinOBB1))) + return false; + } + return true; + } +} \ No newline at end of file diff --git a/Collision.h b/Collision.h new file mode 100644 index 0000000..84017bd --- /dev/null +++ b/Collision.h @@ -0,0 +1,77 @@ +/* + * File: collision.h + * Authors: Nick Koirala (original version), ahnonay (SFML2 compatibility) + * + * Collision Detection and handling class + * For SFML2. + + Notice from the original version: + + (c) 2009 - LittleMonkey Ltd + + This software is provided 'as-is', without any express or + implied warranty. In no event will the authors be held + liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute + it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; + you must not claim that you wrote the original software. + If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but + is not required. + + 2. Altered source versions must be plainly marked as such, + and must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any + source distribution. + + * + * Created on 30 January 2009, 11:02 + */ + +#ifndef SFML_TEMPLATE_COLLISION_H +#define SFML_TEMPLATE_COLLISION_H + +#include + +namespace Collision { + ////// + /// Test for a collision between two sprites by comparing the alpha values of overlapping pixels + /// Supports scaling and rotation + /// AlphaLimit: The threshold at which a pixel becomes "solid". If AlphaLimit is 127, a pixel with + /// alpha value 128 will cause a collision and a pixel with alpha value 126 will not. + /// + /// This functions creates bitmasks of the textures of the two sprites by + /// downloading the textures from the graphics card to memory -> SLOW! + /// You can avoid this by using the "CreateTextureAndBitmask" function + ////// + bool PixelPerfectTest(const sf::Sprite& Object1 ,const sf::Sprite& Object2, sf::Uint8 AlphaLimit = 0); + + ////// + /// Replaces Texture::loadFromFile + /// Load an imagefile into the given texture and create a bitmask for it + /// This is much faster than creating the bitmask for a texture on the first run of "PixelPerfectTest" + /// + /// The function returns false if the file could not be opened for some reason + ////// + bool CreateTextureAndBitmask(sf::Texture &LoadInto, const std::string& Filename); + + ////// + /// Test for collision using circle collision dection + /// Radius is averaged from the dimensions of the sprite so + /// roughly circular objects will be much more accurate + ////// + bool CircleTest(const sf::Sprite& Object1, const sf::Sprite& Object2); + + ////// + /// Test for bounding box collision using the Separating Axis Theorem + /// Supports scaling and rotation + ////// + bool BoundingBoxTest(const sf::Sprite& Object1, const sf::Sprite& Object2); +} + +#endif //SFML_TEMPLATE_COLLISION_H diff --git a/Game.cpp b/Game.cpp new file mode 100644 index 0000000..39215fd --- /dev/null +++ b/Game.cpp @@ -0,0 +1,696 @@ +// +// Created by Benjamin on 1/25/2022. +// + +#include +#include +#include +#include + +#include "Button.h" +#include "Game.h" +#include "Path.h" +#include "Obstacle.h" +#include "Canopy.h" +#include "TowerGUI.h" +#include "Collision.h" +#include "TextBox.h" +#include "VariableLabel.h" + +std::vector open(const std::string &path) { + std::vector files; + + DIR *dir; + struct dirent *ent; + if ((dir = opendir(path.c_str())) != nullptr) { + /* print all the files and directories within directory */ + while ((ent = readdir(dir)) != nullptr) { + files.emplace_back(ent->d_name); + } + closedir(dir); + } else { + /* could not open directory */ + perror(""); + } + + return files; +} + +int wrap(int n, int const nLowerBound, int const nUpperBound) +{ + int range_size = nUpperBound - nLowerBound + 1; + + if (n < nLowerBound) + n += range_size * ((nLowerBound - n) / range_size + 1); + + return nLowerBound + (n - nLowerBound) % range_size; +} + +Game::Game() { + // displayGroup "Hello, World!" -- this still appears in our Run terminal as before + std::cout << "Hello, World!" << std::endl; + + // create a RenderWindow object + // specify the size to be 640x640 + // set the title to be "SFML Example Window" + auto mode = sf::VideoMode::getDesktopMode(); + mode.height += 1; + + window = new sf::RenderWindow(mode, "Tower Defense", sf::Style::None); + screenRatio = sf::Vector2f(window->getSize().x/BASE_RESOLUTION.x, window->getSize().y/BASE_RESOLUTION.y); + + float fps; + sf::Clock clock; + sf::Time previousTime = clock.getElapsedTime(); + sf::Time currentTime; + pixelFont.loadFromFile("./assets/fonts/FreePixel.ttf"); + Label fps_label = Label("0", pixelFont, 14, 10, 10, Label::TOP_LEFT); + + menu = Group(window); + play = Group(window); + + bool show_fps = false; + + load_tower_data(); + create_menu(); + + // while our window is open, keep it open + // this is our draw loop + while( window->isOpen() ) { + window->clear( sf::Color::Black ); // clear the contents of the old frame + // by setting the window to black + + //**************************************** + // ADD ALL OF OUR DRAWING BELOW HERE + //**************************************** + + if (displayGroup == &menu) { + menu.update(); + menu.draw(); + } else if (displayGroup == &play) { + play.update(); + + sf::Vector2f mousePosF(static_cast( sf::Mouse::getPosition().x ), static_cast( sf::Mouse::getPosition().y )); + if (!mouseLeftMenu) { + if (!sidebar->getGlobalBounds().contains(mousePosF)) { + mouseLeftMenu = true; + } + } else { + sf::FloatRect windowRect(window->getPosition().x, window->getPosition().y, window->getSize().x, window->getSize().y); + if (inHand && (sidebar->getGlobalBounds().contains(mousePosF) || !windowRect.contains(mousePosF))) { + play.kill(inHand); + inHand = nullptr; + unpick_tower(); + } + } + + play.draw(); + } + + if (show_fps) { + currentTime = clock.getElapsedTime(); + fps = 1.0f / (currentTime.asSeconds() - previousTime.asSeconds()); // the asSeconds returns a float + previousTime = currentTime; + + fps_label.setString(std::to_string((int)floor(fps))); + window->draw(fps_label); + } + + //**************************************** + // ADD ALL OF OUR DRAWING ABOVE HERE + //**************************************** + + window->display(); // displayGroup the window + + //**************************************** + // HANDLE EVENTS BELOW HERE + //**************************************** + sf::Event event{}; + while( window->pollEvent(event) ) { // ask the window if any events occurred + if( event.type == sf::Event::Closed ) { // if event type is a closed event + // i.e. they clicked the X on the window + window->close(); + } else if ( event.type == sf::Event::MouseButtonReleased ) { + sf::Vector2i mousePos = sf::Mouse::getPosition(*window); + + displayGroup->do_clicked(mousePos); + } else if ( event.type == sf::Event::KeyPressed ) { + if (event.key.code == sf::Keyboard::F1 ) { + show_fps = !show_fps; + } else if ( event.key.code == sf::Keyboard::Escape ) { + if (inHand) { + play.kill(inHand); + inHand = nullptr; + unpick_tower(); + } + } + } + } + } + + clean_up(); +} + +void Game::create_menu() { + startButtonTexture.loadFromFile("./assets/textures/menu/start_button.png"); + quitButtonTexture.loadFromFile("./assets/textures/menu/quit_button.png"); + + menu.empty(); + auto *playButton = new Button(startButtonTexture, window->getSize().x/2.0, window->getSize().y/2.0, ((window->getSize().x / 10.0)/startButtonTexture.getSize().x) * screenRatio.x, Sprite::CENTER, [this] { create_play(); }); + menu.add(playButton); + menu.add(new Button(quitButtonTexture, playButton->getPosition().x, playButton->getPosition().y + 1.5 * playButton->getGlobalBounds().height, playButton->getScale().x, Sprite::CENTER, [this] { end_game(); })); + + displayGroup = &menu; +} + +void Game::create_play() { + load_textures(); + + play.empty(); + while(generate_path() != 0); + generate_obstacles(); + show_dash(); + + displayGroup = &play; +} + +void Game::load_textures() { + placeableDiskTexture.loadFromFile("./assets/textures/gui/placeable_disk.png"); + + selectArrowTextureLeft.loadFromFile("./assets/textures/gui/select_arrow_left.png"); + selectArrowTextureRight.loadFromFile("./assets/textures/gui/select_arrow_right.png"); + + damageTexture.loadFromFile("./assets/textures/gui/damage_icon.png"); + rangeTexture.loadFromFile("./assets/textures/gui/range_icon.png"); + speedTexture.loadFromFile("./assets/textures/gui/attack_speed_icon.png"); + pierceTexture.loadFromFile("./assets/textures/gui/pierce_icon.png"); + splashDamageTexture.loadFromFile("./assets/textures/gui/splash_damage_icon.png"); + splashRangeTexture.loadFromFile("./assets/textures/gui/splash_radius.png"); + + terrainTextures.clear(); + pathTextures.clear(); + towerTextures.clear(); + + //Load terrain textures + for (const std::string &s : open((std::string) "./assets/textures/environments/terrain")) { + if (s.find(std::to_string(environment) + "-") == std::string::npos) continue; + terrainTextures.emplace_back(); + terrainTextures[terrainTextures.size() - 1].loadFromFile("./assets/textures/environments/terrain/" + s); + } + + //Load path textures + for (const std::string &s : open((std::string) "./assets/textures/environments/paths")) { + if (s.find(std::to_string(environment) + "-") == std::string::npos) continue; + pathTextures.emplace_back(); + pathTextures[pathTextures.size() - 1].loadFromFile("./assets/textures/environments/paths/" + s); + } + + //Load obstacle textures + for (std::string &s : open((std::string) "./assets/textures/environments/obstacles")) { + if (s.find(std::to_string(environment) + "-") == std::string::npos || s.find("_top") != std::string::npos) continue; + obstacleBases.emplace_back(); + obstacleBases[obstacleBases.size() - 1].loadFromFile("./assets/textures/environments/obstacles/" + s); + if (s.find("_bottom") != std::string::npos) { + obstacleHasTops.push_back(true); + obstacleTops.emplace_back(); + int index; + while((index = s.find("_bottom")) != std::string::npos) { + s.replace(index, 7, "_top"); //remove and replace from that position + } + obstacleTops[obstacleTops.size() - 1].loadFromFile("./assets/textures/environments/obstacles/" + s); + } else { + obstacleHasTops.push_back(false); + } + } + + //Load tower textures + for (const std::string &s : open((std::string) "./assets/textures/towers")) { + if (s == "." || s == "..") continue; + towerTextures.emplace_back(); + towerTextures[towerTextures.size() - 1].loadFromFile("./assets/textures/towers/" + s); + } +} + +void Game::load_tower_data() { + std::ifstream dataFile("./assets/data/towers.dat"); + towerData.emplace_back(); + + std::string line; + while (std::getline(dataFile, line)) { + if (line.empty()) { + towerData.emplace_back(); + } else { + unsigned int ind = line.find_first_of(' '); + towerData[towerData.size() - 1].insert({line.substr(0, ind), line.substr(ind + 1, line.size() - ind)}); + } + } + + dataFile.close(); +} + +void Game::generate_obstacles() { + static std::mt19937 rng( time(nullptr) ); + float obstaclePerc = DIFFICULTY_OBSTACLE_PERCS[pathDifficulty]/100.0; + float baseScale = 1.5; + float obstacleScale = screenRatio.x * baseScale; + + float area = 0; + while (area/(BASE_RESOLUTION.y * 4*(BASE_RESOLUTION.y/3)) < obstaclePerc) { + int ind = std::uniform_int_distribution(0, obstacleBases.size() - 1)(rng); + sf::Texture t = obstacleBases[ind]; + area += (t.getSize().x * t.getSize().y * baseScale * baseScale); + + auto* obstacle = new Obstacle(obstacleBases[ind]); + obstacle->setOrigin(Sprite::CENTER); + obstacle->setScale(obstacleScale, obstacleScale); + + bool colliding = true; + int angle, x, y; + while (colliding) { + angle = std::uniform_int_distribution(0, 359)(rng); + obstacle->setRotation(angle); + + x = std::uniform_int_distribution(mapCorner + obstacle->getGlobalBounds().width/2.0, window->getSize().x - obstacle->getGlobalBounds().width/2.0)(rng); + y = std::uniform_int_distribution(obstacle->getGlobalBounds().height/2.0, mapBottom - obstacle->getGlobalBounds().height/2.0)(rng); + obstacle->setPosition(x, y); + + colliding = false; + for (auto *s : *play.getLayerSprites(PATH_LAYER)) { + if (obstacle->getGlobalBounds().intersects(s->getGlobalBounds()) && Collision::PixelPerfectTest(*obstacle, *s)) { + colliding = true; + break; + } + } + + if (colliding) { + continue; + } + + //TODO fix this double usage + if (play.getLayerSprites(OBSTACLE_LAYER) != nullptr) { + for (auto *s : *play.getLayerSprites(OBSTACLE_LAYER)) { + if (obstacle->getGlobalBounds().intersects(s->getGlobalBounds()) && Collision::PixelPerfectTest(*obstacle, *s)) { + colliding = true; + break; + } + } + } + } + + play.add(obstacle, OBSTACLE_LAYER); + + if (obstacleHasTops[ind]) { + int canopyInd = 0; + for (int i = 0; i < ind; i++) { + if (obstacleHasTops[i]) { + canopyInd++; + } + } + + play.add(new Canopy(obstacleTops[canopyInd], x, y, std::uniform_int_distribution(0, 359)(rng), obstacleScale, Sprite::CENTER), CANOPY_LAYER); + } + } +} + +int Game::generate_path() { + static std::mt19937 rng( time(nullptr) ); + + int startPoint[2]; + int innerStartPoint[2]; + int endPoint[2]; + int innerEndPoint[2]; + + int numWaypoints = DIFFICULTY_WAYPOINTS[pathDifficulty][std::uniform_int_distribution(0, 1)(rng)]; + int startSide = std::uniform_int_distribution(0, 1)(rng); + if (startSide == 0) { + startPoint[0] = 0; + startPoint[1] = std::uniform_int_distribution(1, 28)(rng); + + innerStartPoint[0] = 1; + innerStartPoint[1] = startPoint[1]; + } else { + startPoint[0] = std::uniform_int_distribution(1, 38)(rng); + startPoint[1] = 0; + + innerStartPoint[0] = startPoint[0]; + innerStartPoint[1] = 1; + } + + int endSide = std::uniform_int_distribution(0, 1)(rng); + if (endSide == 0) { + endPoint[0] = 39; + endPoint[1] = std::uniform_int_distribution(1, 28)(rng); + + innerEndPoint[0] = 38; + innerEndPoint[1] = endPoint[1]; + } else { + endPoint[0] = std::uniform_int_distribution(1, 38)(rng); + endPoint[1] = 29; + + innerEndPoint[0] = endPoint[0]; + innerEndPoint[1] = 28; + } + + waypoints = {sf::Vector2i(startPoint[0], startPoint[1]), sf::Vector2i(innerStartPoint[0], innerStartPoint[1])}; + int prevWaypoint[2] = { innerStartPoint[0], innerStartPoint[1] }; + + for (int i = 0; i < numWaypoints; i++) { + int newWaypoint[2] = { waypoints[waypoints.size() - 1].x, waypoints[waypoints.size() - 1].y }; + while (newWaypoint[0] == prevWaypoint[0] || newWaypoint[1] == prevWaypoint[1] || (i == numWaypoints - 1 && (newWaypoint[0] == innerEndPoint[0] || newWaypoint[1] == innerEndPoint[1]))) { + newWaypoint[0] = std::uniform_int_distribution(1, 38)(rng); + newWaypoint[1] = std::uniform_int_distribution(1, 28)(rng); + } + int endOfLastPath[2] = { waypoints[waypoints.size() - 1].x, waypoints[waypoints.size() - 1].y }; + draw_path(endOfLastPath, newWaypoint); + prevWaypoint[0] = newWaypoint[0]; + prevWaypoint[1] = newWaypoint[1]; + } + + int endOfLastPath[2] = { waypoints[waypoints.size() - 1].x, waypoints[waypoints.size() - 1].y }; + draw_path(endOfLastPath, innerEndPoint); + waypoints.emplace_back(endPoint[0], endPoint[1]); + + if (!distances_ok()) { + return 1; + } + + float tileWidth = window->getSize().y/30.0; + mapCorner = window->getSize().x - tileWidth * 40; + mapBottom = tileWidth * 30; + + for (int i = 0; i < MAP_HEIGHT; i++) { + for (int j = 0; j < MAP_WIDTH; j++) { + float x = mapCorner + j * tileWidth; + float y = i * tileWidth; + + bool found = false; + for (auto &waypoint : waypoints) { + if (waypoint.x == j && waypoint.y == i) { + found = true; + break; + } + } + + if (found) { + play.add(new Path(pathTextures[std::uniform_int_distribution(0, pathTextures.size() - 1)(rng)], x, y, tileWidth / ((float) pathTextures[0].getSize().y), Sprite::TOP_LEFT), PATH_LAYER); + } else { + play.add(new Terrain(terrainTextures[std::uniform_int_distribution(0, terrainTextures.size() - 1)(rng)], x, y, tileWidth / ((float) terrainTextures[0].getSize().y), Sprite::TOP_LEFT), TERRAIN_LAYER); + } + } + } + + return 0; +} + +void Game::draw_path(int *a, int *b) { + static std::mt19937 rng(time(nullptr) ); + + int order = std::uniform_int_distribution(0, 1)(rng); + + if (order == 0) { + for (auto & waypoint : waypoints) { + if (waypoint.x == a[0] && waypoint.y == a[1] - abs(a[1] - b[1])/(a[1] - b[1])) { + order = 1; + break; + } + } + } else { + for (auto & waypoint : waypoints) { + if (waypoint.x == a[0] - abs(a[0] - b[0])/(a[0] - b[0]) && waypoint.y == a[1]) { + order = 0; + break; + } + } + } + + if (order == 0) { + draw_line(a, b, 1); //vertical + int endOfLastPath[2] = { waypoints[waypoints.size() - 1].x, waypoints[waypoints.size() - 1].y }; + draw_line(endOfLastPath, b, 0); //horz + } else { + draw_line(a, b, 0); //horz + int endOfLastPath[2] = { waypoints[waypoints.size() - 1].x, waypoints[waypoints.size() - 1].y }; + draw_line(endOfLastPath, b, 1); //vertical + } +} + +void Game::draw_line(int *a, const int *b, int dir) { + int diff = a[dir] - b[dir]; + + int paired = 1; + if (dir == 1) { + paired = 0; + } + + if (diff != 0) { + int l = 1; + int h = abs(diff) + 1; + + for (int i = l; i < h; i++) { + int tile = i * abs(diff)/diff; + + int newWaypoint[2] = { a[paired] , a[dir] - tile}; + if (dir == 0) { + newWaypoint[0] = a[dir] - tile; + newWaypoint[1] = a[paired]; + } + + waypoints.emplace_back(newWaypoint[0], newWaypoint[1]); + } + } +} + +bool Game::distances_ok() { + int too_short = 0; + for (int i = 0; i < waypoints.size(); i++) { + for (int j = 0; j < waypoints.size(); j++) { + if (abs(i - j) > 1 && sqrt(pow(waypoints[i].x - waypoints[j].x, 2) + pow(waypoints[i].y - waypoints[j].y, 2)) == 1) { + too_short++; + } + } + } + + return too_short <= 10; +} + +void Game::show_dash() { + sidebarTexture.loadFromFile("./assets/textures/gui/backdrop.png"); + energyTexture.loadFromFile("./assets/textures/gui/energy.png"); + gearsTexture.loadFromFile("./assets/textures/gui/gears.png"); + moneyBoxTexture.loadFromFile("./assets/textures/gui/money_box.png"); + livesBoxTexture.loadFromFile("./assets/textures/gui/lives_box.png"); + livesBarTexture.loadFromFile("./assets/textures/gui/health_bar.png"); + towerBoxTexture.loadFromFile("./assets/textures/gui/tower_box.png"); + + sidebar = new Sprite(sidebarTexture, mapCorner, mapBottom, window->getSize().y/((float)sidebarTexture.getSize().y), Sprite::BOTTOM_RIGHT); + play.add(sidebar, GUI_LAYER); + + float tileWidthRatio = (TILE_UNIT / 30.0) * screenRatio.y; + float tileWidth = TILE_UNIT * screenRatio.y; + float moneyBoxHeight = 70 * tileWidthRatio; + float livesBoxHeight = 40 * tileWidthRatio; + float towerBoxHeight = 540 * tileWidthRatio; + float spacing = window->getSize().y / 72; + float total_height = moneyBoxHeight + livesBoxHeight + towerBoxHeight + (2 * spacing); + float gui_start_y = (window->getSize().y - total_height) / 2.0; + float marginRight = 18 * window->getSize().y/((float)sidebarTexture.getSize().y); + + auto moneyBox = new Sprite(moneyBoxTexture, mapCorner - (marginRight + mapCorner + moneyBoxTexture.getSize().x * tileWidthRatio)/2.0, gui_start_y, moneyBoxHeight/((float)moneyBoxTexture.getSize().y), Sprite::TOP_LEFT); + play.add(moneyBox, GUI_LAYER); + auto energyIcon = new Sprite(energyTexture, moneyBox->getPosition().x + tileWidth / 4.0, moneyBox->getPosition().y + tileWidth / 4.0, tileWidth/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(energyIcon, GUI_LAYER); + auto gearsIcon = new Sprite(gearsTexture, moneyBox->getPosition().x + tileWidth / 4.0, moneyBox->getPosition().y + tileWidth * 1.25, tileWidth/((float)gearsTexture.getSize().y), Sprite::TOP_LEFT); + play.add(gearsIcon, GUI_LAYER); + play.add(new VariableLabel(&energy, pixelFont, TILE_UNIT*screenRatio.y, energyIcon->getPosition().x + tileWidth * 1.25, energyIcon->getPosition().y + tileWidth/2.0, Label::LEFT_CENTER), GUI_LAYER); + play.add(new VariableLabel(&gears, pixelFont, TILE_UNIT*screenRatio.y, gearsIcon->getPosition().x + tileWidth * 1.25, gearsIcon->getPosition().y + tileWidth/2.0, Label::LEFT_CENTER), GUI_LAYER); + auto livesBox = new Sprite(livesBoxTexture, mapCorner - (marginRight + mapCorner + livesBoxTexture.getSize().x * tileWidthRatio)/2.0, gui_start_y + moneyBoxHeight + spacing, livesBoxHeight/((float)livesBoxTexture.getSize().y), Sprite::TOP_LEFT); + play.add(livesBox, GUI_LAYER); + play.add(new Sprite(livesBarTexture, livesBox->getPosition().x + livesBox->getGlobalBounds().width/2.0, livesBox->getPosition().y + livesBox->getGlobalBounds().height/2.0, tileWidth/32.0, Sprite::CENTER), GUI_LAYER); + play.add(new Label(std::to_string(health), pixelFont, TILE_UNIT*screenRatio.y, livesBox->getPosition().x + livesBox->getGlobalBounds().width/2.0, livesBox->getPosition().y + livesBox->getGlobalBounds().height/2.0, Label::CENTER), GUI_LAYER); + + auto towerBox = new Sprite(towerBoxTexture, mapCorner - (marginRight + mapCorner + towerBoxTexture.getSize().x * tileWidthRatio)/2.0, gui_start_y + moneyBoxHeight + livesBoxHeight + 2*spacing, towerBoxHeight/((float)towerBoxTexture.getSize().y), Sprite::TOP_LEFT); + play.add(towerBox, GUI_LAYER); + + fill_tower_box(towerBox->getGlobalBounds()); +} + +void Game::fill_tower_box(const sf::Rect& bounds) { + sf::Vector2f pos(bounds.left, bounds.top); + + float towerWidth = bounds.width / 4; + float startOffset = towerWidth / 4; + + towerMenuHoverTexture.loadFromFile("./assets/textures/gui/hover_tower.png"); + + for (int i = 0; i < towerTextures.size(); i++) { + int r = i/3; + int c = i%3; + auto *s = new Sprite(towerMenuHoverTexture, pos.x + startOffset + towerWidth * 1.25 * c, pos.y + startOffset + towerWidth * 1.5 * r, screenRatio.x, Sprite::TOP_LEFT); + play.add(new TowerGUI(towerTextures[i], s->getPosition().x + s->getGlobalBounds().width/2, s->getPosition().y + s->getGlobalBounds().height/2, towerWidth/((float)towerTextures[i].getSize().x), Sprite::CENTER, [this, i, bounds] { pick_tower(i, bounds); }, s), TOWER_BUTTONS_LAYER); + } +} + +void Game::pick_tower(int i, const sf::Rect& towerBoxBounds) { + mouseLeftMenu = false; + + std::vector layers = {PATH_LAYER, OBSTACLE_LAYER, TOWER_LAYER, GUI_LAYER}; + + inHand = new Tower(towerTextures[i], sf::Mouse::getPosition().x, sf::Mouse::getPosition().y, screenRatio.x, Sprite::CENTER, std::stof(towerData[i].at("range")), TILE_UNIT, std::stoi(towerData[i].at("price")), &energy, &inHand, layers, &play, new Sprite(placeableDiskTexture, Sprite::CENTER), [this] { unpick_tower(); }); + Placeable *p = inHand; + inHand->setCommand([this, i, p, towerBoxBounds] { select_tower(i, (Tower *)p, towerBoxBounds); }); + play.add(inHand, TOWER_LAYER); + play.hide_layer(TOWER_BUTTONS_LAYER); + + show_stats(i, towerBoxBounds); +} + +void Game::select_tower(int i, Tower* tower, const sf::Rect& towerBoxBounds) { + play.empty_layer(TOWER_DESC_LAYER); + play.hide_layer(TOWER_BUTTONS_LAYER); + + show_stats(i, tower, towerBoxBounds); +} + +void Game::show_stats(int i, const sf::Rect& towerBoxBounds) { + float iconSpacing = (towerBoxBounds.width - (TILE_UNIT * screenRatio.y) / 2.0)/3.0; + float bottomOffsetMult = 3; + if (towerData[i].find("pierce") != towerData[i].end() || towerData[i].find("splash_radius") != towerData[i].end()) { + bottomOffsetMult = 4; + } + + auto icon = new Sprite(towerTextures[i], towerBoxBounds.left + (TILE_UNIT * screenRatio.y) / 4.0, towerBoxBounds.top + (TILE_UNIT * screenRatio.y) / 2.0, (towerBoxBounds.width/6.0)/((float)towerTextures[i].getSize().x), Sprite::TOP_LEFT); + play.add(icon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["name"], pixelFont, TILE_UNIT*screenRatio.y, icon->getPosition().x + icon->getGlobalBounds().width + (12 * screenRatio.y), icon->getPosition().y + icon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + + play.add(new TextBox(towerData[i]["description"], pixelFont, 16 * screenRatio.y, icon->getPosition().x, icon->getPosition().y + icon->getGlobalBounds().height, towerBoxBounds.width - icon->getPosition().x, Label::TOP_LEFT), TOWER_DESC_LAYER); + + float priceIconY = towerBoxBounds.top + towerBoxBounds.height - (TILE_UNIT * screenRatio.y) * bottomOffsetMult; + float priceIconHeight = TILE_UNIT * screenRatio.y; + + auto energyIcon = new Sprite(energyTexture, towerBoxBounds.left + (TILE_UNIT * screenRatio.y) / 4.0, priceIconY, (TILE_UNIT * screenRatio.y) / ((float) energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(energyIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["price"], pixelFont, TILE_UNIT * screenRatio.y, energyIcon->getPosition().x + energyIcon->getGlobalBounds().width + (12 * screenRatio.y), energyIcon->getPosition().y + energyIcon->getGlobalBounds().height / 2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + + auto damageIcon = new Sprite(damageTexture, towerBoxBounds.left + (TILE_UNIT * screenRatio.y) / 4.0, priceIconY + priceIconHeight + (12 * screenRatio.y), (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(damageIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["damage"], pixelFont, TILE_UNIT*screenRatio.y, damageIcon->getPosition().x + damageIcon->getGlobalBounds().width + (12 * screenRatio.y), damageIcon->getPosition().y + damageIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + auto rangeIcon = new Sprite(rangeTexture, damageIcon->getPosition().x + iconSpacing, damageIcon->getPosition().y, (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(rangeIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["range"], pixelFont, TILE_UNIT*screenRatio.y, rangeIcon->getPosition().x + rangeIcon->getGlobalBounds().width + (12 * screenRatio.y), rangeIcon->getPosition().y + rangeIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + auto speedIcon = new Sprite(speedTexture, rangeIcon->getPosition().x + iconSpacing, damageIcon->getPosition().y, (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(speedIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["speed"], pixelFont, TILE_UNIT*screenRatio.y, speedIcon->getPosition().x + speedIcon->getGlobalBounds().width + (12 * screenRatio.y), speedIcon->getPosition().y + speedIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + + float offset = 0; + if (towerData[i].find("pierce") != towerData[i].end()) { + auto pierceIcon = new Sprite(pierceTexture, damageIcon->getPosition().x, damageIcon->getPosition().y + damageIcon->getGlobalBounds().height + (12 * screenRatio.y), (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(pierceIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["pierce"], pixelFont, TILE_UNIT*screenRatio.y, pierceIcon->getPosition().x + pierceIcon->getGlobalBounds().width + (12 * screenRatio.y), pierceIcon->getPosition().y + pierceIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + offset++; + } + + if (towerData[i].find("splash_radius") != towerData[i].end()) { + auto splashRadiusIcon = new Sprite(splashRangeTexture, damageIcon->getPosition().x + iconSpacing*offset, damageIcon->getPosition().y + damageIcon->getGlobalBounds().height + (12 * screenRatio.y), (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(splashRadiusIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["splash_radius"], pixelFont, TILE_UNIT*screenRatio.y, splashRadiusIcon->getPosition().x + splashRadiusIcon->getGlobalBounds().width + (12 * screenRatio.y), splashRadiusIcon->getPosition().y + splashRadiusIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + offset++; + + auto splashDmgIcon = new Sprite(splashDamageTexture, damageIcon->getPosition().x + iconSpacing*offset, damageIcon->getPosition().y + damageIcon->getGlobalBounds().height + (12 * screenRatio.y), (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(splashDmgIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["splash_damage"], pixelFont, TILE_UNIT*screenRatio.y, splashDmgIcon->getPosition().x + splashDmgIcon->getGlobalBounds().width + (12 * screenRatio.y), splashDmgIcon->getPosition().y + splashDmgIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + } +} + +void Game::show_stats(int i, Tower* tower, const sf::Rect& towerBoxBounds) { + float iconSpacing = (towerBoxBounds.width - (TILE_UNIT * screenRatio.y) / 2.0)/3.0; + float bottomOffsetMult = 3; + if (towerData[i].find("pierce") != towerData[i].end() || towerData[i].find("splash_radius") != towerData[i].end()) { + bottomOffsetMult = 4; + } + + auto icon = new Sprite(towerTextures[i], towerBoxBounds.left + (TILE_UNIT * screenRatio.y) / 4.0, towerBoxBounds.top + (TILE_UNIT * screenRatio.y) / 2.0, (towerBoxBounds.width/6.0)/((float)towerTextures[i].getSize().x), Sprite::TOP_LEFT); + play.add(icon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["name"], pixelFont, TILE_UNIT*screenRatio.y, icon->getPosition().x + icon->getGlobalBounds().width + (12 * screenRatio.y), icon->getPosition().y + icon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + + float descY = icon->getPosition().y + icon->getGlobalBounds().height; + + focus1Label = new Label(foci_strings[tower->getFocus1()], pixelFont, 20 * screenRatio.y, towerBoxBounds.left + towerBoxBounds.width/2.0, descY, Label::CENTER); + play.add(focus1Label, TOWER_DESC_LAYER); + play.add(new Button(selectArrowTextureLeft, focus1Label->getPosition().x - 1.5*TILE_UNIT*screenRatio.y, focus1Label->getPosition().y, (20*screenRatio.y)/((float)selectArrowTextureLeft.getSize().y), Button::CENTER, [this, tower] { prev_focus(1, tower); }), TOWER_DESC_LAYER); + play.add(new Button(selectArrowTextureRight, focus1Label->getPosition().x + 1.5*TILE_UNIT*screenRatio.y, focus1Label->getPosition().y, (20*screenRatio.y)/((float)selectArrowTextureLeft.getSize().y), Button::CENTER, [this, tower] { next_focus(1, tower); }), TOWER_DESC_LAYER); + focus2Label = new Label(foci_strings[tower->getFocus2()], pixelFont, 20 * screenRatio.y, towerBoxBounds.left + towerBoxBounds.width/2.0, focus1Label->getPosition().y + focus1Label->getCharacterSize() + 6*screenRatio.y, Label::CENTER); + play.add(focus2Label, TOWER_DESC_LAYER); + play.add(new Button(selectArrowTextureLeft, focus2Label->getPosition().x - 1.5*TILE_UNIT*screenRatio.y, focus2Label->getPosition().y, (20*screenRatio.y)/((float)selectArrowTextureLeft.getSize().y), Button::CENTER, [this, tower] { prev_focus(2, tower); }), TOWER_DESC_LAYER); + play.add(new Button(selectArrowTextureRight, focus2Label->getPosition().x + 1.5*TILE_UNIT*screenRatio.y, focus2Label->getPosition().y, (20*screenRatio.y)/((float)selectArrowTextureLeft.getSize().y), Button::CENTER, [this, tower] { next_focus(2, tower); }), TOWER_DESC_LAYER); + play.add(new Label(foci_strings[0], pixelFont, 20 * screenRatio.y, towerBoxBounds.left + towerBoxBounds.width/2.0, focus2Label->getPosition().y + focus2Label->getCharacterSize() + 6*screenRatio.y, Label::CENTER), TOWER_DESC_LAYER); + descY = descY + 2*TILE_UNIT * screenRatio.y; + + play.add(new TextBox(towerData[i]["description"], pixelFont, 16 * screenRatio.y, icon->getPosition().x, descY, towerBoxBounds.width - icon->getPosition().x, Label::TOP_LEFT), TOWER_DESC_LAYER); + + float priceIconY = towerBoxBounds.top + towerBoxBounds.height - (TILE_UNIT * screenRatio.y) * bottomOffsetMult; + float priceIconHeight = TILE_UNIT * screenRatio.y; + + auto damageIcon = new Sprite(damageTexture, towerBoxBounds.left + (TILE_UNIT * screenRatio.y) / 4.0, priceIconY + priceIconHeight + (12 * screenRatio.y), (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(damageIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["damage"], pixelFont, TILE_UNIT*screenRatio.y, damageIcon->getPosition().x + damageIcon->getGlobalBounds().width + (12 * screenRatio.y), damageIcon->getPosition().y + damageIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + auto rangeIcon = new Sprite(rangeTexture, damageIcon->getPosition().x + iconSpacing, damageIcon->getPosition().y, (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(rangeIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["range"], pixelFont, TILE_UNIT*screenRatio.y, rangeIcon->getPosition().x + rangeIcon->getGlobalBounds().width + (12 * screenRatio.y), rangeIcon->getPosition().y + rangeIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + auto speedIcon = new Sprite(speedTexture, rangeIcon->getPosition().x + iconSpacing, damageIcon->getPosition().y, (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(speedIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["speed"], pixelFont, TILE_UNIT*screenRatio.y, speedIcon->getPosition().x + speedIcon->getGlobalBounds().width + (12 * screenRatio.y), speedIcon->getPosition().y + speedIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + + float offset = 0; + if (towerData[i].find("pierce") != towerData[i].end()) { + auto pierceIcon = new Sprite(pierceTexture, damageIcon->getPosition().x, damageIcon->getPosition().y + damageIcon->getGlobalBounds().height + (12 * screenRatio.y), (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(pierceIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["pierce"], pixelFont, TILE_UNIT*screenRatio.y, pierceIcon->getPosition().x + pierceIcon->getGlobalBounds().width + (12 * screenRatio.y), pierceIcon->getPosition().y + pierceIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + offset++; + } + + if (towerData[i].find("splash_radius") != towerData[i].end()) { + auto splashRadiusIcon = new Sprite(splashRangeTexture, damageIcon->getPosition().x + iconSpacing*offset, damageIcon->getPosition().y + damageIcon->getGlobalBounds().height + (12 * screenRatio.y), (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(splashRadiusIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["splash_radius"], pixelFont, TILE_UNIT*screenRatio.y, splashRadiusIcon->getPosition().x + splashRadiusIcon->getGlobalBounds().width + (12 * screenRatio.y), splashRadiusIcon->getPosition().y + splashRadiusIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + offset++; + + auto splashDmgIcon = new Sprite(splashDamageTexture, damageIcon->getPosition().x + iconSpacing*offset, damageIcon->getPosition().y + damageIcon->getGlobalBounds().height + (12 * screenRatio.y), (TILE_UNIT * screenRatio.y)/((float)energyTexture.getSize().y), Sprite::TOP_LEFT); + play.add(splashDmgIcon, TOWER_DESC_LAYER); + play.add(new Label(towerData[i]["splash_damage"], pixelFont, TILE_UNIT*screenRatio.y, splashDmgIcon->getPosition().x + splashDmgIcon->getGlobalBounds().width + (12 * screenRatio.y), splashDmgIcon->getPosition().y + splashDmgIcon->getGlobalBounds().height/2.0, Label::LEFT_CENTER), TOWER_DESC_LAYER); + } +} + +void Game::prev_focus(int focus, Tower* tower) { + if (focus == 1) { + tower->setFocus1(wrap(tower->getFocus1() - 1, 0, 10)); + focus1Label->setString(foci_strings[tower->getFocus1()]); + } else { + tower->setFocus2(wrap(tower->getFocus2() - 1, 0, 10)); + focus2Label->setString(foci_strings[tower->getFocus2()]); + } +} + +void Game::next_focus(int focus, Tower* tower) { + if (focus == 1) { + tower->setFocus1(wrap(tower->getFocus1() + 1, 0, 10)); + focus1Label->setString(foci_strings[tower->getFocus1()]); + } else { + tower->setFocus2(wrap(tower->getFocus2() + 1, 0, 10)); + focus2Label->setString(foci_strings[tower->getFocus2()]); + } +} + +void Game::unpick_tower() { + play.empty_layer(TOWER_DESC_LAYER); + play.show_layer(TOWER_BUTTONS_LAYER); +} + +void Game::end_game() { + window->close(); +} + +void Game::clean_up() { + menu.empty(); + play.empty(); + delete window; +} diff --git a/Game.h b/Game.h new file mode 100644 index 0000000..6f53411 --- /dev/null +++ b/Game.h @@ -0,0 +1,128 @@ +// +// Created by Benjamin on 1/25/2022. +// + +#ifndef SFML_TEMPLATE_GAME_H +#define SFML_TEMPLATE_GAME_H + +#include "Group.h" +#include "Terrain.h" +#include "Placeable.h" +#include "Tower.h" + +#include +#include + +class Game { +public: + const sf::Vector2f BASE_RESOLUTION = sf::Vector2f(1920, 1080); + + Game(); +private: + static const int MAP_HEIGHT = 30; + static const int MAP_WIDTH = 40; + + static const int EASY = 1; + static const int MEDIUM = 2; + static const int HARD = 3; + static const int EXPERT = 4; + const int DIFFICULTY_WAYPOINTS[4][2] = {{5, 6}, {3, 4}, {1, 2}, {0, 1}}; + const int DIFFICULTY_OBSTACLE_PERCS[4] = {5, 10, 15, 20}; + + static const int PLAINS = 1; + + static const int TERRAIN_LAYER = 0; + static const int PATH_LAYER = 1; + static const int OBSTACLE_LAYER = 2; + static const int TOWER_LAYER = 3; + static const int CANOPY_LAYER = 4; + static const int GUI_LAYER = 5; + static const int TOWER_BUTTONS_LAYER = 6; + static const int TOWER_DESC_LAYER = 7; + + static const int TILE_UNIT = 36; + + const std::string foci_strings[10] = {"First", "Last", "Strong", "Weak", "Fast", "Slow", "In-file", "Clustered", "Healthy", "Unhealthy"}; + + int pathDifficulty = EASY; + int environment = PLAINS; + + std::vector waypoints; + float mapCorner; + float mapBottom; + + int energy = 100, gears = 100, health = 100; + + Placeable* inHand = nullptr; + Sprite* sidebar; + bool mouseLeftMenu = false; + + Label *focus1Label, *focus2Label; + + sf::Vector2f screenRatio; + + sf::RenderWindow *window; + Group *displayGroup; + + Group menu; + Group play; + + sf::Texture startButtonTexture; + sf::Texture quitButtonTexture; + sf::Texture selectArrowTextureLeft; + sf::Texture selectArrowTextureRight; + + sf::Texture sidebarTexture; + sf::Texture energyTexture; + sf::Texture gearsTexture; + sf::Texture damageTexture; + sf::Texture rangeTexture; + sf::Texture speedTexture; + sf::Texture pierceTexture; + sf::Texture splashRangeTexture; + sf::Texture splashDamageTexture; + sf::Texture moneyBoxTexture; + sf::Texture livesBoxTexture; + sf::Texture livesBarTexture; + sf::Texture towerBoxTexture; + std::vector terrainTextures; + std::vector pathTextures; + std::vector obstacleBases; + std::vector obstacleHasTops; + std::vector obstacleTops; + std::vector towerTextures; + sf::Texture towerMenuHoverTexture; + sf::Texture placeableDiskTexture; + + std::vector> towerData; + + sf::Font pixelFont; + + void create_menu(); + void create_play(); + void load_textures(); + void load_tower_data(); + + void generate_obstacles(); + void show_dash(); + void fill_tower_box(const sf::Rect&); + + int generate_path(); + void draw_path(int *, int *); + void draw_line(int *, const int *, int dir); + bool distances_ok(); + + void pick_tower(int, const sf::Rect&); + void select_tower(int, Tower*, const sf::Rect&); + void show_stats(int, const sf::Rect&); + void show_stats(int, Tower*, const sf::Rect&); + void prev_focus(int, Tower*); + void next_focus(int, Tower*); + void unpick_tower(); + + void end_game(); + void clean_up(); +}; + + +#endif //SFML_TEMPLATE_GAME_H diff --git a/Group.cpp b/Group.cpp new file mode 100644 index 0000000..9d158e4 --- /dev/null +++ b/Group.cpp @@ -0,0 +1,185 @@ +// +// Created by Benjamin on 1/25/2022. +// + +#include +#include "Group.h" + +Group::Group() { + window = nullptr; +} + +Group::Group(sf::RenderWindow *window) { + this->window = window; +} + +std::vector * Group::getLayerSprites(unsigned int l) { + if (layers.size() > l) + return &sprites[l]; + else + return nullptr; +} + +std::vector