From b49080f675fb6ea6c342a29244747df181fb2021 Mon Sep 17 00:00:00 2001 From: bMorgan01 Date: Tue, 8 Mar 2022 10:18:34 -0700 Subject: [PATCH] init --- .gitignore | 8 + Button.cpp | 33 + Button.h | 24 + Canopy.cpp | 19 + Canopy.h | 20 + Collision.cpp | 190 +++++ Collision.h | 77 ++ Game.cpp | 696 ++++++++++++++++++ Game.h | 128 ++++ Group.cpp | 185 +++++ Group.h | 39 + Label.cpp | 31 + Label.h | 30 + Obstacle.cpp | 7 + Obstacle.h | 17 + Path.cpp | 7 + Path.h | 18 + Placeable.cpp | 100 +++ Placeable.h | 39 + Sprite.cpp | 87 +++ Sprite.h | 48 ++ Terrain.cpp | 7 + Terrain.h | 17 + TextBox.cpp | 31 + TextBox.h | 16 + Tower.cpp | 25 + Tower.h | 34 + TowerGUI.cpp | 30 + TowerGUI.h | 20 + VariableLabel.cpp | 13 + VariableLabel.h | 20 + assets/data/towers.dat | 60 ++ assets/fonts/FreePixel.ttf | Bin 0 -> 64880 bytes assets/textures/character/character.png | Bin 0 -> 869 bytes .../environments/obstacles/log1-1.png | Bin 0 -> 1008 bytes .../environments/obstacles/puddle1-1.png | Bin 0 -> 730 bytes .../environments/obstacles/rock1-1.png | Bin 0 -> 1123 bytes .../environments/obstacles/rock1-2.png | Bin 0 -> 1117 bytes .../environments/obstacles/rock1-3.png | Bin 0 -> 746 bytes .../obstacles/stump1-1_bottom.png | Bin 0 -> 764 bytes .../environments/obstacles/stump1-1_top.png | Bin 0 -> 1012 bytes .../obstacles/stump1-2_bottom.png | Bin 0 -> 764 bytes .../environments/obstacles/stump1-2_top.png | Bin 0 -> 1013 bytes .../environments/obstacles/stump1-3.png | Bin 0 -> 764 bytes .../textures/environments/paths/path1-1.png | Bin 0 -> 1502 bytes .../textures/environments/paths/path1-2.png | Bin 0 -> 1399 bytes .../textures/environments/paths/path1-3.png | Bin 0 -> 1421 bytes .../textures/environments/paths/path1-4.png | Bin 0 -> 1489 bytes .../textures/environments/paths/path1-5.png | Bin 0 -> 1509 bytes .../environments/terrain/terrain1-1.png | Bin 0 -> 1192 bytes .../environments/terrain/terrain1-2.png | Bin 0 -> 1199 bytes .../environments/terrain/terrain1-3.png | Bin 0 -> 1189 bytes .../environments/terrain/terrain1-4.png | Bin 0 -> 1097 bytes .../environments/terrain/terrain1-5.png | Bin 0 -> 1102 bytes assets/textures/gui/attack_speed_icon.png | Bin 0 -> 991 bytes assets/textures/gui/backdrop.png | Bin 0 -> 10883 bytes assets/textures/gui/damage_icon.png | Bin 0 -> 1331 bytes assets/textures/gui/energy.png | Bin 0 -> 840 bytes assets/textures/gui/gears.png | Bin 0 -> 1032 bytes assets/textures/gui/health_bar.png | Bin 0 -> 1021 bytes assets/textures/gui/hover_tower.png | Bin 0 -> 965 bytes assets/textures/gui/lives_box.png | Bin 0 -> 1002 bytes assets/textures/gui/melee_icon.png | Bin 0 -> 1174 bytes assets/textures/gui/money_box.png | Bin 0 -> 1288 bytes assets/textures/gui/pierce_icon.png | Bin 0 -> 1270 bytes assets/textures/gui/placeable_disk.png | Bin 0 -> 23824 bytes assets/textures/gui/proj_speed_icon.png | Bin 0 -> 1073 bytes assets/textures/gui/range_icon.png | Bin 0 -> 1156 bytes assets/textures/gui/ranged_icon.png | Bin 0 -> 1242 bytes assets/textures/gui/select_arrow_left.png | Bin 0 -> 6368 bytes assets/textures/gui/select_arrow_right.png | Bin 0 -> 6311 bytes assets/textures/gui/splash_damage_icon.png | Bin 0 -> 1180 bytes assets/textures/gui/splash_radius.png | Bin 0 -> 1125 bytes assets/textures/gui/tower_box.png | Bin 0 -> 5526 bytes assets/textures/menu/quit_button.png | Bin 0 -> 1340 bytes assets/textures/menu/start_button.png | Bin 0 -> 1332 bytes assets/textures/placeables/bait_trap.png | Bin 0 -> 1032 bytes assets/textures/towers/1_caveman.png | Bin 0 -> 866 bytes assets/textures/towers/1_rock_thrower.png | Bin 0 -> 1030 bytes assets/textures/towers/1_spear_thrower.png | Bin 0 -> 904 bytes assets/textures/towers/2_archer.png | Bin 0 -> 1078 bytes assets/textures/towers/2_swordsman.png | Bin 0 -> 878 bytes main.cpp | 7 + 83 files changed, 2083 insertions(+) create mode 100644 Button.cpp create mode 100644 Button.h create mode 100644 Canopy.cpp create mode 100644 Canopy.h create mode 100644 Collision.cpp create mode 100644 Collision.h create mode 100644 Game.cpp create mode 100644 Game.h create mode 100644 Group.cpp create mode 100644 Group.h create mode 100644 Label.cpp create mode 100644 Label.h create mode 100644 Obstacle.cpp create mode 100644 Obstacle.h create mode 100644 Path.cpp create mode 100644 Path.h create mode 100644 Placeable.cpp create mode 100644 Placeable.h create mode 100644 Sprite.cpp create mode 100644 Sprite.h create mode 100644 Terrain.cpp create mode 100644 Terrain.h create mode 100644 TextBox.cpp create mode 100644 TextBox.h create mode 100644 Tower.cpp create mode 100644 Tower.h create mode 100644 TowerGUI.cpp create mode 100644 TowerGUI.h create mode 100644 VariableLabel.cpp create mode 100644 VariableLabel.h create mode 100644 assets/data/towers.dat create mode 100644 assets/fonts/FreePixel.ttf create mode 100644 assets/textures/character/character.png create mode 100644 assets/textures/environments/obstacles/log1-1.png create mode 100644 assets/textures/environments/obstacles/puddle1-1.png create mode 100644 assets/textures/environments/obstacles/rock1-1.png create mode 100644 assets/textures/environments/obstacles/rock1-2.png create mode 100644 assets/textures/environments/obstacles/rock1-3.png create mode 100644 assets/textures/environments/obstacles/stump1-1_bottom.png create mode 100644 assets/textures/environments/obstacles/stump1-1_top.png create mode 100644 assets/textures/environments/obstacles/stump1-2_bottom.png create mode 100644 assets/textures/environments/obstacles/stump1-2_top.png create mode 100644 assets/textures/environments/obstacles/stump1-3.png create mode 100644 assets/textures/environments/paths/path1-1.png create mode 100644 assets/textures/environments/paths/path1-2.png create mode 100644 assets/textures/environments/paths/path1-3.png create mode 100644 assets/textures/environments/paths/path1-4.png create mode 100644 assets/textures/environments/paths/path1-5.png create mode 100644 assets/textures/environments/terrain/terrain1-1.png create mode 100644 assets/textures/environments/terrain/terrain1-2.png create mode 100644 assets/textures/environments/terrain/terrain1-3.png create mode 100644 assets/textures/environments/terrain/terrain1-4.png create mode 100644 assets/textures/environments/terrain/terrain1-5.png create mode 100644 assets/textures/gui/attack_speed_icon.png create mode 100644 assets/textures/gui/backdrop.png create mode 100644 assets/textures/gui/damage_icon.png create mode 100644 assets/textures/gui/energy.png create mode 100644 assets/textures/gui/gears.png create mode 100644 assets/textures/gui/health_bar.png create mode 100644 assets/textures/gui/hover_tower.png create mode 100644 assets/textures/gui/lives_box.png create mode 100644 assets/textures/gui/melee_icon.png create mode 100644 assets/textures/gui/money_box.png create mode 100644 assets/textures/gui/pierce_icon.png create mode 100644 assets/textures/gui/placeable_disk.png create mode 100644 assets/textures/gui/proj_speed_icon.png create mode 100644 assets/textures/gui/range_icon.png create mode 100644 assets/textures/gui/ranged_icon.png create mode 100644 assets/textures/gui/select_arrow_left.png create mode 100644 assets/textures/gui/select_arrow_right.png create mode 100644 assets/textures/gui/splash_damage_icon.png create mode 100644 assets/textures/gui/splash_radius.png create mode 100644 assets/textures/gui/tower_box.png create mode 100644 assets/textures/menu/quit_button.png create mode 100644 assets/textures/menu/start_button.png create mode 100644 assets/textures/placeables/bait_trap.png create mode 100644 assets/textures/towers/1_caveman.png create mode 100644 assets/textures/towers/1_rock_thrower.png create mode 100644 assets/textures/towers/1_spear_thrower.png create mode 100644 assets/textures/towers/2_archer.png create mode 100644 assets/textures/towers/2_swordsman.png create mode 100644 main.cpp 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