diff --git a/.gitignore b/.gitignore index f147edf..747a309 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,8 @@ compile_commands.json # QtCreator local machine specific files for imported projects *creator.user* + + + +/.idea/ +!Controller/ui_controller.h \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fb75bbb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,81 @@ +cmake_minimum_required(VERSION 3.20) +project(Game) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +#set(CMAKE_AUTOUIC ON) + +find_package(Qt6 COMPONENTS + Core + Gui + Widgets + REQUIRED) + +set(RESOURCES + Resources/resources.qrc) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(GAME_OBJECTS + GameObjects/Interface/damageable.cpp + GameObjects/Interface/entity.cpp + GameObjects/Entities/Mobs/Basis/mob.cpp + GameObjects/Entities/Towers/tower.cpp + GameObjects/Entities/Towers/TowerSlots/tower_slot.cpp + GameObjects/Entities/Projectiles/autoguided_projectile.cpp + GameObjects/Entities/Projectiles/magic_projectile.cpp + GameObjects/Interface/graphics_object.cpp + GameObjects/Entities/Projectiles/projectile.cpp + GameObjects/Entities/Mobs/skeleton.cpp + GameObjects/Entities/Mobs/cobra.cpp + GameObjects/Entities/Mobs/hedgehog.cpp + GameObjects/Entities/Mobs/dwarf.cpp + GameObjects/Entities/Towers/magic_tower.cpp + GameObjects/Entities/Towers/cannon_tower.cpp + GameObjects/explosion.cpp + GameObjects/Entities/Traps/bear_trap.cpp + GameObjects/Entities/Traps/bomb.cpp + GameObjects/coin.cpp + GameObjects/Entities/Projectiles/linear_autoguided_projectile.cpp + GameObjects/Entities/Projectiles/cannon_projectile.cpp) + +set(UTILITIES + Utilities/damage.cpp + Utilities/time.cpp + Utilities/vector_f.cpp + Utilities/Resources/pixmap_loader.cpp + Utilities/timer.cpp + Utilities/route.cpp + Utilities/wave.cpp + wave_manager.cpp + Utilities/animation.cpp + Utilities/utility.cpp + Utilities/randomaizer.cpp) + +set(UI + UI/tooltip.cpp + UI/textured_box.cpp + UI/linear_menu.cpp + UI/linear_layout.cpp + UI/button.cpp + UI/padding_box.cpp + UI/pixmap_object.cpp + UI/pixmap_object.h + UI/resource_displayer.cpp) + +add_executable(Game + ${RESOURCES} + ${GAME_OBJECTS} + ${UTILITIES} + ${UI} + main.cpp + main_window.cpp + Controller/controller.cpp + Controller/ui_controller.cpp + game_view.cpp + constants.cpp + game_scene.cpp + level.cpp) + +target_link_libraries(Game Qt::Core Qt::Gui Qt::Widgets) \ No newline at end of file diff --git a/Controller/controller.cpp b/Controller/controller.cpp new file mode 100644 index 0000000..3cbf43b --- /dev/null +++ b/Controller/controller.cpp @@ -0,0 +1,160 @@ +#include "Controller/controller.h" + +#include +#include +#include +#include +#include +#include + +#include "GameObjects/Entities/Mobs/Basis/mob.h" +#include "GameObjects/Entities/Mobs/skeleton.h" +#include "GameObjects/Entities/Mobs/hedgehog.h" +#include "GameObjects/Entities/Mobs/cobra.h" +#include "GameObjects/Entities/Mobs/dwarf.h" +#include "GameObjects/explosion.h" +#include "constants.h" +#include "UI/button.h" +#include "UI/linear_menu.h" +#include "UI/padding_box.h" +#include "Utilities/Resources/pixmap_loader.h" + +Controller* Controller::instance; + +Controller::Controller() : + scene_(new GameScene(Scene::kRect)), + view_(new GameView(scene_)), + tick_timer_(new QTimer(this)), + level_(new Level(1)), + base_hp_(5), + balance_(kStartBalance), + damage_per_current_tick_(0), + resource_displayer_(nullptr) { + SetupScene(); + SetupInterface(); + LaunchTickTimer(); + + connect(this, &Controller::GameOver, [this]() { + scene_->addItem(new Dwarf({100, 100})); + // it's needed, but it also blocks close button + // view_->setInteractive(false); + tick_timer_->stop(); + QGraphicsTextItem* game_over_item = new QGraphicsTextItem(); + game_over_item->setPlainText("Game Over"); + QFont font = game_over_item->font(); + font.setPixelSize(150); + game_over_item->setFont(font); + game_over_item->setPos( + -game_over_item->boundingRect().width() / 2, + -game_over_item->boundingRect().height() / 2); + game_over_item->setZValue(1000000); + scene_->addItem(game_over_item); + }); +} + +GameView* Controller::GetView() const { + return view_; +} + +GameScene* Controller::GetScene() const { + return scene_; +} + +Level* Controller::GetLevel() const { + return level_; +} + +void Controller::SetupScene() { + { // temporary code + TextButton* quit_button = new TextButton({0, 0}, "Quit"); + auto quit_button_font = quit_button->GetTextDocument()->defaultFont(); + quit_button_font.setPixelSize(20); + quit_button->GetTextDocument()->setDefaultFont(quit_button_font); + quit_button->setPos( + scene_->sceneRect().topRight() + - quit_button->boundingRect().topRight() + - VectorF(5, -5)); + connect(quit_button, &TextButton::Clicked, [](){ QApplication::exit(); }); + scene_->addItem(quit_button); + } // temporary code end + + level_->AddObjectsToScene(scene_); +} + +void Controller::LaunchTickTimer() { + tick_timer_->setInterval(1000 / kFPS); + tick_timer_->start(); + connect(tick_timer_, &QTimer::timeout, this, &Controller::TickAllTickables); +} + +Controller* Controller::Instance() { + if (instance == nullptr) { + instance = new Controller(); + } + return instance; +} + +void Controller::TickAllTickables() { + // because we don't want to emit GameOver more than one time + assert(base_hp_ > 0); + + Time delta = Time(1000 / kFPS); + for (QGraphicsItem* graphics_item : scene_->items()) { + if (Tickable* tickable = dynamic_cast(graphics_item)) { + // TODO(jansenin): make time dependency(it + // could have been more than 1000/30 ms) + tickable->Tick(delta); + } + } + if (level_->IsTimeForGrow()) { + for (QGraphicsItem* graphics_item : scene_->items()) { + if (Mob* mob = dynamic_cast(graphics_item)) { + mob->TimeToGrow(); + } + } + } + level_->Tick(delta); + + base_hp_ -= damage_per_current_tick_; + resource_displayer_->SetHp(base_hp_); + damage_per_current_tick_ = 0; + if (base_hp_ <= 0) { + base_hp_ = 0; + emit GameOver(); + } +} + +void Controller::DealDamageToBase(int damage) { + damage_per_current_tick_ += damage; +} + +void Controller::SetupInterface() { + resource_displayer_ = new ResourcesDisplayer(); + resource_displayer_->SetMoney(balance_); + resource_displayer_->SetHp(base_hp_); + resource_displayer_->SetOriginPoint(TexturedBox::OriginPoint::kTopLeft); + resource_displayer_->setPos(scene_->sceneRect().topLeft() + VectorF(5, 5)); + scene_->addItem(resource_displayer_); +} + +void Controller::AddMoney(int money) { + balance_ += money; + resource_displayer_->SetMoney(balance_); +} + +void Controller::LoseMoney(int money) { + balance_ -= money; + resource_displayer_->SetMoney(balance_); +} + +int Controller::GetBalance() { + return balance_; +} + +bool Controller::HaveEnoughMoney(int money) { + if (money > balance_) { + return false; + } + return true; +} + diff --git a/Controller/controller.h b/Controller/controller.h new file mode 100644 index 0000000..7f361a8 --- /dev/null +++ b/Controller/controller.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#include "GameObjects/Interface/entity.h" +#include "GameObjects/coin.h" +#include "game_view.h" +#include "game_scene.h" +#include "level.h" +#include "constants.h" +#include "UI/resource_displayer.h" + +class Controller : public QObject { + Q_OBJECT + + public: + static Controller* Instance(); + + [[nodiscard]] GameView* GetView() const; + [[nodiscard]] GameScene* GetScene() const; + [[nodiscard]] Level* GetLevel() const; + + void DealDamageToBase(int damage); + + void SetupInterface(); + + void AddMoney(int money); + void LoseMoney(int money); + int GetBalance(); + bool HaveEnoughMoney(int money); + + signals: + void GameOver(); + + public slots: + void TickAllTickables(); + + private: + static Controller* instance; + + Controller(); + + void SetupScene(); + void LaunchTickTimer(); + + void RegulateMoney(); + + GameScene* scene_; + GameView* view_; + QTimer* tick_timer_; + Level* level_; + int balance_; + + int base_hp_; + int damage_per_current_tick_; + ResourcesDisplayer* resource_displayer_; +}; diff --git a/Controller/ui_controller.cpp b/Controller/ui_controller.cpp new file mode 100644 index 0000000..b7c9c25 --- /dev/null +++ b/Controller/ui_controller.cpp @@ -0,0 +1 @@ +#include "ui_controller.h" diff --git a/Controller/ui_controller.h b/Controller/ui_controller.h new file mode 100644 index 0000000..c19adeb --- /dev/null +++ b/Controller/ui_controller.h @@ -0,0 +1,3 @@ +#pragma once + +class UIController {}; diff --git a/GameObjects/Entities/Mobs/Basis/mob.cpp b/GameObjects/Entities/Mobs/Basis/mob.cpp new file mode 100644 index 0000000..4f63333 --- /dev/null +++ b/GameObjects/Entities/Mobs/Basis/mob.cpp @@ -0,0 +1,127 @@ +#include "mob.h" + +#include +#include "Controller/controller.h" +#include "Utilities/randomaizer.h" +#include "GameObjects/coin.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +Mob::Mob(const VectorF& coordinates, + Animation* animation, + int health, + int damage_to_base, + qreal speed) + : Entity(coordinates, animation, health), + speed_(speed), + growing_time_(Time(0)), + growing_step_(0), + dealed_damage_to_base_(false), + damage_to_base_(damage_to_base) {} + +Mob::Mob(const VectorF& coordinates, + QPixmap* pixmap, + int health, + int damage_to_base, + qreal speed) + : Entity(coordinates, new Animation(pixmap), health), + speed_(speed), + growing_time_(Time(0)), + growing_step_(0), + dealed_damage_to_base_(false), + damage_to_base_(damage_to_base) {} + +qreal Mob::GetSpeed() const { + return speed_; +} + +void Mob::SetSpeed(qreal speed) { + speed_ = speed; +} + +void Mob::SetRoute(Route* route) { + if (route == nullptr && route_ != nullptr) { + route_->RemoveEntity(this); + } + std::vector points = route->GetPoints(); + for (int i = 0; i < points.size(); ++i) { + points[i].setX(points[i].x() + Randomaizer::Random() % 50); + points[i].setY(points[i].y() + Randomaizer::Random() % 50); + } + route_ = new Route(points); + if (route_ != nullptr) { + route_->AddEntity(this); + } +} + +void Mob::MoveToRouteStart() { + Entity::setPos(route_->GetStart()); +} + +void Mob::Tick(Time delta) { + Entity::Tick(delta); + + if (route_ != nullptr) { + if (route_->isEnd(this) && !dealed_damage_to_base_) { + dealed_damage_to_base_ = true; + Controller::Instance()->DealDamageToBase(damage_to_base_); + } + } + if (growing_time_.ms() > 0) { + setScale(scale() + growing_step_ * delta.ms()); + growing_time_ -= delta; + } + update(); +} + +Mob::~Mob() { + if (route_ != nullptr) { + route_->RemoveEntity(this); + } + if (rand() % Entities::kCoinAppearChance == 1) { //NOLINT + scene()->addItem(new Coin(VectorF(pos().x(), pos().y()), + PixmapLoader::Pixmaps::kCoinAnimations)); + } + delete route_; +} + +QRectF Mob::boundingRect() const { + QRectF result = Entity::boundingRect(); + result.translate(0, -result.height() / 2); + return result; +} + +void Mob::TimeToGrow() { + int grow_chance = std::abs(Randomaizer::Random()) % 6; + qreal grow_index = 0; + switch (grow_chance) { + case 0 : { + grow_index = 1.0; + break; + } + case 1 : { + grow_index = 1.0; + break; + } + case 2 : { + grow_index = 1.0; + break; + } + case 3 : { + grow_index = 1.1; + break; + } + case 4 : { + grow_index = 1.3; + break; + } + case 5 : { + grow_index = 1.5; + break; + } + } + health_ = static_cast(grow_index * static_cast(health_)); + growing_step_ = (grow_index * scale() - scale()) / + Entities::kGrowingSpeed.ms(); + growing_time_ = Entities::kGrowingSpeed; +} diff --git a/GameObjects/Entities/Mobs/Basis/mob.h b/GameObjects/Entities/Mobs/Basis/mob.h new file mode 100644 index 0000000..06beac0 --- /dev/null +++ b/GameObjects/Entities/Mobs/Basis/mob.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include "GameObjects/Interface/entity.h" +#include "Utilities/vector_f.h" +#include "Utilities/route.h" + +class Mob : public Entity { + public: + Mob(const VectorF& coordinates, + QPixmap* pixmap, + int health, + int damage_to_base, + qreal speed); + + Mob(const VectorF& coordinates, + Animation* animation, + int health, + int damage_to_base, + qreal speed); + void TimeToGrow(); + + virtual void SetRoute(Route* route); + void MoveToRouteStart(); + + [[nodiscard]] qreal GetSpeed() const; + void SetSpeed(qreal speed); + + void Tick(Time delta) override; + + QRectF boundingRect() const override; + + ~Mob() override; + + protected: + Route* route_{nullptr}; + qreal speed_; + bool dealed_damage_to_base_; + int damage_to_base_; + Time growing_time_; + qreal growing_step_; +}; diff --git a/GameObjects/Entities/Mobs/cobra.cpp b/GameObjects/Entities/Mobs/cobra.cpp new file mode 100644 index 0000000..bc5d3f6 --- /dev/null +++ b/GameObjects/Entities/Mobs/cobra.cpp @@ -0,0 +1,45 @@ +#include "cobra.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +namespace C = Entities::Cobra; +using P = PixmapLoader::Pixmaps; + +Cobra::Cobra(const VectorF& coordinates) : + Mob(coordinates, + new Animation(P::Cobra::kWalk, + C::kTimeBetweenFrames), + C::kHealth, + C::kDamageToBase, + C::kSpeed), is_destroying_(false), + walk_animation_(animation_), + death_animation_(new Animation(P::Cobra::kDeath, C::kTimeBetweenFrames)) { + setScale(3); +} + +void Cobra::ApplyDamage(Damage damage) { + Damageable::ApplyDamage(damage); + if (health_ <= 0 && !is_destroying_) { + death_animation_->Reset(); + is_destroying_ = true; + animation_ = death_animation_; + } +} + +void Cobra::SetRoute(Route* route) { + Mob::SetRoute(route); + MoveToRouteStart(); +} + +void Cobra::Tick(Time delta) { + Mob::Tick(delta); + if (is_destroying_ && animation_->WasEndedDuringPreviousUpdate()) { + animation_->SetIndex(animation_->FrameCount() - 1); + deleteLater(); + } + if (route_ != nullptr) { + if (!is_destroying_ && !route_->isEnd(this)) { + route_->Move(this, speed_ * delta.seconds()); + } + } +} diff --git a/GameObjects/Entities/Mobs/cobra.h b/GameObjects/Entities/Mobs/cobra.h new file mode 100644 index 0000000..e9b991b --- /dev/null +++ b/GameObjects/Entities/Mobs/cobra.h @@ -0,0 +1,16 @@ +#pragma once +#include "Basis/mob.h" + +class Cobra : public Mob { + public: + explicit Cobra(const VectorF& coordinates = {0, 0}); + void ApplyDamage(Damage damage) override; + void SetRoute(Route* route) override; + void Tick(Time delta) override; + + private: + bool is_destroying_; + + Animation* walk_animation_; + Animation* death_animation_; +}; diff --git a/GameObjects/Entities/Mobs/dwarf.cpp b/GameObjects/Entities/Mobs/dwarf.cpp new file mode 100644 index 0000000..bad2cba --- /dev/null +++ b/GameObjects/Entities/Mobs/dwarf.cpp @@ -0,0 +1,49 @@ +#include "dwarf.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +namespace D = Entities::Dwarf; +using P = PixmapLoader::Pixmaps; + +Dwarf::Dwarf(const VectorF& coordinates) : + Mob(coordinates, + new Animation(P::Dwarf::kWalk, + D::kTimeBetweenFrames), + D::kHealth, + D::kDamageToBase, + D::kSpeed), is_destroying_(false), + walk_animation_(animation_), + death_animation_(new Animation(P::Dwarf::kDeath, D::kTimeBetweenFrames)) { + setScale(3); +} + +void Dwarf::ApplyDamage(Damage damage) { + Damageable::ApplyDamage(damage); + if (health_ <= 0 && !is_destroying_) { + death_animation_->Reset(); + is_destroying_ = true; + animation_ = death_animation_; + } +} + +void Dwarf::SetRoute(Route* route) { + Mob::SetRoute(route); + MoveToRouteStart(); +} + +void Dwarf::Tick(Time delta) { + Mob::Tick(delta); + if (is_destroying_ && animation_->WasEndedDuringPreviousUpdate()) { + animation_->SetIndex(animation_->FrameCount() - 1); + deleteLater(); + } + if (route_ != nullptr) { + if (!is_destroying_ && !route_->isEnd(this)) { + route_->Move(this, speed_ * delta.seconds()); + } + } +} + +QRectF Dwarf::boundingRect() const { + return Mob::boundingRect().translated(0, 5); +} diff --git a/GameObjects/Entities/Mobs/dwarf.h b/GameObjects/Entities/Mobs/dwarf.h new file mode 100644 index 0000000..c49146e --- /dev/null +++ b/GameObjects/Entities/Mobs/dwarf.h @@ -0,0 +1,20 @@ +#pragma once +#include "Basis/mob.h" + + +class Dwarf : public Mob { + public: + explicit Dwarf(const VectorF& coordinates = {0, 0}); + void ApplyDamage(Damage damage) override; + void SetRoute(Route* route) override; + void Tick(Time delta) override; + + QRectF boundingRect() const override; + + private: + bool is_destroying_; + + Animation* walk_animation_; + Animation* death_animation_; +}; + diff --git a/GameObjects/Entities/Mobs/hedgehog.cpp b/GameObjects/Entities/Mobs/hedgehog.cpp new file mode 100644 index 0000000..839eefe --- /dev/null +++ b/GameObjects/Entities/Mobs/hedgehog.cpp @@ -0,0 +1,46 @@ +#include "hedgehog.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +namespace H = Entities::Hedgehog; +using P = PixmapLoader::Pixmaps; + +Hedgehog::Hedgehog(const VectorF& coordinates) : + Mob(coordinates, + new Animation(P::Hedgehog::kWalk, + H::kTimeBetweenFrames), + H::kHealth, + H::kDamageToBase, + H::kSpeed), is_destroying_(false), + walk_animation_(animation_), + death_animation_( + new Animation(P::Hedgehog::kDeath, H::kTimeBetweenFrames)) { + setScale(2); +} + +void Hedgehog::ApplyDamage(Damage damage) { + Damageable::ApplyDamage(damage); + if (health_ <= 0 && !is_destroying_) { + death_animation_->Reset(); + is_destroying_ = true; + animation_ = death_animation_; + } +} + +void Hedgehog::SetRoute(Route* route) { + Mob::SetRoute(route); + MoveToRouteStart(); +} + +void Hedgehog::Tick(Time delta) { + Mob::Tick(delta); + if (is_destroying_ && animation_->WasEndedDuringPreviousUpdate()) { + animation_->SetIndex(animation_->FrameCount() - 1); + deleteLater(); + } + if (route_ != nullptr) { + if (!is_destroying_ && !route_->isEnd(this)) { + route_->Move(this, speed_ * delta.seconds()); + } + } +} diff --git a/GameObjects/Entities/Mobs/hedgehog.h b/GameObjects/Entities/Mobs/hedgehog.h new file mode 100644 index 0000000..311f114 --- /dev/null +++ b/GameObjects/Entities/Mobs/hedgehog.h @@ -0,0 +1,17 @@ +#pragma once +#include "Basis/mob.h" + +class Hedgehog : public Mob { + public: + explicit Hedgehog(const VectorF& coordinates = {0, 0}); + void ApplyDamage(Damage damage) override; + void SetRoute(Route* route) override; + void Tick(Time delta) override; + + private: + bool is_destroying_; + + Animation* walk_animation_; + Animation* death_animation_; +}; + diff --git a/GameObjects/Entities/Mobs/skeleton.cpp b/GameObjects/Entities/Mobs/skeleton.cpp new file mode 100644 index 0000000..8f83561 --- /dev/null +++ b/GameObjects/Entities/Mobs/skeleton.cpp @@ -0,0 +1,46 @@ +#include "skeleton.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +namespace S = Entities::Skeleton; +using P = PixmapLoader::Pixmaps; + +Skeleton::Skeleton(const VectorF& coordinates) + : Mob(coordinates, + new Animation(P::Skeleton::kWalk, + S::kTimeBetweenFrames), + S::kHealth, + S::kDamageToBase, + S::kSpeed), is_destroying_(false), + walk_animation_(animation_), + death_animation_( + new Animation(P::Skeleton::kDeath, S::kTimeBetweenFrames)) { + setScale(3); +} + +void Skeleton::ApplyDamage(Damage damage) { + Damageable::ApplyDamage(damage); + if (health_ <= 0 && !is_destroying_) { + death_animation_->Reset(); + is_destroying_ = true; + animation_ = death_animation_; + } +} + +void Skeleton::SetRoute(Route* route) { + Mob::SetRoute(route); + MoveToRouteStart(); +} + +void Skeleton::Tick(Time delta) { + Mob::Tick(delta); + if (is_destroying_ && animation_->WasEndedDuringPreviousUpdate()) { + animation_->SetIndex(animation_->FrameCount() - 1); + deleteLater(); + } + if (route_ != nullptr) { + if (!is_destroying_ && !route_->isEnd(this)) { + route_->Move(this, speed_ * delta.seconds()); + } + } +} diff --git a/GameObjects/Entities/Mobs/skeleton.h b/GameObjects/Entities/Mobs/skeleton.h new file mode 100644 index 0000000..e391231 --- /dev/null +++ b/GameObjects/Entities/Mobs/skeleton.h @@ -0,0 +1,18 @@ +#pragma once +#include "Basis/mob.h" + +class Skeleton : public Mob { + public: + explicit Skeleton(const VectorF& coordinates = {0, 0}); + void ApplyDamage(Damage damage) override; + void SetRoute(Route* route) override; + void Tick(Time delta) override; + + private: + bool is_destroying_; + + Animation* walk_animation_; + Animation* death_animation_; +}; + + diff --git a/GameObjects/Entities/Projectiles/autoguided_projectile.cpp b/GameObjects/Entities/Projectiles/autoguided_projectile.cpp new file mode 100644 index 0000000..cbd80a8 --- /dev/null +++ b/GameObjects/Entities/Projectiles/autoguided_projectile.cpp @@ -0,0 +1,129 @@ +#include "autoguided_projectile.h" + +#include + +#include + +#include "GameObjects/Entities/Mobs/Basis/mob.h" +#include "constants.h" + +AutoguidedProjectile::AutoguidedProjectile( + const VectorF& coordinates, + Animation* animation, + Entity* target, + qreal start_speed, + qreal max_speed, + qreal acceleration, + qreal enemy_find_distance, + Damage damage) + : Projectile(coordinates, animation), + target_(target), + speed_( + VectorF(target->scenePos() - scenePos()).normalized() * start_speed), + max_speed_(max_speed), + acceleration_(acceleration), + enemy_find_distance_(enemy_find_distance), + damage_(damage), is_destroying_(false) { + connect(target_, &Entity::destroyed, this, + &AutoguidedProjectile::FindNewTargetOrDie); +} + +AutoguidedProjectile::AutoguidedProjectile( + const VectorF& coordinates, + QPixmap* pixmap, + Entity* target, + qreal start_speed, + qreal max_speed, + qreal acceleration, + qreal enemy_find_distance, + Damage damage) + : AutoguidedProjectile( + coordinates, + new Animation(pixmap), + target, + start_speed, + max_speed, + acceleration, + enemy_find_distance, + damage) {} + +void AutoguidedProjectile::Tick(Time delta) { + Projectile::Tick(delta); + if (target_ == nullptr) { + return; + } + if (target_->GetHealth() <= 0) { + FindNewTargetOrDie(); + } + if (target_ == nullptr) { + return; + } + + Move(delta); + + if (target_->collidesWithItem(this)) { + target_->ApplyDamage(damage_); + is_destroying_ = true; + } +} + +void AutoguidedProjectile::Move(Time delta) { + if (target_ == nullptr) { + return; + } + + VectorF delta_velocity = + VectorFromThisToTarget().normalized() * acceleration_ * delta.seconds(); + + speed_ += delta_velocity; + + qreal speed_scalar = speed_.length(); + speed_ = speed_.normalized() * std::min(max_speed_, speed_scalar); + + MoveBy(speed_ * delta.seconds()); +} + +void AutoguidedProjectile::FindNewTargetOrDie() { + if (scene()->Mobs().empty()) { + SetTarget(nullptr); + deleteLater(); + } else { + auto old_target = target_; + for (auto new_target : scene()->Mobs()) { + VectorF this_to_target_vector = + VectorF(new_target->scenePos() - scenePos()); + + if (new_target != target_ && + this_to_target_vector.length() < enemy_find_distance_ && + new_target->GetHealth() > 0) { + SetTarget(new_target); + break; + } + } + if (old_target == target_) { + SetTarget(nullptr); + deleteLater(); + } + } +} + +void AutoguidedProjectile::SetTarget(Entity* target) { + // TODO(jansenin): what if target_ is destroyed? + disconnect( + target_, + &Entity::destroyed, + this, + &AutoguidedProjectile::FindNewTargetOrDie); + target_ = target; + if (target_ != nullptr) { + connect( + target_, + &Entity::destroyed, + this, + &AutoguidedProjectile::FindNewTargetOrDie); + } +} + +VectorF AutoguidedProjectile::VectorFromThisToTarget() { + return target_->scenePos() - scenePos(); +} diff --git a/GameObjects/Entities/Projectiles/autoguided_projectile.h b/GameObjects/Entities/Projectiles/autoguided_projectile.h new file mode 100644 index 0000000..2b1aeab --- /dev/null +++ b/GameObjects/Entities/Projectiles/autoguided_projectile.h @@ -0,0 +1,42 @@ +#pragma once + +#include "projectile.h" + +class AutoguidedProjectile : public Projectile { + public: + AutoguidedProjectile(const VectorF& coordinates, + QPixmap* pixmap, + Entity* target, + qreal start_speed, + qreal max_speed, + qreal acceleration, + qreal enemy_find_distance, + Damage damage); + + AutoguidedProjectile(const VectorF& coordinates, + Animation* animation, + Entity* target, + qreal start_speed, + qreal max_speed, + qreal acceleration, + qreal enemy_find_distance, + Damage damage); + + void SetTarget(Entity* target); + + void Tick(Time delta) override; + + protected: + void Move(Time delta); + void FindNewTargetOrDie(); + + VectorF VectorFromThisToTarget(); + + bool is_destroying_; + Entity* target_; + VectorF speed_; + Damage damage_; + qreal max_speed_; + qreal acceleration_; + qreal enemy_find_distance_; +}; diff --git a/GameObjects/Entities/Projectiles/cannon_projectile.cpp b/GameObjects/Entities/Projectiles/cannon_projectile.cpp new file mode 100644 index 0000000..ae5a2ee --- /dev/null +++ b/GameObjects/Entities/Projectiles/cannon_projectile.cpp @@ -0,0 +1,26 @@ +#include "cannon_projectile.h" + +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +CannonProjectile::CannonProjectile( + const VectorF& coordinates, Entity* target, int level) + : LinearAutoguidedProjectile( + coordinates, + PixmapLoader::Pixmaps::kCannonProjectileLevel1, + target, + Entities::CannonProjectile::kSpeed, + Entities::CannonProjectile::kDamageLevel1) { + if (level == 2) { + animation_ = new Animation(PixmapLoader::Pixmaps::kCannonProjectileLevel2); + damage_ = Entities::CannonProjectile::kDamageLevel2; + } + if (level == 3) { + animation_ = new Animation(PixmapLoader::Pixmaps::kCannonProjectileLevel3); + damage_ = Entities::CannonProjectile::kDamageLevel3; + } +} + +void CannonProjectile::Tick(Time delta) { + LinearAutoguidedProjectile::Tick(delta); +} diff --git a/GameObjects/Entities/Projectiles/cannon_projectile.h b/GameObjects/Entities/Projectiles/cannon_projectile.h new file mode 100644 index 0000000..2adf022 --- /dev/null +++ b/GameObjects/Entities/Projectiles/cannon_projectile.h @@ -0,0 +1,10 @@ +#pragma once + +#include "linear_autoguided_projectile.h" + +class CannonProjectile : public LinearAutoguidedProjectile { + public: + CannonProjectile(const VectorF& coordinates, Entity* target, int level); + + void Tick(Time delta) override; +}; diff --git a/GameObjects/Entities/Projectiles/linear_autoguided_projectile.cpp b/GameObjects/Entities/Projectiles/linear_autoguided_projectile.cpp new file mode 100644 index 0000000..2077aec --- /dev/null +++ b/GameObjects/Entities/Projectiles/linear_autoguided_projectile.cpp @@ -0,0 +1,92 @@ +#include "linear_autoguided_projectile.h" + +#include + +#include + +#include "GameObjects/Entities/Mobs/Basis/mob.h" +#include "GameObjects/explosion.h" +#include "constants.h" + +LinearAutoguidedProjectile::LinearAutoguidedProjectile( + const VectorF& coordinates, + QPixmap* pixmap, + Entity* target, + qreal speed, + Damage damage) + : LinearAutoguidedProjectile( + coordinates, new Animation(pixmap), target, speed, damage) {} + +LinearAutoguidedProjectile::LinearAutoguidedProjectile( + const VectorF& coordinates, + Animation* animation, + Entity* target, + qreal speed, + Damage damage) + : Projectile(coordinates, animation), + target_(target), + speed_(speed), + damage_(damage) { + connect(target_, &Entity::destroyed, this, + &LinearAutoguidedProjectile::Die); +} + +void LinearAutoguidedProjectile::Tick(Time delta) { + Projectile::Tick(delta); + if (target_ == nullptr) { + return; + } + if (target_->GetHealth() <= 0) { + Die(); + } + if (target_ == nullptr) { + return; + } + + Move(delta); + + if (target_->collidesWithItem(this)) { + target_->ApplyDamage(damage_); + scene()->addItem( + new Explosion( + scenePos(), 10, Damage(10))); + deleteLater(); + } +} + +void LinearAutoguidedProjectile::Move(Time delta) { + if (target_ == nullptr) { + return; + } + + VectorF delta_pos = + VectorFromThisToTarget().normalized() * speed_ * delta.seconds(); + + MoveBy(delta_pos); +} + +void LinearAutoguidedProjectile::SetTarget(Entity* target) { + // TODO(jansenin): what if target_ is destroyed? + disconnect( + target_, + &Entity::destroyed, + this, + &LinearAutoguidedProjectile::Die); + target_ = target; + if (target_ != nullptr) { + connect( + target_, + &Entity::destroyed, + this, + &LinearAutoguidedProjectile::Die); + } +} + +VectorF LinearAutoguidedProjectile::VectorFromThisToTarget() { + return target_->scenePos() - scenePos(); +} + +void LinearAutoguidedProjectile::Die() { + SetTarget(nullptr); + deleteLater(); +} diff --git a/GameObjects/Entities/Projectiles/linear_autoguided_projectile.h b/GameObjects/Entities/Projectiles/linear_autoguided_projectile.h new file mode 100644 index 0000000..7fbca85 --- /dev/null +++ b/GameObjects/Entities/Projectiles/linear_autoguided_projectile.h @@ -0,0 +1,35 @@ +#pragma once + +#include "projectile.h" + +class LinearAutoguidedProjectile : public Projectile { + public: + LinearAutoguidedProjectile( + const VectorF& coordinates, + QPixmap* pixmap, + Entity* target, + qreal speed, + Damage damage); + + LinearAutoguidedProjectile( + const VectorF& coordinates, + Animation* animation, + Entity* target, + qreal speed, + Damage damage); + + void SetTarget(Entity* target); + + void Tick(Time delta) override; + + protected: + void Move(Time delta); + void Die(); + VectorF VectorFromThisToTarget(); + + Entity* target_; + qreal speed_; + Damage damage_; +}; + + diff --git a/GameObjects/Entities/Projectiles/magic_projectile.cpp b/GameObjects/Entities/Projectiles/magic_projectile.cpp new file mode 100644 index 0000000..e68794c --- /dev/null +++ b/GameObjects/Entities/Projectiles/magic_projectile.cpp @@ -0,0 +1,53 @@ +#include "magic_projectile.h" + +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +using P = PixmapLoader::Pixmaps; + +MagicProjectile::MagicProjectile(const VectorF& coordinates, + Entity* target, + int level) + : AutoguidedProjectile( + coordinates, + PixmapLoader::Pixmaps::kMagicProjectileLevel1, + target, + Entities::MagicProjectile::kSpeed, + Entities::MagicProjectile::kMaxSpeed, + Entities::MagicProjectile::kAcceleration, + Entities::MagicProjectile::kEnemyFindDistance, + Entities::MagicProjectile::kDamageLevel1), + destroying_animation_(new Animation( + P::MagicProjectile::kDestroyingLevel1, + Entities::MagicProjectile::kTimeBetweenFrames)) { + if (level == 2) { + animation_ = new Animation(PixmapLoader::Pixmaps::kMagicProjectileLevel2); + damage_ = Entities::MagicProjectile::kDamageLevel2; + destroying_animation_ = new Animation( + P::MagicProjectile::kDestroyingLevel2, + Entities::MagicProjectile::kTimeBetweenFrames); + } + if (level == 3) { + animation_ = new Animation(PixmapLoader::Pixmaps::kMagicProjectileLevel3); + damage_ = Entities::MagicProjectile::kDamageLevel3; + destroying_animation_ = new Animation( + P::MagicProjectile::kDestroyingLevel3, + Entities::MagicProjectile::kTimeBetweenFrames); + } +} + +void MagicProjectile::Tick(Time delta) { + Entity::Tick(delta); + if (!is_destroying_) { + AutoguidedProjectile::Tick(delta); + if (is_destroying_) { + animation_ = destroying_animation_; + animation_->Reset(); + } + return; + } + if (is_destroying_ && animation_->WasEndedDuringPreviousUpdate()) { + animation_->SetIndex(animation_->FrameCount() - 1); + deleteLater(); + } +} diff --git a/GameObjects/Entities/Projectiles/magic_projectile.h b/GameObjects/Entities/Projectiles/magic_projectile.h new file mode 100644 index 0000000..2db1246 --- /dev/null +++ b/GameObjects/Entities/Projectiles/magic_projectile.h @@ -0,0 +1,13 @@ +#pragma once + +#include "autoguided_projectile.h" + +class MagicProjectile : public AutoguidedProjectile { + public: + MagicProjectile(const VectorF& coordinates, Entity* target, int level); + + void Tick(Time delta) override; + + private: + Animation* destroying_animation_; +}; diff --git a/GameObjects/Entities/Projectiles/projectile.cpp b/GameObjects/Entities/Projectiles/projectile.cpp new file mode 100644 index 0000000..6e3aa13 --- /dev/null +++ b/GameObjects/Entities/Projectiles/projectile.cpp @@ -0,0 +1,7 @@ +#include "projectile.h" + +Projectile::Projectile(const VectorF& coordinates, QPixmap* pixmap) + : Projectile(coordinates, new Animation(pixmap)) {} + +Projectile::Projectile(const VectorF& coordinates, Animation* animation) + : Entity(coordinates, animation) {} diff --git a/GameObjects/Entities/Projectiles/projectile.h b/GameObjects/Entities/Projectiles/projectile.h new file mode 100644 index 0000000..a5fc192 --- /dev/null +++ b/GameObjects/Entities/Projectiles/projectile.h @@ -0,0 +1,11 @@ +#pragma once + +#include "GameObjects/Interface/entity.h" + +class Projectile : public Entity { + public: + Projectile(const VectorF& coordinates, QPixmap* pixmap); + Projectile(const VectorF& coordinates, Animation* animation); +}; + + diff --git a/GameObjects/Entities/Towers/TowerSlots/tower_slot.cpp b/GameObjects/Entities/Towers/TowerSlots/tower_slot.cpp new file mode 100644 index 0000000..a718aa6 --- /dev/null +++ b/GameObjects/Entities/Towers/TowerSlots/tower_slot.cpp @@ -0,0 +1,64 @@ +#include "tower_slot.h" +#include "GameObjects/Entities/Towers/tower.h" +#include "GameObjects/Entities/Towers/magic_tower.h" +#include "GameObjects/Entities/Towers/cannon_tower.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" +#include "Controller/controller.h" +#include + +bool TowerSlot::IsTakenUp() const { + return tower_ != nullptr; +} + +void TowerSlot::TakeUpArea(Tower* tower) { + tower_ = tower; + scene()->IncMagicTowersCount(); +} + +void TowerSlot::ClearArea() { + tower_ = nullptr; +} + +TowerSlot::TowerSlot(const VectorF& coordinates) + : Entity(coordinates, PixmapLoader::Pixmaps::kTowerSlot), tower_(nullptr) { + setScale(0.45); +} + +void TowerSlot::Tick(Time time) { + Entity::Tick(time); + + if (tower_ != nullptr) { + tower_->Tick(time); + } +} + +void TowerSlot::mousePressEvent(QGraphicsSceneMouseEvent* event) { + if (!Controller::Instance()->HaveEnoughMoney(Costs::kMagicTowerCost)) { + return; + } + if (event->button() == Qt::MouseButton::LeftButton) { + if (!IsTakenUp()) { + MagicTower* tower = new MagicTower(scenePos()); + scene()->addItem(tower); + TakeUpArea(tower); + } + QGraphicsItem::mousePressEvent(event); + return; + } + if (event->button() == Qt::MouseButton::RightButton) { + if (!IsTakenUp()) { + CannonTower* tower = new CannonTower(scenePos()); + scene()->addItem(tower); + TakeUpArea(tower); + } + QGraphicsItem::mousePressEvent(event); + return; + } +} + void TowerSlot::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + if (IsTakenUp()) return; + Entity::paint(painter, option, widget); + } diff --git a/GameObjects/Entities/Towers/TowerSlots/tower_slot.h b/GameObjects/Entities/Towers/TowerSlots/tower_slot.h new file mode 100644 index 0000000..68fdb25 --- /dev/null +++ b/GameObjects/Entities/Towers/TowerSlots/tower_slot.h @@ -0,0 +1,21 @@ +#pragma once + +#include "GameObjects/Entities/Towers/tower.h" +#include "GameObjects/Interface/entity.h" + +class TowerSlot : public Entity { + public: + explicit TowerSlot(const VectorF& coordinates); + + [[nodiscard]] bool IsTakenUp() const; + void TakeUpArea(Tower* tower); + void ClearArea(); + void Tick(Time time) override; + void mousePressEvent(QGraphicsSceneMouseEvent* event) override; + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) override; + + protected: + Tower* tower_; +}; diff --git a/GameObjects/Entities/Towers/cannon_tower.cpp b/GameObjects/Entities/Towers/cannon_tower.cpp new file mode 100644 index 0000000..1702e1c --- /dev/null +++ b/GameObjects/Entities/Towers/cannon_tower.cpp @@ -0,0 +1,60 @@ +#include "cannon_tower.h" +#include "constants.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "Controller/controller.h" +#include "GameObjects/Entities/Projectiles/cannon_projectile.h" + +using P = PixmapLoader::Pixmaps; + +CannonTower::CannonTower(const VectorF& coordinates) : + Tower(coordinates, + P::kCannonTowerLevel1, + Entities::CannonTower::kAttackCooldownLevel1, + Entities::CannonTower::kAttackRangeLevel1, + Entities::CannonTower::kMaxLevel, + Entities::CannonTower::kPrice) { + setScale(2); + Controller::Instance()->LoseMoney(Costs::kCannonTowerCost); +} + +void CannonTower::Upgrade() { + Tower::Upgrade(); + if (current_level_ == 1) { + if (!Controller::Instance()->HaveEnoughMoney(Costs::kTowerLevel2Upgrade)) { + return; + } else { + ++current_level_; + Controller::Instance()->LoseMoney(Costs::kTowerLevel2Upgrade); + Tower::cooldown_ = Entities::CannonTower::kAttackCooldownLevel2; + Tower::range_ = Entities::CannonTower::kAttackRangeLevel2; + delete Tower::animation_; + Tower::animation_ = new Animation(P::kCannonTowerLevel2); + update(); + return; + } + } + if (current_level_ == 2) { + if (!Controller::Instance()->HaveEnoughMoney(Costs::kTowerLevel3Upgrade)) { + return; + } else { + ++current_level_; + Controller::Instance()->LoseMoney(Costs::kTowerLevel3Upgrade); + Tower::cooldown_ = Entities::CannonTower::kAttackCooldownLevel3; + Tower::range_ = Entities::CannonTower::kAttackRangeLevel3; + delete Tower::animation_; + Tower::animation_ = new Animation(P::kCannonTowerLevel3); + update(); + return; + } + } +} + +void CannonTower::mousePressEvent(QGraphicsSceneMouseEvent* event) { + Upgrade(); +} +Projectile* CannonTower::SpawnProjectile(const VectorF& coordinates, + Entity* target, + int level) { + return new CannonProjectile(coordinates, target, level); +} + diff --git a/GameObjects/Entities/Towers/cannon_tower.h b/GameObjects/Entities/Towers/cannon_tower.h new file mode 100644 index 0000000..cd1e6ce --- /dev/null +++ b/GameObjects/Entities/Towers/cannon_tower.h @@ -0,0 +1,14 @@ +#pragma once +#include "tower.h" +#include + +class CannonTower : public Tower { + public: + explicit CannonTower(const VectorF& coordinates); + void mousePressEvent(QGraphicsSceneMouseEvent* event) override; + Projectile* SpawnProjectile(const VectorF& coordinates, + Entity* target, + int level) override; + private: + void Upgrade() override; +}; diff --git a/GameObjects/Entities/Towers/magic_tower.cpp b/GameObjects/Entities/Towers/magic_tower.cpp new file mode 100644 index 0000000..8bee99d --- /dev/null +++ b/GameObjects/Entities/Towers/magic_tower.cpp @@ -0,0 +1,60 @@ +#include "magic_tower.h" +#include "constants.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "Controller/controller.h" +#include "GameObjects/Entities/Projectiles/magic_projectile.h" + +using P = PixmapLoader::Pixmaps; + +MagicTower::MagicTower(const VectorF& coordinates) : + Tower(coordinates, + P::kMagicTowerLevel1, + Entities::MagicTower::kAttackCooldownLevel1, + Entities::MagicTower::kAttackRangeLevel1, + Entities::MagicTower::kMaxLevel, + Entities::MagicTower::kPrice) { + Controller::Instance()->LoseMoney(Costs::kMagicTowerCost); +} + +void MagicTower::Upgrade() { + Tower::Upgrade(); + if (current_level_ == 1) { + if (!Controller::Instance()->HaveEnoughMoney(Costs::kTowerLevel2Upgrade)) { + return; + } else { + ++current_level_; + Controller::Instance()->LoseMoney(Costs::kTowerLevel2Upgrade); + Tower::cooldown_ = Entities::MagicTower::kAttackCooldownLevel2; + Tower::range_ = Entities::MagicTower::kAttackRangeLevel2; + delete Tower::animation_; + Tower::animation_ = new Animation(P::kMagicTowerLevel2); + update(); + return; + } + } + if (current_level_ == 2) { + if (!Controller::Instance()->HaveEnoughMoney(Costs::kTowerLevel3Upgrade)) { + return; + } else { + ++current_level_; + Controller::Instance()->LoseMoney(Costs::kTowerLevel3Upgrade); + Tower::cooldown_ = Entities::MagicTower::kAttackCooldownLevel3; + Tower::range_ = Entities::MagicTower::kAttackRangeLevel3; + delete Tower::animation_; + Tower::animation_ = new Animation(P::kMagicTowerLevel3); + update(); + return; + } + } +} + +void MagicTower::mousePressEvent(QGraphicsSceneMouseEvent* event) { + Upgrade(); +} + +Projectile* MagicTower::SpawnProjectile(const VectorF& coordinates, + Entity* target, + int level) { + return new MagicProjectile(coordinates, target, level); +} + diff --git a/GameObjects/Entities/Towers/magic_tower.h b/GameObjects/Entities/Towers/magic_tower.h new file mode 100644 index 0000000..af0bb49 --- /dev/null +++ b/GameObjects/Entities/Towers/magic_tower.h @@ -0,0 +1,16 @@ +#pragma once +#include "tower.h" +#include + +class MagicTower : public Tower { + public: + explicit MagicTower(const VectorF& coordinates); + void mousePressEvent(QGraphicsSceneMouseEvent* event) override; + Projectile* SpawnProjectile(const VectorF& coordinates, + Entity* target, + int level) override; + + private: + void Upgrade() override; +}; + diff --git a/GameObjects/Entities/Towers/tower.cpp b/GameObjects/Entities/Towers/tower.cpp new file mode 100644 index 0000000..cca7331 --- /dev/null +++ b/GameObjects/Entities/Towers/tower.cpp @@ -0,0 +1,74 @@ +#include + +#include "tower.h" +#include "GameObjects/Entities/Mobs/Basis/mob.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "GameObjects/Entities/Projectiles/projectile.h" +#include "constants.h" + +QPolygonF CreateAttackArea(qreal range) { + const int points_count = Entities::kCircleAttackAreaApproximationPointsCount; + QVector points; + for (int i = 0 ; i < points_count; ++i) { + qreal angle = i * 2 * M_PI / points_count; + points.push_back(QPointF { cos(angle), sin(angle) } * range); + } + return QPolygonF(points); +} + +Tower::Tower(const VectorF& coordinates, QPixmap* pixmap, Time cooldown, + qreal range, int max_level, int price, int health) + : Entity(coordinates, new Animation(pixmap), health), + attack_timer_(Time(0)), + range_(range), + cooldown_(cooldown), + current_level_(1), + max_level_(max_level), + price_(price), + local_attack_area_(CreateAttackArea(range_)) { + // TODO(jansenin): change it when coordinates are changed + scene_attack_area_ = local_attack_area_.translated(scenePos()); +} + +Tower::Tower(const VectorF& coordinates, Animation* animation, + Time cooldown, qreal range, int max_level, int price, int health) + : Entity(coordinates, animation, health), + attack_timer_(Time(0)), + range_(range), + cooldown_(cooldown), + current_level_(1), + max_level_(max_level), + price_(price), + local_attack_area_(CreateAttackArea(range_)) { + // TODO(jansenin): change it when coordinates are changed + scene_attack_area_ = local_attack_area_.translated(scenePos()); +} + +void Tower::Tick(Time delta) { + Entity::Tick(delta); + + attack_timer_.Tick(delta); + + if (attack_timer_.IsExpired()) { + QList items_in_attack_area = + scene()->items(scene_attack_area_); + for (QGraphicsItem* item : items_in_attack_area) { + if (Mob* mob = dynamic_cast(item)) { + if (mob->GetHealth() <= 0) { + continue; + } + scene()->addItem(SpawnProjectile( + scenePos() - QPointF(0, 20), mob, current_level_)); + attack_timer_.Start(cooldown_); + break; + } + } + } +} + +void Tower::Upgrade() {} + +Projectile* Tower::SpawnProjectile( + const VectorF& coordinates, Entity* target, int level) { + return nullptr; +} diff --git a/GameObjects/Entities/Towers/tower.h b/GameObjects/Entities/Towers/tower.h new file mode 100644 index 0000000..aa2fc1b --- /dev/null +++ b/GameObjects/Entities/Towers/tower.h @@ -0,0 +1,42 @@ +#pragma once + +#include "GameObjects/Interface/entity.h" +#include "Utilities/timer.h" +#include + +QPolygonF CreateAttackArea(qreal range); + +class Tower : public Entity { + public: + Tower(const VectorF& coordinates, + QPixmap* pixmap, + Time cooldown, + qreal range, + int max_level, + int price, + int health = 0); + + Tower(const VectorF& coordinates, + Animation* animation, + Time cooldown, + qreal range, + int max_level, + int price, + int health = 0); + + virtual void Upgrade(); + virtual Projectile* SpawnProjectile( + const VectorF& coordinates, Entity* target, int level); + + void Tick(Time delta) override; + + protected: + int price_; + int current_level_; + int max_level_; + Time cooldown_; + qreal range_; + Timer attack_timer_; + QPolygonF local_attack_area_; + QPolygonF scene_attack_area_; +}; diff --git a/GameObjects/Entities/Traps/bear_trap.cpp b/GameObjects/Entities/Traps/bear_trap.cpp new file mode 100644 index 0000000..5dac7f2 --- /dev/null +++ b/GameObjects/Entities/Traps/bear_trap.cpp @@ -0,0 +1,91 @@ +#include "bear_trap.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" +#include "Controller/controller.h" + +#include +#include +#include + +QRectF BearTrap::boundingRect() const { + return QRectF(QPointF(-15, -15), QSize(30, 30)); +} + +BearTrap::BearTrap(const VectorF& coordinates) + : BearTrap(coordinates, + new Animation( + PixmapLoader::Pixmaps::kBearTrapIdle, + 50_ms)) { + attacking_animation_ = new Animation( + PixmapLoader::Pixmaps::kBearTrapAttacking, + 50_ms);; + idle_animation_ = new Animation( + PixmapLoader::Pixmaps::kBearTrapIdle, + 50_ms); + broken_animation_ = new Animation( + PixmapLoader::Pixmaps::kBearTrapBroken, + 50_ms); + repairing_animation_ = new Animation( + PixmapLoader::Pixmaps::kBearTrapBroken, + 50_ms); + setFlag(QGraphicsItem::ItemIsFocusable, true); + setScale(2.5); +} + +BearTrap::BearTrap(const VectorF& coordinates, Animation* animation) + : Entity(coordinates, animation) {} + +void BearTrap::Tick(Time delta) { + if (animation_->WasEndedDuringPreviousUpdate()) { + if (is_broken_) { + animation_ = repairing_animation_; + } else { + animation_ = idle_animation_; + } + } + Entity::Tick(delta); + std::vector mobs = scene()->Mobs(); + for (auto mob : mobs) { + if (mob->sceneBoundingRect().intersects(this->sceneBoundingRect()) + && !is_broken_) { + mob->ApplyDamage(Damage(mob->GetHealth())); + animation_ = attacking_animation_; + is_broken_ = true; + } + } +} + +void BearTrap::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + Entity::paint(painter, option, widget); +} + +void BearTrap::mousePressEvent(QGraphicsSceneMouseEvent* event) { + if (!is_broken_) { + return; + } + if (!Controller::Instance()->HaveEnoughMoney(Costs::kBearTrapRepairingCost)) { + return; + } + if (event->button() != Qt::LeftButton) { + return; + } + RepairTrap(); +} + +void BearTrap::RepairTrap() { + is_broken_ = false; + animation_ = repairing_animation_; + Controller::Instance()->LoseMoney(Costs::kBearTrapRepairingCost); + update(); +} + +BearTrap::~BearTrap() { + delete idle_animation_; + delete broken_animation_; + delete attacking_animation_; + delete repairing_animation_; +} + + diff --git a/GameObjects/Entities/Traps/bear_trap.h b/GameObjects/Entities/Traps/bear_trap.h new file mode 100644 index 0000000..b5fee4a --- /dev/null +++ b/GameObjects/Entities/Traps/bear_trap.h @@ -0,0 +1,27 @@ +#pragma once + +#include "GameObjects/Entities/Mobs/Basis/mob.h" + +class BearTrap : public Entity { + public: + explicit BearTrap(const VectorF& coordinates); + BearTrap(const VectorF& coordinates, Animation* animation); + ~BearTrap(); + + [[nodiscard]] QRectF boundingRect() const; + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget); + void Tick(Time delta); + void RepairTrap(); + + void mousePressEvent(QGraphicsSceneMouseEvent* event) override; + + private: + Animation* idle_animation_; + Animation* attacking_animation_; + Animation* broken_animation_; + Animation* repairing_animation_; + + bool is_broken_ = false; +}; diff --git a/GameObjects/Entities/Traps/bomb.cpp b/GameObjects/Entities/Traps/bomb.cpp new file mode 100644 index 0000000..836bfc8 --- /dev/null +++ b/GameObjects/Entities/Traps/bomb.cpp @@ -0,0 +1,75 @@ +#include "bomb.h" +#include "Utilities/Resources/pixmap_loader.h" + +#include "constants.h" +#include "Controller/controller.h" +#include "GameObjects/explosion.h" + +#include +#include +#include + +QRectF Bomb::boundingRect() const { + return QRectF(QPointF(-15, -15), QSize(30, 30)); +} + +Bomb::Bomb(const VectorF& coordinates) + : Bomb(coordinates, new Animation( + PixmapLoader::Pixmaps::kBombIdle, + 50_ms)) { + idle_animation_ = new Animation( + PixmapLoader::Pixmaps::kBombIdle, + 50_ms); + explosion_animation_ = new Animation( + PixmapLoader::Pixmaps::kBombExplosion, + 50_ms); + setFlag(QGraphicsItem::ItemIsFocusable, true); + setScale(2.5); +} + +Bomb::Bomb(const VectorF& coordinates, Animation* animation) + : Entity(coordinates, animation) {} + +void Bomb::Tick(Time delta) { + if (animation_->WasEndedDuringPreviousUpdate()) { + if (activated_) { + animation_ = explosion_animation_; + } + } + + Entity::Tick(delta); + if (activated_ && animation_->WasEndedDuringPreviousUpdate()) { + scene()->addItem( + new Explosion( + scenePos(), 100, Damage(1000))); + deleteLater(); + } +} + +void Bomb::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + Entity::paint(painter, option, widget); +} + +void Bomb::mousePressEvent(QGraphicsSceneMouseEvent* event) { + if (!Controller::Instance()->HaveEnoughMoney(Costs::kBombExplosionCost)) { + return; + } + Controller::Instance()->LoseMoney(Costs::kBombExplosionCost); + if (event->button() != Qt::LeftButton) { + return; + } + Explode(); +} + +Bomb::~Bomb() { + delete idle_animation_; + delete explosion_animation_; +} + +void Bomb::Explode() { + activated_ = true; +} + + diff --git a/GameObjects/Entities/Traps/bomb.h b/GameObjects/Entities/Traps/bomb.h new file mode 100644 index 0000000..9a826b0 --- /dev/null +++ b/GameObjects/Entities/Traps/bomb.h @@ -0,0 +1,26 @@ +#pragma once + +#include "GameObjects/Entities/Mobs/Basis/mob.h" + +class Bomb : public Entity { + public: + explicit Bomb(const VectorF& coordinates); + Bomb(const VectorF& coordinates, Animation* animation); + ~Bomb(); + + [[nodiscard]] QRectF boundingRect() const; + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget); + void Tick(Time delta); + + void Explode(); + + void mousePressEvent(QGraphicsSceneMouseEvent* event) override; + + private: + Animation* idle_animation_; + Animation* explosion_animation_; + + bool activated_ = false; +}; diff --git a/GameObjects/Interface/damageable.cpp b/GameObjects/Interface/damageable.cpp new file mode 100644 index 0000000..ebfbd77 --- /dev/null +++ b/GameObjects/Interface/damageable.cpp @@ -0,0 +1,20 @@ +#include "damageable.h" + +#include +#include + +Damageable::Damageable(int health) { + health_ = health; +} + +void Damageable::ApplyDamage(Damage damage) { + SetHealth(std::max(health_ - damage.GetDamage(), 0)); +} + +void Damageable::SetHealth(int health) { + health_ = health; +} + +int Damageable::GetHealth() const { + return health_; +} diff --git a/GameObjects/Interface/damageable.h b/GameObjects/Interface/damageable.h new file mode 100644 index 0000000..2b581d9 --- /dev/null +++ b/GameObjects/Interface/damageable.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Utilities/damage.h" + +class Damageable { + public: + explicit Damageable(int health); + virtual void ApplyDamage(Damage damage); + [[nodiscard]] int GetHealth() const; + + protected: + int health_; + virtual void SetHealth(int health); +}; diff --git a/GameObjects/Interface/entity.cpp b/GameObjects/Interface/entity.cpp new file mode 100644 index 0000000..64f97c3 --- /dev/null +++ b/GameObjects/Interface/entity.cpp @@ -0,0 +1,45 @@ +#include "entity.h" + +Entity::Entity( + const VectorF& coordinates, + QPixmap* pixmap, + int health) + : Entity(coordinates, new Animation(pixmap), health) {} + +Entity::Entity(const VectorF& coordinates, Animation* animation, int health) + : Damageable(health), + GraphicsObject(), + animation_(animation) { + animation->Frame(); + setPos(coordinates); + setFlag(ItemSendsGeometryChanges); + connect(this, &Entity::yChanged, [this](){ + setZValue(pos().y()); + }); +} + +QRectF Entity::boundingRect() const { + const QPixmap* frame = animation_->Frame(); + return QRectF( + frame->rect().translated( + QPoint{ -frame->width()/2, -frame->height()/2 })); +} + +void Entity::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + painter->save(); + static QPen pen(QColor(0, 0, 255, 50)); + painter->setPen(pen); + // painter->drawRect(boundingRect()); + painter->drawPixmap(boundingRect().toRect(), *animation_->Frame()); + painter->restore(); +} + +void Entity::Tick(Time delta) { + QPixmap* previous_frame = animation_->Frame(); + animation_->Tick(delta); + if (previous_frame != animation_->Frame()) { + update(); + } +} diff --git a/GameObjects/Interface/entity.h b/GameObjects/Interface/entity.h new file mode 100644 index 0000000..40840af --- /dev/null +++ b/GameObjects/Interface/entity.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "tickable.h" +#include "damageable.h" +#include "Utilities/vector_f.h" +#include "graphics_object.h" +#include "Utilities/animation.h" + +class Entity + : public Tickable, + public Damageable, + public GraphicsObject { + public: + Entity( + const VectorF& coordinates, + QPixmap* pixmap, + int health = 0); + + Entity( + const VectorF& coordinates, + Animation* animation, + int health = 0); + + QRectF boundingRect() const override; + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) override; + void Tick(Time delta) override; + + protected: + Animation* animation_; +}; diff --git a/GameObjects/Interface/graphics_object.cpp b/GameObjects/Interface/graphics_object.cpp new file mode 100644 index 0000000..8ff8298 --- /dev/null +++ b/GameObjects/Interface/graphics_object.cpp @@ -0,0 +1,18 @@ +#include "graphics_object.h" + +GraphicsObject::GraphicsObject(QGraphicsItem* parent) + : QGraphicsObject(parent) {} + +GameScene* GraphicsObject::scene() { + auto result = dynamic_cast(QGraphicsObject::scene()); + assert(result != nullptr); + return result; +} + +GameView* GraphicsObject::view() { + return scene()->view(); +} + +void GraphicsObject::MoveBy(const VectorF& delta) { + setPos(pos() + delta); +} diff --git a/GameObjects/Interface/graphics_object.h b/GameObjects/Interface/graphics_object.h new file mode 100644 index 0000000..a9018a5 --- /dev/null +++ b/GameObjects/Interface/graphics_object.h @@ -0,0 +1,19 @@ +#pragma once + +#include "QGraphicsObject" + +#include "Utilities/vector_f.h" +#include "game_scene.h" +#include "game_view.h" + +class GraphicsObject : public QGraphicsObject { + public: + explicit GraphicsObject(QGraphicsItem* parent = nullptr); + + GameScene* scene(); + GameView* view(); + + void MoveBy(const VectorF& delta); +}; + + diff --git a/GameObjects/Interface/tickable.h b/GameObjects/Interface/tickable.h new file mode 100644 index 0000000..22c9f6f --- /dev/null +++ b/GameObjects/Interface/tickable.h @@ -0,0 +1,8 @@ +#pragma once + +#include "Utilities/time.h" + +class Tickable { + public: + virtual void Tick(Time delta) = 0; +}; diff --git a/GameObjects/coin.cpp b/GameObjects/coin.cpp new file mode 100644 index 0000000..b5aefda --- /dev/null +++ b/GameObjects/coin.cpp @@ -0,0 +1,60 @@ +#include "coin.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" +#include "Controller/controller.h" + +#include +#include +#include + +Coin::Coin(const VectorF& coordinates, QPixmap* pixmap) + : Coin(coordinates, new Animation( + PixmapLoader::Pixmaps::kCoinIdle, + 50_ms)) { + idle_animation_ = new Animation( + PixmapLoader::Pixmaps::kCoinIdle, + 50_ms); + collecting_route_ = nullptr; + speed_ = 300; + setFlag(QGraphicsItem::ItemIsFocusable, true); + setScale(2.5); +} + +Coin::Coin(const VectorF& coordinates, Animation* animation) + : Entity(coordinates, animation) {} + +void Coin::Tick(Time delta) { + if (animation_->WasEndedDuringPreviousUpdate()) { + animation_ = idle_animation_; + } + if (collecting_route_ != nullptr) { + collecting_route_->Move(this, delta.ms() * speed_); + } + Entity::Tick(delta); +} + +void Coin::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + Entity::paint(painter, option, widget); +} + +void Coin::mousePressEvent(QGraphicsSceneMouseEvent* event) { + Controller::Instance()->AddMoney(Costs::kCoinCost); + update(); + SetRoute(); + delete this; +} + +Coin::~Coin() { + delete idle_animation_; +} + +void Coin::SetRoute() { + std::vector points; + points.emplace_back(this->pos().x(), this->pos().y()); + points.emplace_back(50, 50); + collecting_route_ = new Route(points); + collecting_route_->AddEntity(this); +} + diff --git a/GameObjects/coin.h b/GameObjects/coin.h new file mode 100644 index 0000000..c413fd3 --- /dev/null +++ b/GameObjects/coin.h @@ -0,0 +1,24 @@ +#pragma once + +#include "GameObjects/Entities/Mobs/Basis/mob.h" + +class Coin : public Entity { + public: + Coin(const VectorF& coordinates, QPixmap* pixmap); + Coin(const VectorF& coordinates, Animation* animation); + ~Coin(); + + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget); + void Tick(Time delta); + + void SetRoute(); + + void mousePressEvent(QGraphicsSceneMouseEvent* event) override; + + private: + Animation* idle_animation_; + Route* collecting_route_; + qreal speed_; +}; diff --git a/GameObjects/explosion.cpp b/GameObjects/explosion.cpp new file mode 100644 index 0000000..24dd66d --- /dev/null +++ b/GameObjects/explosion.cpp @@ -0,0 +1,59 @@ +#include "explosion.h" + +#include "GameObjects/Entities/Mobs/Basis/mob.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +Explosion::Explosion(const VectorF& coordinates, qreal radius, Damage damage) + : radius_(radius), + damage_(damage), + animation_(new Animation( + PixmapLoader::Pixmaps::Explosion::kExplosion, + Explosions::kTimeBetweenFrames)) { + setPos(coordinates); + setZValue(Explosions::kZValue); +} + +QRectF Explosion::boundingRect() const { + QRectF result = animation_->Frame()->rect(); + qreal scale = result.width() / (radius_ * 2); + result = QRectF(0, 0, result.width() / scale, result.height() / scale); + result.translate(-result.width() / 2, -result.height() / 2); + return result; +} + +void Explosion::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + QRectF rect = boundingRect(); + QPixmap* pixmap = animation_->Frame(); + painter->drawPixmap(rect, *pixmap, pixmap->rect()); +} + +void Explosion::Tick(Time delta) { + animation_->Tick(delta); + + if (animation_->WasEndedDuringPreviousUpdate()) { + deleteLater(); + } +} + +void Explosion::DamageMobs() { + for (auto mob : scene()->Mobs()) { + if (VectorF(scenePos() - mob->scenePos()).length() < radius_) { + mob->ApplyDamage(damage_); + } + } +} + +QVariant Explosion::itemChange(QGraphicsItem::GraphicsItemChange change, + const QVariant& value) { + if (change == GraphicsItemChange::ItemSceneHasChanged) { + DamageMobs(); + } + return QGraphicsItem::itemChange(change, value); +} + +Explosion::~Explosion() { + delete animation_; +} diff --git a/GameObjects/explosion.h b/GameObjects/explosion.h new file mode 100644 index 0000000..a335a66 --- /dev/null +++ b/GameObjects/explosion.h @@ -0,0 +1,32 @@ +#pragma once + +#include "GameObjects/Interface/graphics_object.h" +#include "GameObjects/Interface/tickable.h" +#include "constants.h" +#include "Utilities/animation.h" + +class Explosion : public GraphicsObject, public Tickable { + public: + Explosion( + const VectorF& coordinates, + qreal radius = Explosions::kDefaultRadius, + Damage damage = Explosions::kDefaultDamage); + + QRectF boundingRect() const override; + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) override; + void Tick(Time delta) override; + + virtual ~Explosion(); + + QVariant itemChange(QGraphicsItem::GraphicsItemChange change, + const QVariant& value) override; + + private: + void DamageMobs(); + + Animation* animation_; + qreal radius_; + Damage damage_; +}; diff --git a/Resources/GUI/Textured boxes/Button/bottom_left_corner.png b/Resources/GUI/Textured boxes/Button/bottom_left_corner.png new file mode 100644 index 0000000..802763f Binary files /dev/null and b/Resources/GUI/Textured boxes/Button/bottom_left_corner.png differ diff --git a/Resources/GUI/Textured boxes/Button/bottom_right_corner.png b/Resources/GUI/Textured boxes/Button/bottom_right_corner.png new file mode 100644 index 0000000..282c56c Binary files /dev/null and b/Resources/GUI/Textured boxes/Button/bottom_right_corner.png differ diff --git a/Resources/GUI/Textured boxes/Button/bottom_side.png b/Resources/GUI/Textured boxes/Button/bottom_side.png new file mode 100644 index 0000000..ea7429b Binary files /dev/null and b/Resources/GUI/Textured boxes/Button/bottom_side.png differ diff --git a/Resources/GUI/Textured boxes/Button/inside.png b/Resources/GUI/Textured boxes/Button/inside.png new file mode 100644 index 0000000..4e02e0a Binary files /dev/null and b/Resources/GUI/Textured boxes/Button/inside.png differ diff --git a/Resources/GUI/Textured boxes/Button/left_side.png b/Resources/GUI/Textured boxes/Button/left_side.png new file mode 100644 index 0000000..021f80e Binary files /dev/null and b/Resources/GUI/Textured boxes/Button/left_side.png differ diff --git a/Resources/GUI/Textured boxes/Button/right_side.png b/Resources/GUI/Textured boxes/Button/right_side.png new file mode 100644 index 0000000..f4de339 Binary files /dev/null and b/Resources/GUI/Textured boxes/Button/right_side.png differ diff --git a/Resources/GUI/Textured boxes/Button/top_left_corner.png b/Resources/GUI/Textured boxes/Button/top_left_corner.png new file mode 100644 index 0000000..9cabf01 Binary files /dev/null and b/Resources/GUI/Textured boxes/Button/top_left_corner.png differ diff --git a/Resources/GUI/Textured boxes/Button/top_right_corner.png b/Resources/GUI/Textured boxes/Button/top_right_corner.png new file mode 100644 index 0000000..3fda1ac Binary files /dev/null and b/Resources/GUI/Textured boxes/Button/top_right_corner.png differ diff --git a/Resources/GUI/Textured boxes/Button/top_side.png b/Resources/GUI/Textured boxes/Button/top_side.png new file mode 100644 index 0000000..6fad175 Binary files /dev/null and b/Resources/GUI/Textured boxes/Button/top_side.png differ diff --git a/Resources/GUI/Textured boxes/Default/bottom_left_corner.png b/Resources/GUI/Textured boxes/Default/bottom_left_corner.png new file mode 100644 index 0000000..865b8c8 Binary files /dev/null and b/Resources/GUI/Textured boxes/Default/bottom_left_corner.png differ diff --git a/Resources/GUI/Textured boxes/Default/bottom_right_corner.png b/Resources/GUI/Textured boxes/Default/bottom_right_corner.png new file mode 100644 index 0000000..e17577e Binary files /dev/null and b/Resources/GUI/Textured boxes/Default/bottom_right_corner.png differ diff --git a/Resources/GUI/Textured boxes/Default/bottom_side.png b/Resources/GUI/Textured boxes/Default/bottom_side.png new file mode 100644 index 0000000..d3a7c85 Binary files /dev/null and b/Resources/GUI/Textured boxes/Default/bottom_side.png differ diff --git a/Resources/GUI/Textured boxes/Default/inside.png b/Resources/GUI/Textured boxes/Default/inside.png new file mode 100644 index 0000000..8a2ea5d Binary files /dev/null and b/Resources/GUI/Textured boxes/Default/inside.png differ diff --git a/Resources/GUI/Textured boxes/Default/left_side.png b/Resources/GUI/Textured boxes/Default/left_side.png new file mode 100644 index 0000000..ad818a8 Binary files /dev/null and b/Resources/GUI/Textured boxes/Default/left_side.png differ diff --git a/Resources/GUI/Textured boxes/Default/right_side.png b/Resources/GUI/Textured boxes/Default/right_side.png new file mode 100644 index 0000000..d657201 Binary files /dev/null and b/Resources/GUI/Textured boxes/Default/right_side.png differ diff --git a/Resources/GUI/Textured boxes/Default/top_left_corner.png b/Resources/GUI/Textured boxes/Default/top_left_corner.png new file mode 100644 index 0000000..f86fc9d Binary files /dev/null and b/Resources/GUI/Textured boxes/Default/top_left_corner.png differ diff --git a/Resources/GUI/Textured boxes/Default/top_right_corner.png b/Resources/GUI/Textured boxes/Default/top_right_corner.png new file mode 100644 index 0000000..4bb255b Binary files /dev/null and b/Resources/GUI/Textured boxes/Default/top_right_corner.png differ diff --git a/Resources/GUI/Textured boxes/Default/top_side.png b/Resources/GUI/Textured boxes/Default/top_side.png new file mode 100644 index 0000000..a68a497 Binary files /dev/null and b/Resources/GUI/Textured boxes/Default/top_side.png differ diff --git a/Resources/GUI/Textured boxes/Menu/bottom_left_corner.png b/Resources/GUI/Textured boxes/Menu/bottom_left_corner.png new file mode 100644 index 0000000..206794b Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu/bottom_left_corner.png differ diff --git a/Resources/GUI/Textured boxes/Menu/bottom_right_corner.png b/Resources/GUI/Textured boxes/Menu/bottom_right_corner.png new file mode 100644 index 0000000..28f4ed9 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu/bottom_right_corner.png differ diff --git a/Resources/GUI/Textured boxes/Menu/bottom_side.png b/Resources/GUI/Textured boxes/Menu/bottom_side.png new file mode 100644 index 0000000..69b6da9 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu/bottom_side.png differ diff --git a/Resources/GUI/Textured boxes/Menu/inside.png b/Resources/GUI/Textured boxes/Menu/inside.png new file mode 100644 index 0000000..c92ce16 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu/inside.png differ diff --git a/Resources/GUI/Textured boxes/Menu/left_side.png b/Resources/GUI/Textured boxes/Menu/left_side.png new file mode 100644 index 0000000..723fca2 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu/left_side.png differ diff --git a/Resources/GUI/Textured boxes/Menu/right_side.png b/Resources/GUI/Textured boxes/Menu/right_side.png new file mode 100644 index 0000000..db372a1 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu/right_side.png differ diff --git a/Resources/GUI/Textured boxes/Menu/top_left_corner.png b/Resources/GUI/Textured boxes/Menu/top_left_corner.png new file mode 100644 index 0000000..8697f88 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu/top_left_corner.png differ diff --git a/Resources/GUI/Textured boxes/Menu/top_right_corner.png b/Resources/GUI/Textured boxes/Menu/top_right_corner.png new file mode 100644 index 0000000..5a507cb Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu/top_right_corner.png differ diff --git a/Resources/GUI/Textured boxes/Menu/top_side.png b/Resources/GUI/Textured boxes/Menu/top_side.png new file mode 100644 index 0000000..69c47e9 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu/top_side.png differ diff --git a/Resources/GUI/Textured boxes/Menu2/bottom_left_corner.png b/Resources/GUI/Textured boxes/Menu2/bottom_left_corner.png new file mode 100644 index 0000000..0518658 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu2/bottom_left_corner.png differ diff --git a/Resources/GUI/Textured boxes/Menu2/bottom_right_corner.png b/Resources/GUI/Textured boxes/Menu2/bottom_right_corner.png new file mode 100644 index 0000000..69eaa1b Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu2/bottom_right_corner.png differ diff --git a/Resources/GUI/Textured boxes/Menu2/bottom_side.png b/Resources/GUI/Textured boxes/Menu2/bottom_side.png new file mode 100644 index 0000000..35fca26 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu2/bottom_side.png differ diff --git a/Resources/GUI/Textured boxes/Menu2/inside.png b/Resources/GUI/Textured boxes/Menu2/inside.png new file mode 100644 index 0000000..177199b Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu2/inside.png differ diff --git a/Resources/GUI/Textured boxes/Menu2/left_side.png b/Resources/GUI/Textured boxes/Menu2/left_side.png new file mode 100644 index 0000000..2ebb7ca Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu2/left_side.png differ diff --git a/Resources/GUI/Textured boxes/Menu2/right_side.png b/Resources/GUI/Textured boxes/Menu2/right_side.png new file mode 100644 index 0000000..8525a30 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu2/right_side.png differ diff --git a/Resources/GUI/Textured boxes/Menu2/top_left_corner.png b/Resources/GUI/Textured boxes/Menu2/top_left_corner.png new file mode 100644 index 0000000..dcd80f2 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu2/top_left_corner.png differ diff --git a/Resources/GUI/Textured boxes/Menu2/top_right_corner.png b/Resources/GUI/Textured boxes/Menu2/top_right_corner.png new file mode 100644 index 0000000..2360b04 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu2/top_right_corner.png differ diff --git a/Resources/GUI/Textured boxes/Menu2/top_side.png b/Resources/GUI/Textured boxes/Menu2/top_side.png new file mode 100644 index 0000000..8826e87 Binary files /dev/null and b/Resources/GUI/Textured boxes/Menu2/top_side.png differ diff --git a/Resources/Levels/Level1/level.json b/Resources/Levels/Level1/level.json new file mode 100644 index 0000000..6a480ee --- /dev/null +++ b/Resources/Levels/Level1/level.json @@ -0,0 +1,270 @@ +{ + "startMoney": 10000, + "BearTraps": [ + { + "points": [ + { + "x": 50, + "y": -200 + } + ] + } + ], + "Bombs": [ + { + "points": [ + { + "x": 650, + "y": 50 + } + ] + } + ], + "towerSlotPositions": [ + { + "x": 50, + "y": 200 + }, + { + "x": 180, + "y": 200 + }, + { + "x": -650, + "y": 150 + }, + { + "x": -650, + "y": 250 + }, + { + "x": -650, + "y": 350 + }, + { + "x": -100, + "y": -100 + }, + { + "x": -300, + "y": -100 + }, + { + "x": -300, + "y": 200 + }, + { + "x": 550, + "y": 200 + }, + { + "x": 750, + "y": 200 + }, + { + "x": 650, + "y": -100 + }, + { + "x": 450, + "y": -100 + } + ], + "routes": [ + { + "points": [ + { + "x": -1000, + "y": 0 + }, + { + "x": -480, + "y": 0 + }, + { + "x": -480, + "y": -220 + }, + { + "x": 60, + "y": -220 + }, + { + "x": 60, + "y": 80 + }, + { + "x": 1500, + "y": 80 + } + ] + }, + { + "points": [ + { + "x": -1000, + "y": 0 + }, + { + "x": -480, + "y": 0 + }, + { + "x": -480, + "y": 380 + }, + { + "x": 380, + "y": 380 + }, + { + "x": 380, + "y": 80 + }, + { + "x": 1500, + "y": 80 + } + ] + } + ], + "waves": [ + { + "startTimeRelativeToPrevWave": 0, + "spawnEntries": [ + { + "mobType": "Hedgehog", + "count": 4, + "startTime": 0, + "entryDuration": 2000, + "routeIndex": 1 + }, + { + "mobType": "Hedgehog", + "count": 4, + "startTime": 2600, + "entryDuration": 2000, + "routeIndex": 0 + } + ] + }, + { + "startTimeRelativeToPrevWave": 10000, + "spawnEntries": [ + { + "mobType": "Hedgehog", + "count": 12, + "startTime": 0, + "entryDuration": 8000, + "routeIndex": 0 + }, + { + "mobType": "Cobra", + "count": 4, + "startTime": 12000, + "entryDuration": 4000, + "routeIndex": 0 + } + ] + }, + { + "startTimeRelativeToPrevWave": 10000, + "spawnEntries": [ + { + "mobType": "Cobra", + "count": 10, + "startTime": 0, + "entryDuration": 6000, + "routeIndex": 0 + }, + { + "mobType": "Hedgehog", + "count": 20, + "startTime": 10000, + "entryDuration": 10000, + "routeIndex": 1 + } + ] + }, + { + "startTimeRelativeToPrevWave": 8000, + "spawnEntries": [ + { + "mobType": "Skeleton", + "count": 2, + "startTime": 0, + "entryDuration": 12000, + "routeIndex": 0 + }, + { + "mobType": "Hedgehog", + "count": 30, + "startTime": 10000, + "entryDuration": 10000, + "routeIndex": 1 + } + ] + }, + { + "startTimeRelativeToPrevWave": 8000, + "spawnEntries": [ + { + "mobType": "Skeleton", + "count": 4, + "startTime": 0, + "entryDuration": 10000, + "routeIndex": 0 + } + ] + }, + { + "startTimeRelativeToPrevWave": 8000, + "spawnEntries": [ + { + "mobType": "Skeleton", + "count": 6, + "startTime": 0, + "entryDuration": 10000, + "routeIndex": 0 + }, + { + "mobType": "Dwarf", + "count": 1, + "startTime": 0, + "entryDuration": 10000, + "routeIndex": 0 + } + ] + } + ], + "GrowTimes": [ + 4000, + 1000, + 1000, + 3000, + 3000, + 10000, + 10000, + 1000, + 1000, + 20000, + 10000, + 1000, + 1000, + 1000, + 1000, + 1000, + 10000, + 10000, + 5000, + 3000, + 10000, + 10000, + 1000, + 1000, + 1000, + 1000, + 1000, + 1000 + ] +} \ No newline at end of file diff --git a/Resources/Levels/Level1/map.png b/Resources/Levels/Level1/map.png new file mode 100644 index 0000000..1a6516c Binary files /dev/null and b/Resources/Levels/Level1/map.png differ diff --git a/Resources/images/background.png b/Resources/images/background.png new file mode 100644 index 0000000..06f192a Binary files /dev/null and b/Resources/images/background.png differ diff --git a/Resources/images/bear_trap.png b/Resources/images/bear_trap.png new file mode 100644 index 0000000..11f3160 Binary files /dev/null and b/Resources/images/bear_trap.png differ diff --git a/Resources/images/bomb0.png b/Resources/images/bomb0.png new file mode 100644 index 0000000..6d01084 Binary files /dev/null and b/Resources/images/bomb0.png differ diff --git a/Resources/images/bomb1.png b/Resources/images/bomb1.png new file mode 100644 index 0000000..5c03b8b Binary files /dev/null and b/Resources/images/bomb1.png differ diff --git a/Resources/images/bomb2.png b/Resources/images/bomb2.png new file mode 100644 index 0000000..65af93c Binary files /dev/null and b/Resources/images/bomb2.png differ diff --git a/Resources/images/bomb3.png b/Resources/images/bomb3.png new file mode 100644 index 0000000..a9445b7 Binary files /dev/null and b/Resources/images/bomb3.png differ diff --git a/Resources/images/bomb4.png b/Resources/images/bomb4.png new file mode 100644 index 0000000..ebb6942 Binary files /dev/null and b/Resources/images/bomb4.png differ diff --git a/Resources/images/bomb5.png b/Resources/images/bomb5.png new file mode 100644 index 0000000..6660275 Binary files /dev/null and b/Resources/images/bomb5.png differ diff --git a/Resources/images/cannon_projectile_level1.png b/Resources/images/cannon_projectile_level1.png new file mode 100644 index 0000000..02b79ef Binary files /dev/null and b/Resources/images/cannon_projectile_level1.png differ diff --git a/Resources/images/cannon_projectile_level2.png b/Resources/images/cannon_projectile_level2.png new file mode 100644 index 0000000..048a613 Binary files /dev/null and b/Resources/images/cannon_projectile_level2.png differ diff --git a/Resources/images/cannon_projectile_level3.png b/Resources/images/cannon_projectile_level3.png new file mode 100644 index 0000000..7b4bda0 Binary files /dev/null and b/Resources/images/cannon_projectile_level3.png differ diff --git a/Resources/images/cannon_tower_level1.png b/Resources/images/cannon_tower_level1.png new file mode 100644 index 0000000..dbf7c4e Binary files /dev/null and b/Resources/images/cannon_tower_level1.png differ diff --git a/Resources/images/cannon_tower_level2.png b/Resources/images/cannon_tower_level2.png new file mode 100644 index 0000000..3ffe5f7 Binary files /dev/null and b/Resources/images/cannon_tower_level2.png differ diff --git a/Resources/images/cannon_tower_level3.png b/Resources/images/cannon_tower_level3.png new file mode 100644 index 0000000..acf07e4 Binary files /dev/null and b/Resources/images/cannon_tower_level3.png differ diff --git a/Resources/images/cobra.png b/Resources/images/cobra.png new file mode 100644 index 0000000..f7e2add Binary files /dev/null and b/Resources/images/cobra.png differ diff --git a/Resources/images/coin.png b/Resources/images/coin.png new file mode 100644 index 0000000..6bce34c Binary files /dev/null and b/Resources/images/coin.png differ diff --git a/Resources/images/dwarf.png b/Resources/images/dwarf.png new file mode 100644 index 0000000..44cdbe2 Binary files /dev/null and b/Resources/images/dwarf.png differ diff --git a/Resources/images/explosion.png b/Resources/images/explosion.png new file mode 100644 index 0000000..16fcf93 Binary files /dev/null and b/Resources/images/explosion.png differ diff --git a/Resources/images/fire_totem.png b/Resources/images/fire_totem.png new file mode 100644 index 0000000..d0dc40a Binary files /dev/null and b/Resources/images/fire_totem.png differ diff --git a/Resources/images/fire_totem2.png b/Resources/images/fire_totem2.png new file mode 100644 index 0000000..014f8f2 Binary files /dev/null and b/Resources/images/fire_totem2.png differ diff --git a/Resources/images/health_status_1.png b/Resources/images/health_status_1.png new file mode 100644 index 0000000..0437b43 Binary files /dev/null and b/Resources/images/health_status_1.png differ diff --git a/Resources/images/health_status_2.png b/Resources/images/health_status_2.png new file mode 100644 index 0000000..0a87300 Binary files /dev/null and b/Resources/images/health_status_2.png differ diff --git a/Resources/images/hedgehog.png b/Resources/images/hedgehog.png new file mode 100644 index 0000000..9c44eda Binary files /dev/null and b/Resources/images/hedgehog.png differ diff --git a/Resources/images/magic_projectile_animations_level1.png b/Resources/images/magic_projectile_animations_level1.png new file mode 100644 index 0000000..a2f6494 Binary files /dev/null and b/Resources/images/magic_projectile_animations_level1.png differ diff --git a/Resources/images/magic_projectile_animations_level2.png b/Resources/images/magic_projectile_animations_level2.png new file mode 100644 index 0000000..9c0444f Binary files /dev/null and b/Resources/images/magic_projectile_animations_level2.png differ diff --git a/Resources/images/magic_projectile_animations_level3.png b/Resources/images/magic_projectile_animations_level3.png new file mode 100644 index 0000000..7afdea9 Binary files /dev/null and b/Resources/images/magic_projectile_animations_level3.png differ diff --git a/Resources/images/magic_projectile_level1.png b/Resources/images/magic_projectile_level1.png new file mode 100644 index 0000000..2a5fb1e Binary files /dev/null and b/Resources/images/magic_projectile_level1.png differ diff --git a/Resources/images/magic_projectile_level2.png b/Resources/images/magic_projectile_level2.png new file mode 100644 index 0000000..39c9b77 Binary files /dev/null and b/Resources/images/magic_projectile_level2.png differ diff --git a/Resources/images/magic_projectile_level3.png b/Resources/images/magic_projectile_level3.png new file mode 100644 index 0000000..e597c16 Binary files /dev/null and b/Resources/images/magic_projectile_level3.png differ diff --git a/Resources/images/magic_tower_level1.png b/Resources/images/magic_tower_level1.png new file mode 100644 index 0000000..2ba7911 Binary files /dev/null and b/Resources/images/magic_tower_level1.png differ diff --git a/Resources/images/magic_tower_level2.png b/Resources/images/magic_tower_level2.png new file mode 100644 index 0000000..d6c66f6 Binary files /dev/null and b/Resources/images/magic_tower_level2.png differ diff --git a/Resources/images/magic_tower_level3.png b/Resources/images/magic_tower_level3.png new file mode 100644 index 0000000..054cd08 Binary files /dev/null and b/Resources/images/magic_tower_level3.png differ diff --git a/Resources/images/money_icon.png b/Resources/images/money_icon.png new file mode 100644 index 0000000..0f63239 Binary files /dev/null and b/Resources/images/money_icon.png differ diff --git a/Resources/images/skeleton/death.png b/Resources/images/skeleton/death.png new file mode 100644 index 0000000..64f539d Binary files /dev/null and b/Resources/images/skeleton/death.png differ diff --git a/Resources/images/skeleton/walk.png b/Resources/images/skeleton/walk.png new file mode 100644 index 0000000..17dfd1f Binary files /dev/null and b/Resources/images/skeleton/walk.png differ diff --git a/Resources/images/tower_slot.png b/Resources/images/tower_slot.png new file mode 100644 index 0000000..6d27ca3 Binary files /dev/null and b/Resources/images/tower_slot.png differ diff --git a/Resources/resources.qrc b/Resources/resources.qrc new file mode 100644 index 0000000..2df3211 --- /dev/null +++ b/Resources/resources.qrc @@ -0,0 +1,91 @@ + + + images/background.png + images/magic_tower_level1.png + images/magic_tower_level2.png + images/magic_tower_level3.png + images/cannon_tower_level1.png + images/cannon_tower_level2.png + images/cannon_tower_level3.png + images/magic_projectile_level1.png + images/magic_projectile_level2.png + images/magic_projectile_level3.png + images/magic_projectile_animations_level1.png + images/magic_projectile_animations_level2.png + images/magic_projectile_animations_level3.png + images/cannon_projectile_level1.png + images/cannon_projectile_level2.png + images/cannon_projectile_level3.png + images/tower_slot.png + images/money_icon.png + images/health_status_1.png + images/health_status_2.png + + + + images/fire_totem.png + + images/skeleton/walk.png + images/skeleton/death.png + + images/cobra.png + + images/hedgehog.png + + images/dwarf.png + + Levels/Level1/map.png + Levels/Level1/level.json + + images/explosion.png + + images/bear_trap.png + images/bomb0.png + images/bomb1.png + images/bomb2.png + images/bomb3.png + images/bomb4.png + images/bomb5.png + images/coin.png + + GUI/Textured boxes/Default/top_left_corner.png + GUI/Textured boxes/Default/top_right_corner.png + GUI/Textured boxes/Default/bottom_left_corner.png + GUI/Textured boxes/Default/bottom_right_corner.png + GUI/Textured boxes/Default/left_side.png + GUI/Textured boxes/Default/right_side.png + GUI/Textured boxes/Default/top_side.png + GUI/Textured boxes/Default/bottom_side.png + GUI/Textured boxes/Default/inside.png + + GUI/Textured boxes/Menu/top_left_corner.png + GUI/Textured boxes/Menu/top_right_corner.png + GUI/Textured boxes/Menu/bottom_left_corner.png + GUI/Textured boxes/Menu/bottom_right_corner.png + GUI/Textured boxes/Menu/left_side.png + GUI/Textured boxes/Menu/right_side.png + GUI/Textured boxes/Menu/top_side.png + GUI/Textured boxes/Menu/bottom_side.png + GUI/Textured boxes/Menu/inside.png + + GUI/Textured boxes/Menu2/top_left_corner.png + GUI/Textured boxes/Menu2/top_right_corner.png + GUI/Textured boxes/Menu2/bottom_left_corner.png + GUI/Textured boxes/Menu2/bottom_right_corner.png + GUI/Textured boxes/Menu2/left_side.png + GUI/Textured boxes/Menu2/right_side.png + GUI/Textured boxes/Menu2/top_side.png + GUI/Textured boxes/Menu2/bottom_side.png + GUI/Textured boxes/Menu2/inside.png + + GUI/Textured boxes/Button/top_left_corner.png + GUI/Textured boxes/Button/top_right_corner.png + GUI/Textured boxes/Button/bottom_left_corner.png + GUI/Textured boxes/Button/bottom_right_corner.png + GUI/Textured boxes/Button/left_side.png + GUI/Textured boxes/Button/right_side.png + GUI/Textured boxes/Button/top_side.png + GUI/Textured boxes/Button/bottom_side.png + GUI/Textured boxes/Button/inside.png + + \ No newline at end of file diff --git a/UI/button.cpp b/UI/button.cpp new file mode 100644 index 0000000..1b222f7 --- /dev/null +++ b/UI/button.cpp @@ -0,0 +1,63 @@ +#include "button.h" + +#include +#include + +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +TextButton::TextButton(VectorF position, const QString& text) + : TexturedBox() { + textured_box_pixmaps_ = PixmapLoader::kButtonTexturedBoxPixmaps; + text_item_ = new QGraphicsTextItem(); + icon_ = new PixmapObject(PixmapLoader::Pixmaps::kEmpty); + layout_ = new LinearLayout(); + layout_->AddItem(icon_); + layout_->AddItem(text_item_); + layout_->SetType(LinearLayout::Type::Horizontal); + padding_box_ = new PaddingBox(layout_, 0); + text_item_->setDefaultTextColor(UI::kButtonDefaultTextColor); + SetWrappingItem(padding_box_); + setPos(position); + SetText(text); + setZValue(UI::kDefaultZValue); + setCursor(Qt::PointingHandCursor); +} + +void TextButton::SetText(const QString& text) { + prepareGeometryChange(); + text_item_->setPlainText(text); +} + +void TextButton::mousePressEvent(QGraphicsSceneMouseEvent* event) { + emit Clicked(); +} + +QTextDocument* TextButton::GetTextDocument() { + return text_item_->document(); +} + +qreal TextButton::Padding() { + return padding_box_->Padding(); +} + +void TextButton::SetPadding(qreal padding) { + padding_box_->SetPadding(padding); +} + +qreal TextButton::Spacing() { + return layout_->Spacing(); +} + +void TextButton::SetSpacing(qreal spacing) { + layout_->SetSpacing(spacing); +} + +QPixmap* TextButton::Icon() { + return icon_->Pixmap(); +} + +void TextButton::SetIcon(QPixmap* icon) { + icon_->SetPixmap(icon); + layout_->RecalculatePositions(); +} diff --git a/UI/button.h b/UI/button.h new file mode 100644 index 0000000..fcf1ed9 --- /dev/null +++ b/UI/button.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "textured_box.h" +#include "Utilities/vector_f.h" +#include "padding_box.h" +#include "pixmap_object.h" +#include "linear_layout.h" + +class TextButton : public TexturedBox { + Q_OBJECT + + public: + explicit TextButton(VectorF position = {0, 0}, const QString& text = ""); + + void SetText(const QString& text); + QTextDocument* GetTextDocument(); + + qreal Padding(); + void SetPadding(qreal padding); + + qreal Spacing(); + void SetSpacing(qreal spacing); + + QPixmap* Icon(); + void SetIcon(QPixmap* icon); + + signals: + + void Clicked(); + + protected: + void mousePressEvent(QGraphicsSceneMouseEvent* event) override; + + QGraphicsTextItem* text_item_; + PixmapObject* icon_; + PaddingBox* padding_box_; + LinearLayout* layout_; +}; diff --git a/UI/linear_layout.cpp b/UI/linear_layout.cpp new file mode 100644 index 0000000..0d04c1a --- /dev/null +++ b/UI/linear_layout.cpp @@ -0,0 +1,173 @@ +#include "linear_layout.h" + +#include + +#include +#include + +#include "constants.h" + +LinearLayout::LinearLayout(QGraphicsItem* parent) + : QGraphicsObject(parent), + items_(std::vector()), + type_(Type::Horizontal) { + setZValue(UI::kDefaultZValue); +} + +QRectF LinearLayout::boundingRect() const { + return childrenBoundingRect(); +} + +void LinearLayout::OnItemDestroyed(QObject* item) { + QGraphicsObject* object = dynamic_cast(item); + assert(object != nullptr); + RemoveItem(object); +} + + +void LinearLayout::AddItem(QGraphicsObject* item) { + prepareGeometryChange(); + items_.push_back(item); + item->setParentItem(this); + ConnectItem(item); + RecalculatePositions(); +} + +void LinearLayout::RemoveItem(QGraphicsObject* item) { + auto i = std::find(items_.begin(), items_.end(), item); + if (i == items_.end()) { + qDebug() << "LinearLayout.RemoveItem: there is no item in items_"; + } + items_.erase(i); + + prepareGeometryChange(); + item->setParentItem(nullptr); + DisconnectItem(item); + RecalculatePositions(); +} + +bool LinearLayout::HasItem(QGraphicsObject* item) { + int count = std::count(items_.begin(), items_.end(), item); + assert(count <= 1); + return count == 1; +} + +// wasn't tested properly(works on text buttons and should work on +// elements, that satisfy: +// boundingBox().x() == boundingBox().y() == 0) +void LinearLayout::RecalculatePositions() { + if (type_ == Type::Horizontal) { + qreal max_height = 0; + for (int i = 0 ; i < items_.size() ; ++i) { + max_height = std::max(max_height, items_.at(i)->boundingRect().height()); + } + qreal x = 0; + for (int i = 0 ; i < items_.size() ; ++i) { + QGraphicsItem* item = items_.at(i); + qreal item_width = item->boundingRect().width() * item->scale(); + qreal item_height = item->boundingRect().height() * item->scale(); + item->setPos( + x, + max_height / 2 - item_height / 2); + x += item_width + spacing_; + } + } else if (type_ == Type::Vertical) { + qreal max_width = 0; + for (int i = 0 ; i < items_.size() ; ++i) { + max_width = std::max( + max_width, + items_.at(i)->boundingRect().width() * items_.at(i)->scale()); + } + qreal y = 0; + for (int i = 0 ; i < items_.size() ; ++i) { + QGraphicsItem* item = items_.at(i); + qreal item_width = item->boundingRect().width() * item->scale(); + qreal item_height = item->boundingRect().height() * item->scale(); + item->setPos( + max_width / 2 - item_width / 2, + y); + y += item_height + spacing_; + } + } else { + assert(false); + } +} + +qreal LinearLayout::Spacing() const { + return spacing_; +} + +void LinearLayout::SetSpacing(qreal spacing) { + if (spacing_ != spacing) { + spacing_ = spacing; + prepareGeometryChange(); + RecalculatePositions(); + } +} + +void LinearLayout::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + if (kDebugMode) { + painter->setPen(QPen(Qt::green)); + painter->drawRect(boundingRect()); + } +} + +void LinearLayout::ConnectItem(QGraphicsObject* item) { + connect( + item, + &QGraphicsObject::heightChanged, + this, + &LinearLayout::RecalculatePositions); + connect( + item, + &QGraphicsObject::widthChanged, + this, + &LinearLayout::RecalculatePositions); + connect( + item, + &QGraphicsObject::scaleChanged, + this, + &LinearLayout::RecalculatePositions); + connect( + item, + &QGraphicsObject::destroyed, + this, + &LinearLayout::OnItemDestroyed); +} + +void LinearLayout::DisconnectItem(QGraphicsObject* item) { + disconnect( + item, + &QGraphicsObject::heightChanged, + this, + &LinearLayout::RecalculatePositions); + disconnect( + item, + &QGraphicsObject::widthChanged, + this, + &LinearLayout::RecalculatePositions); + disconnect( + item, + &QGraphicsObject::scaleChanged, + this, + &LinearLayout::RecalculatePositions); + disconnect( + item, + &QGraphicsObject::destroyed, + this, + &LinearLayout::OnItemDestroyed); +} + +enum LinearLayout::Type LinearLayout::Type() const { + return type_; +} + +void LinearLayout::SetType(enum LinearLayout::Type type) { + if (type_ != type) { + type_ = type; + prepareGeometryChange(); + RecalculatePositions(); + } +} diff --git a/UI/linear_layout.h b/UI/linear_layout.h new file mode 100644 index 0000000..0f70079 --- /dev/null +++ b/UI/linear_layout.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include +#include + + + +// positions items one after another, centered by y +class LinearLayout : public QGraphicsObject { + public: + enum class Type { Horizontal, Vertical }; + + explicit LinearLayout(QGraphicsItem* parent = nullptr); + + QRectF boundingRect() const override; + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) override; + + void AddItem(QGraphicsObject* item); + void RemoveItem(QGraphicsObject* item); + bool HasItem(QGraphicsObject* item); + + qreal Spacing() const; + void SetSpacing(qreal spacing); + + Type Type() const; + void SetType(enum Type type); + + void RecalculatePositions(); + + protected: + void OnItemDestroyed(QObject* item); + void ConnectItem(QGraphicsObject* item); + void DisconnectItem(QGraphicsObject* item); + + std::vector items_; + qreal spacing_; + enum Type type_; +}; diff --git a/UI/linear_menu.cpp b/UI/linear_menu.cpp new file mode 100644 index 0000000..c156429 --- /dev/null +++ b/UI/linear_menu.cpp @@ -0,0 +1,67 @@ +#include "linear_menu.h" + +#include +#include +#include +#include + +#include "constants.h" + +LinearMenu::LinearMenu(QGraphicsItem* parent) + : TexturedBox() { + textured_box_pixmaps_ = PixmapLoader::kMenuTexturedBoxPixmaps; + layout_ = new LinearLayout(); + padding_box_ = new PaddingBox(layout_, 0); + SetWrappingItem(padding_box_); + this->setParentItem(parent); + setZValue(UI::kDefaultZValue); +} + +void LinearMenu::AddItem(QGraphicsObject* graphics_object) { + layout_->AddItem(graphics_object); +} + +void LinearMenu::RemoveItem(QGraphicsObject* graphics_object) { + layout_->RemoveItem(graphics_object); +} + +void LinearMenu::SetSpacing(qreal spacing) { + layout_->SetSpacing(spacing); +} + +qreal LinearMenu::Spacing() { + return layout_->Spacing(); +} + +qreal LinearMenu::Padding() const { + return padding_box_->Padding(); +} + +void LinearMenu::SetPadding(qreal padding) { + prepareGeometryChange(); + padding_box_->SetPadding(padding); +} + +LinearMenu::Type LinearMenu::GetType() const { + return layout_->Type(); +} + +void LinearMenu::SetType(LinearMenu::Type type) { + layout_->SetType(type); +} + +void LinearMenu::RecalculatePositions() { + layout_->RecalculatePositions(); +} + +void LinearMenu::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + TexturedBox::paint(painter, option, widget); + + if (kDebugMode) { + static QPen pen(Qt::red); + painter->setPen(pen); + painter->drawRect(TexturedBox::boundingRect()); + } +} diff --git a/UI/linear_menu.h b/UI/linear_menu.h new file mode 100644 index 0000000..e98dd90 --- /dev/null +++ b/UI/linear_menu.h @@ -0,0 +1,34 @@ +#pragma once + +#include "textured_box.h" +#include "linear_layout.h" +#include "padding_box.h" + +class LinearMenu : public TexturedBox { + public: + using Type = enum LinearLayout::Type; + + explicit LinearMenu(QGraphicsItem* parent = nullptr); + + void AddItem(QGraphicsObject* graphics_object); + void RemoveItem(QGraphicsObject* graphics_object); + + void SetSpacing(qreal spacing); + qreal Spacing(); + + qreal Padding() const; + void SetPadding(qreal padding); + + Type GetType() const; + void SetType(Type type); + + void RecalculatePositions(); + + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) override; + + protected: + LinearLayout* layout_; + PaddingBox* padding_box_; +}; diff --git a/UI/padding_box.cpp b/UI/padding_box.cpp new file mode 100644 index 0000000..1f2ccd8 --- /dev/null +++ b/UI/padding_box.cpp @@ -0,0 +1,54 @@ +#include "padding_box.h" + +#include + +#include "Utilities/vector_f.h" +#include "constants.h" + +PaddingBox::PaddingBox(QGraphicsItem* wrapping_item, qreal padding) + : wrapping_item_(wrapping_item), padding_(padding) { + wrapping_item->setParentItem(this); + wrapping_item->setPos(padding, padding); + setZValue(UI::kDefaultZValue); +} + +qreal PaddingBox::Padding() const { + return padding_; +} +void PaddingBox::SetPadding(qreal padding) { + padding_ = padding; + wrapping_item_->setPos(padding, padding); +} + +QRectF PaddingBox::boundingRect() const { + QRectF inner_rect = wrapping_item_->boundingRect(); + VectorF inner_rect_half_size = VectorF( + inner_rect.width() / 2, + inner_rect.height() / 2); + QRectF result = QRectF( + inner_rect.center() - inner_rect_half_size * scale(), + inner_rect.center() + inner_rect_half_size * scale()); + result.translate(-result.x(), -result.y()); + result.adjust(0, 0, 2 * padding_, 2 * padding_); + return result; +} + +void PaddingBox::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + if (kDebugMode) { + static QPen white_pen(Qt::white); + static QPen green_pen(Qt::green); + static QPen blue_pen(Qt::blue); + + painter->setPen(white_pen); + painter->drawRect(boundingRect()); + + painter->setPen(green_pen); + painter->drawRect( + wrapping_item_->boundingRect().translated(wrapping_item_->pos())); + + painter->setPen(blue_pen); + painter->drawEllipse(-5, -5, 10, 10); + } +} diff --git a/UI/padding_box.h b/UI/padding_box.h new file mode 100644 index 0000000..38da9f6 --- /dev/null +++ b/UI/padding_box.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +class PaddingBox : public QGraphicsObject { + public: + PaddingBox(QGraphicsItem* wrapping_item, qreal padding); + + qreal Padding() const; + void SetPadding(qreal padding); + + QRectF boundingRect() const override; + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) override; + + protected: + qreal padding_; + QGraphicsItem* wrapping_item_; +}; diff --git a/UI/pixmap_object.cpp b/UI/pixmap_object.cpp new file mode 100644 index 0000000..9ca9ab0 --- /dev/null +++ b/UI/pixmap_object.cpp @@ -0,0 +1,24 @@ +#include "pixmap_object.h" + +#include + +PixmapObject::PixmapObject(QPixmap* pixmap, QGraphicsItem* parent) + : QGraphicsObject(parent), pixmap_(pixmap) {} + +QRectF PixmapObject::boundingRect() const { + return pixmap_->rect(); +} + +void PixmapObject::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + painter->drawPixmap(0, 0, *pixmap_); +} + +QPixmap* PixmapObject::Pixmap() const { + return pixmap_; +} + +void PixmapObject::SetPixmap(QPixmap* pixmap) { + pixmap_ = pixmap; +} diff --git a/UI/pixmap_object.h b/UI/pixmap_object.h new file mode 100644 index 0000000..8380d3d --- /dev/null +++ b/UI/pixmap_object.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +class PixmapObject : public QGraphicsObject { + public: + explicit PixmapObject(QPixmap* pixmap, QGraphicsItem* parent = nullptr); + + QRectF boundingRect() const override; + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) override; + + QPixmap* Pixmap() const; + void SetPixmap(QPixmap* pixmap); + + protected: + QPixmap* pixmap_; +}; + + diff --git a/UI/resource_displayer.cpp b/UI/resource_displayer.cpp new file mode 100644 index 0000000..3ae4e26 --- /dev/null +++ b/UI/resource_displayer.cpp @@ -0,0 +1,57 @@ +#include "resource_displayer.h" + +#include + +#include "padding_box.h" +#include "Utilities/vector_f.h" +#include "constants.h" + +ResourcesDisplayer::ResourcesDisplayer() { + resources_layout_ = new LinearLayout(); + + textured_box_pixmaps_ = PixmapLoader::kDefaultTexturedBoxPixmaps; + SetWrappingItem(resources_layout_); + + LinearLayout* money_layout = new LinearLayout(); + LinearLayout* hp_layout = new LinearLayout(); + + money_icon_ = new PixmapObject(PixmapLoader::Pixmaps::kMoneyIcon); + hp_icon_ = new PixmapObject(PixmapLoader::Pixmaps::kHealthStatus1); + + money_item_ = new QGraphicsTextItem(); + hp_item_ = new QGraphicsTextItem(); + money_item_->setPlainText("money"); + hp_item_->setPlainText("hp"); + QFont money_font = money_item_->font(); + QFont hp_font = hp_item_->font(); + money_font.setPixelSize(20); + hp_font.setPixelSize(20); + money_item_->setFont(money_font); + hp_item_->setFont(hp_font); + + money_layout->AddItem(new PaddingBox(money_icon_, 10)); + money_layout->AddItem(new PaddingBox(money_item_, 10)); + + hp_layout->AddItem(hp_icon_); + hp_layout->AddItem(hp_item_); + + resources_layout_->AddItem(money_layout); + resources_layout_->AddItem(hp_layout); + + resources_layout_->RecalculatePositions(); +} + +void ResourcesDisplayer::SetMoney(int money) { + money_item_->setPlainText("Money: " + QString::number(money)); + resources_layout_->RecalculatePositions(); + RecalculateInnerPos(); +} + +void ResourcesDisplayer::SetHp(int hp) { + hp_item_->setPlainText("Hp: " + QString::number(hp)); + if (hp < 5 / 2) { // half hp + hp_icon_->SetPixmap(PixmapLoader::Pixmaps::kHealthStatus2); + } + resources_layout_->RecalculatePositions(); + RecalculateInnerPos(); +} diff --git a/UI/resource_displayer.h b/UI/resource_displayer.h new file mode 100644 index 0000000..748ab24 --- /dev/null +++ b/UI/resource_displayer.h @@ -0,0 +1,20 @@ +#pragma once + +#include "textured_box.h" +#include "linear_layout.h" +#include "pixmap_object.h" + +class ResourcesDisplayer : public TexturedBox { + public: + ResourcesDisplayer(); + + void SetMoney(int money); + void SetHp(int hp); + + private: + LinearLayout* resources_layout_; + QGraphicsTextItem* money_item_; + QGraphicsTextItem* hp_item_; + PixmapObject* money_icon_; + PixmapObject* hp_icon_; +}; diff --git a/UI/textured_box.cpp b/UI/textured_box.cpp new file mode 100644 index 0000000..45e237f --- /dev/null +++ b/UI/textured_box.cpp @@ -0,0 +1,203 @@ +#include "textured_box.h" + +#include + +#include "Utilities/vector_f.h" +#include "constants.h" + +TexturedBox::TexturedBox( + QGraphicsItem* wrapping_item, + TexturedBoxPixmaps textured_box_pixmaps) + : textured_box_pixmaps_(textured_box_pixmaps), + wrapping_item_(wrapping_item), + origin_point_(OriginPoint::kTopLeft) { + SetWrappingItem(wrapping_item_); + setZValue(UI::kDefaultZValue); +} + +TexturedBox::TexturedBox() + : origin_point_(OriginPoint::kTopLeft) { + setZValue(UI::kDefaultZValue); +} + +void TexturedBox::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + TexturedBoxPixmaps& pixmaps = textured_box_pixmaps_; + + QRectF bounding_rect = boundingRect(); + QRectF inner_rect = wrapping_item_->boundingRect().translated( + pixmaps.left_side->width(), + pixmaps.top_side->height()) + .translated(bounding_rect.topLeft()); + + QRectF left_side_rect = { + bounding_rect.x(), + inner_rect.y(), + static_cast(pixmaps.left_side->width()), + inner_rect.height()}; + QRectF right_side_rect = { + inner_rect.right(), + inner_rect.y(), + static_cast(pixmaps.right_side->width()), + inner_rect.height()}; + QRectF top_side_rect = { + inner_rect.x(), + bounding_rect.y(), + inner_rect.width(), + static_cast(pixmaps.top_side->height())}; + QRectF bottom_side_rect = { + inner_rect.x(), + inner_rect.bottom(), + inner_rect.width(), + static_cast(pixmaps.bottom_side->height())}; + + // there are -1 because of gaps + painter->drawPixmap( + inner_rect.adjusted(-1, -1, 1, 1), + *pixmaps.inside, + pixmaps.inside->rect()); + + painter->drawPixmap( + left_side_rect.adjusted(0, -1, 0, 1), + *pixmaps.left_side, + pixmaps.left_side->rect()); + painter->drawPixmap( + right_side_rect.adjusted(0, -1, 0, 1), + *pixmaps.right_side, + pixmaps.right_side->rect()); + painter->drawPixmap( + top_side_rect.adjusted(-1, 0, 1, 0), + *pixmaps.top_side, + pixmaps.top_side->rect()); + painter->drawPixmap( + bottom_side_rect.adjusted(-1, 0, 1, 0), + *pixmaps.bottom_side, + pixmaps.bottom_side->rect()); + + + VectorF top_left_corner_pos = { + bounding_rect.left(), + bounding_rect.top() + }; + VectorF top_right_corner_pos = { + bounding_rect.right() - pixmaps.top_right_corner->width(), + bounding_rect.top() + }; + VectorF bottom_left_corner_pos = { + bounding_rect.left(), + bounding_rect.bottom() - pixmaps.bottom_left_corner->height() + }; + VectorF bottom_right_corner_pos = { + bounding_rect.right() - pixmaps.bottom_right_corner->width(), + bounding_rect.bottom() - pixmaps.bottom_right_corner->height() + }; + + painter->drawPixmap(top_left_corner_pos, *pixmaps.top_left_corner); + painter->drawPixmap(top_right_corner_pos, *pixmaps.top_right_corner); + painter->drawPixmap(bottom_left_corner_pos, *pixmaps.bottom_left_corner); + painter->drawPixmap(bottom_right_corner_pos, *pixmaps.bottom_right_corner); + + if (kDebugMode) { + painter->setPen(QPen(Qt::white, 3)); + painter->drawRect(boundingRect()); + + painter->setPen(QPen(Qt::yellow, 5)); + painter->drawRect(inner_rect); + } +} + +QRectF TexturedBox::boundingRect() const { + qreal left_side_width = textured_box_pixmaps_.left_side->width(); + qreal right_side_width = textured_box_pixmaps_.right_side->width(); + qreal top_side_height = textured_box_pixmaps_.top_side->height(); + qreal bottom_side_height = textured_box_pixmaps_.bottom_side->height(); + + QRectF unscaled_result = wrapping_item_->boundingRect().adjusted( + 0, + 0, + right_side_width + left_side_width, + bottom_side_height + top_side_height); + VectorF half_size = + VectorF(unscaled_result.width(), unscaled_result.height()) / 2; + + return QRectF( + unscaled_result.center() - half_size, + unscaled_result.center() + half_size); + + QRectF result = QRectF( + unscaled_result.center() - half_size, + unscaled_result.center() + half_size); + + return ConvertRectConsideringOriginPoint(result); +} + +QRectF TexturedBox::ConvertRectConsideringOriginPoint(QRectF rect) const { + assert(rect.top() == 0); + assert(rect.left() == 0); + + switch (origin_point_) { + case OriginPoint::kTopLeft: + return rect; + + break; + case OriginPoint::kTopRight: + rect.translate(-rect.width(), 0); + return rect; + + break; + case OriginPoint::kBottomLeft: + rect.translate(0, -rect.height()); + return rect; + + break; + case OriginPoint::kBottomRight: + rect.translate(-rect.width(), -rect.height()); + return rect; + + break; + default: + assert(false); + throw std::exception(); + break; + } +} + +void TexturedBox::RecalculateInnerPos() { + VectorF wrapping_item_pos = VectorF( + textured_box_pixmaps_.left_side->width(), + textured_box_pixmaps_.top_side->height()); + + VectorF origin_point_pos_fix2 = boundingRect().topLeft(); + + wrapping_item_pos += origin_point_pos_fix2; + wrapping_item_->setPos(wrapping_item_pos); +} + +void TexturedBox::SetTexturedBoxPixmaps(const TexturedBoxPixmaps& pixmaps) { + prepareGeometryChange(); + textured_box_pixmaps_ = pixmaps; + wrapping_item_->setPos( + textured_box_pixmaps_.left_side->width(), + textured_box_pixmaps_.top_side->height()); + RecalculateInnerPos(); +} + +void TexturedBox::SetWrappingItem(QGraphicsItem* wrapping_item) { + prepareGeometryChange(); + wrapping_item_ = wrapping_item; + wrapping_item->setParentItem(this); + wrapping_item->setPos( + textured_box_pixmaps_.left_side->width(), + textured_box_pixmaps_.top_side->height()); + RecalculateInnerPos(); +} + +enum TexturedBox::OriginPoint TexturedBox::OriginPoint() const { + return origin_point_; +} + +void TexturedBox::SetOriginPoint(enum TexturedBox::OriginPoint origin_point) { + origin_point_ = origin_point; + RecalculateInnerPos(); +} diff --git a/UI/textured_box.h b/UI/textured_box.h new file mode 100644 index 0000000..7a393df --- /dev/null +++ b/UI/textured_box.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "Utilities/Resources/pixmap_loader.h" +#include "Utilities/Resources/textured_box_pixmaps.h" + +class TexturedBox : public QGraphicsObject { + public: + enum class OriginPoint { kTopLeft, kBottomLeft, kTopRight, kBottomRight }; + + explicit TexturedBox( + QGraphicsItem* wrapping_item, + TexturedBoxPixmaps textured_box_pixmaps = + PixmapLoader::kDefaultTexturedBoxPixmaps); + + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) override; + QRectF boundingRect() const override; + + void SetTexturedBoxPixmaps(const TexturedBoxPixmaps& pixmaps); + void SetWrappingItem(QGraphicsItem* wrapping_item); + + OriginPoint OriginPoint() const; + void SetOriginPoint(enum OriginPoint origin_point); + + void RecalculateInnerPos(); + + protected: + TexturedBox(); + + QRectF ConvertRectConsideringOriginPoint(QRectF rect) const; + + TexturedBoxPixmaps textured_box_pixmaps_; + QGraphicsItem* wrapping_item_; + enum OriginPoint origin_point_; +}; diff --git a/UI/tooltip.cpp b/UI/tooltip.cpp new file mode 100644 index 0000000..cbfa5f9 --- /dev/null +++ b/UI/tooltip.cpp @@ -0,0 +1,52 @@ +#include "tooltip.h" + +#include +#include + +#include "Utilities/Resources/pixmap_loader.h" +#include "Utilities/vector_f.h" +#include "constants.h" + +Tooltip::Tooltip(QGraphicsItem* parent) + : TexturedBox(), text_item_(new QGraphicsTextItem()) { + textured_box_pixmaps_ = PixmapLoader::kDefaultTexturedBoxPixmaps; + SetWrappingItem(text_item_); + setZValue(UI::kDefaultZValue); +} + +Tooltip::Tooltip(QString text, QGraphicsItem* parent) + : Tooltip(parent) { + setPlainText(text); +} + +void Tooltip::setPlainText(const QString& text) { + qreal initial_text_width = text_item_->document()->size().width(); + text_item_->setPlainText(text); + qreal current_text_width = text_item_->document()->size().width(); + + moveBy(initial_text_width / 2, 0); + moveBy(- current_text_width / 2, 0); + + prepareGeometryChange(); +} + +void Tooltip::setHtml(const QString& text) { + qreal initial_text_width = text_item_->document()->size().width(); + text_item_->setHtml(text); + qreal current_text_width = text_item_->document()->size().width(); + + moveBy(initial_text_width / 2, 0); + moveBy(- current_text_width / 2, 0); + + prepareGeometryChange(); +} + +void Tooltip::setPos(qreal ax, qreal ay) { + QGraphicsItem::setPos(ax, ay); + qreal text_width = boundingRect().width(); + moveBy(- text_width / 2, 0); +} + +void Tooltip::setPos(const QPointF& pos) { + setPos(pos.x(), pos.y()); +} diff --git a/UI/tooltip.h b/UI/tooltip.h new file mode 100644 index 0000000..19aeb65 --- /dev/null +++ b/UI/tooltip.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "textured_box.h" + +// it's position = top(not very accurate) center point +class Tooltip : public TexturedBox { + public: + explicit Tooltip(QGraphicsItem* parent = nullptr); + explicit Tooltip(QString text, QGraphicsItem* parent = nullptr); + + void setPlainText(const QString& text); + void setHtml(const QString& text); + + void setPos(qreal ax, qreal ay); + void setPos(const QPointF& pos); + + protected: + QGraphicsTextItem* text_item_; +}; diff --git a/Utilities/Resources/pixmap_loader.cpp b/Utilities/Resources/pixmap_loader.cpp new file mode 100644 index 0000000..46eceff --- /dev/null +++ b/Utilities/Resources/pixmap_loader.cpp @@ -0,0 +1,574 @@ +#include + +#include "constants.h" +#include "pixmap_loader.h" + +using P = PixmapLoader::Pixmaps; + +QPixmap* P::kBackground; +QPixmap* P::kMagicProjectileLevel1; +QPixmap* P::kMagicProjectileLevel2; +QPixmap* P::kMagicProjectileLevel3; +QPixmap* P::kCannonProjectileLevel1; +QPixmap* P::kCannonProjectileLevel2; +QPixmap* P::kCannonProjectileLevel3; +QPixmap* P::kMagicTowerLevel1; +QPixmap* P::kMagicTowerLevel2; +QPixmap* P::kMagicTowerLevel3; +QPixmap* P::kCannonTowerLevel1; +QPixmap* P::kCannonTowerLevel2; +QPixmap* P::kCannonTowerLevel3; +QPixmap* P::kTowerSlot; +std::vector P::kLevelMaps; +QPixmap* P::kMoneyIcon; +QPixmap* P::kHealthStatus1; +QPixmap* P::kHealthStatus2; +QPixmap* P::kEmpty; + +QPixmap* P::kBombIdleFrame; +QPixmap* P::kBomb0; +QPixmap* P::kBomb1; +QPixmap* P::kBomb2; +QPixmap* P::kBomb3; +QPixmap* P::kBomb4; +QPixmap* P::kBomb5; +std::vector P::kBombExplosion; +std::vector P::kBombIdle; + +QPixmap* P::kBearTrap; +QPixmap* P::kBearTrapAnimations; +std::vector P::kBearTrapIdle; +std::vector P::kBearTrapAttacking; +std::vector P::kBearTrapBroken; +std::vector P::kBearTrapRepairing; + +std::vector P::kCoinIdle; +QPixmap* P::kCoinAnimations; + +QPixmap* P::FireTotem::kAnimations; +std::vector P::FireTotem::kIdle; +std::vector P::FireTotem::kDisappear; +std::vector P::FireTotem::kAppearing; + +std::vector P::Skeleton::kWalk; +std::vector P::Skeleton::kDeath; + +QPixmap* P::Cobra::kAnimations; +std::vector P::Cobra::kWalk; +std::vector P::Cobra::kDeath; + +QPixmap* P::Hedgehog::kAnimations; +std::vector P::Hedgehog::kWalk; +std::vector P::Hedgehog::kDeath; + +QPixmap* P::Dwarf::kAnimations; +std::vector P::Dwarf::kWalk; +std::vector P::Dwarf::kDeath; + +QPixmap* P::Explosion::kAnimations; +std::vector P::Explosion::kExplosion; + +QPixmap* P::MagicProjectile::kAnimationsLevel1; +QPixmap* P::MagicProjectile::kAnimationsLevel2; +QPixmap* P::MagicProjectile::kAnimationsLevel3; +std::vector P::MagicProjectile::kDestroyingLevel1; +std::vector P::MagicProjectile::kDestroyingLevel2; +std::vector P::MagicProjectile::kDestroyingLevel3; +// ----------------------------------------------------------------------------- + +TexturedBoxPixmaps PixmapLoader::kDefaultTexturedBoxPixmaps; +TexturedBoxPixmaps PixmapLoader::kMenuTexturedBoxPixmaps; +TexturedBoxPixmaps PixmapLoader::kMenu2TexturedBoxPixmaps; +TexturedBoxPixmaps PixmapLoader::kButtonTexturedBoxPixmaps; + +void PixmapLoader::LoadPixmaps() { + P::kBackground = new QPixmap(":images/background.png"); + P::kMagicProjectileLevel1 = + new QPixmap(":images/magic_projectile_level1.png"); + P::kMagicProjectileLevel2 = + new QPixmap(":images/magic_projectile_level2.png"); + P::kMagicProjectileLevel3 = + new QPixmap(":images/magic_projectile_level3.png"); + P::kCannonProjectileLevel1 = + new QPixmap(":images/cannon_projectile_level1.png"); + P::kCannonProjectileLevel2 = + new QPixmap(":images/cannon_projectile_level2.png"); + P::kCannonProjectileLevel3 = + new QPixmap(":images/cannon_projectile_level3.png"); + P::kBearTrap = new QPixmap(":images/bear_trap.png"); + P::kTowerSlot = new QPixmap(":images/tower_slot.png"); + P::kMagicTowerLevel1 = new QPixmap(":images/magic_tower_level1.png"); + P::kMagicTowerLevel2 = new QPixmap(":images/magic_tower_level2.png"); + P::kMagicTowerLevel3 = new QPixmap(":images/magic_tower_level3.png"); + P::kCannonTowerLevel1 = new QPixmap(":images/cannon_tower_level1.png"); + P::kCannonTowerLevel2 = new QPixmap(":images/cannon_tower_level2.png"); + P::kCannonTowerLevel3 = new QPixmap(":images/cannon_tower_level3.png"); + P::kMoneyIcon = new QPixmap(":images/money_icon.png"); + P::kHealthStatus1 = new QPixmap(":images/health_status_1.png"); + P::kHealthStatus2 = new QPixmap(":images/health_status_2.png"); + for (int i = 1; i <= LevelData::kLevelsCount; ++i) { + P::kLevelMaps.push_back(new QPixmap(":Levels/Level" + + QString::number(i) + + "/map.png")); + } + P::kEmpty = new QPixmap(); + + LoadBearTrapAnimations(); + LoadBombAnimations(); + LoadCoinAnimations(); + LoadFireTotemAnimations(); + LoadSkeletonAnimations(); + LoadCobraAnimations(); + LoadHedgehogAnimations(); + LoadDwarfAnimations(); + LoadExplosionAnimation(); + LoadUI(); + LoadMagicProjectileAnimations(); +} + +std::vector PixmapLoader::CreateHorizontalFramesVector( + QPixmap* source, + int frame_width, + int frame_height, + int frames_count, + int start_x, + int y) { + std::vector result; + + for (int i = 0; i < frames_count; ++i) { + int x = i * frame_width + start_x; + QPixmap* frame = new QPixmap(std::move(source->copy( + QRect(x, y, frame_width, frame_height)))); + result.push_back(frame); + } + + return result; +} + +void PixmapLoader::LoadFireTotemAnimations() { + // file size - 896x480 + // 5 frame rows, 14 frame columns + const int frame_width = 896 / 14; + const int frame_height = 480 / 5; + const int idle_animation_frames_count = 7; + const int disappear_animation_frames_count = 14; + const int appear_animation_frames_count = 8; + // row and column start from 0 + const int idle_animation_row = 1; + const int idle_animation_column = 0; + const int disappear_animation_row = 4; + const int disappear_animation_column = 0; + const int appear_animation_row = 0; + const int appear_animation_column = 0; + + P::FireTotem::kAnimations = new QPixmap(":images/fire_totem.png"); + + P::FireTotem::kIdle = CreateHorizontalFramesVector( + P::FireTotem::kAnimations, + frame_width, + frame_height, + idle_animation_frames_count, + idle_animation_column * frame_width, + idle_animation_row * frame_height); + + P::FireTotem::kDisappear = CreateHorizontalFramesVector( + P::FireTotem::kAnimations, + frame_width, + frame_height, + disappear_animation_frames_count, + disappear_animation_column * frame_width, + disappear_animation_row * frame_height); + + P::FireTotem::kAppearing = CreateHorizontalFramesVector( + P::FireTotem::kAnimations, + frame_width, + frame_height, + appear_animation_frames_count, + appear_animation_column * frame_width, + appear_animation_row * frame_height); +} + +void PixmapLoader::LoadSkeletonAnimations() { + // walk 13 frame columns + // death 14 frame columns + // walk file size - 286x33 + // death file size - 495x32 + const int frame_walk_width = 286 / 13; + const int frame_walk_height = 33; + const int walk_animation_frames_count = 13; + + const int frame_death_width = 495 / 15; + const int frame_death_height = 32; + const int death_animation_frames_count = 14; + + // row and column start from 0 + const int walk_animation_row = 0; + const int walk_animation_column = 0; + + const int death_animation_row = 0; + const int death_animation_column = 1; + + QPixmap* walk_animation = new QPixmap(":images/skeleton/walk.png"); + QPixmap* death_animation = new QPixmap(":images/skeleton/death.png"); + + P::Skeleton::kWalk = CreateHorizontalFramesVector( + walk_animation, + frame_walk_width, + frame_walk_height, + walk_animation_frames_count, + walk_animation_column * frame_walk_width, + walk_animation_row * frame_walk_height); + + P::Skeleton::kDeath = CreateHorizontalFramesVector( + death_animation, + frame_death_width, + frame_death_height, + death_animation_frames_count, + death_animation_column * frame_death_width, + death_animation_row * frame_death_height); + + delete walk_animation; + delete death_animation; +} + +void PixmapLoader::LoadCobraAnimations() { + // file size - 256x160 + // 5 frame rows, 8 frame columns + const int frame_width = 256 / 8; + const int frame_height = 160 / 5; + const int death_animation_frames_count = 6; + const int walk_animation_frames_count = 8; + // row and column start from 0 + const int death_animation_row = 4; + const int death_animation_column = 0; + const int walk_animation_row = 1; + const int walk_animation_column = 0; + + P::Cobra::kAnimations = new QPixmap(":images/cobra.png"); + + P::Cobra::kWalk = CreateHorizontalFramesVector( + P::Cobra::kAnimations, + frame_width, + frame_height, + walk_animation_frames_count, + walk_animation_column * frame_width, + walk_animation_row * frame_height); + + P::Cobra::kDeath = CreateHorizontalFramesVector( + P::Cobra::kAnimations, + frame_width, + frame_height, + death_animation_frames_count, + death_animation_column * frame_width, + death_animation_row * frame_height); +} + +void PixmapLoader::LoadHedgehogAnimations() { + // file size - 128x128 + // 4 frame rows, 4 frame columns + const int frame_width = 128 / 4; + const int frame_height = 128 / 4; + const int death_animation_frames_count = 3; + const int walk_animation_frames_count = 4; + // row and column start from 0 + const int death_animation_row = 3; + const int death_animation_column = 0; + const int walk_animation_row = 1; + const int walk_animation_column = 0; + + P::Hedgehog::kAnimations = new QPixmap(":images/hedgehog.png"); + + P::Hedgehog::kWalk = CreateHorizontalFramesVector( + P::Hedgehog::kAnimations, + frame_width, + frame_height, + walk_animation_frames_count, + walk_animation_column * frame_width, + walk_animation_row * frame_height); + + P::Hedgehog::kDeath = CreateHorizontalFramesVector( + P::Hedgehog::kAnimations, + frame_width, + frame_height, + death_animation_frames_count, + death_animation_column * frame_width, + death_animation_row * frame_height); +} + +void PixmapLoader::LoadDwarfAnimations() { + // file size - 512x256 + // 8 frame rows, 8 frame columns + const int frame_width = 512 / 8; + const int frame_height = 256 / 8; + const int death_animation_frames_count = 7; + const int walk_animation_frames_count = 8; + // row and column start from 0 + const int death_animation_row = 7; + const int death_animation_column = 0; + const int walk_animation_row = 1; + const int walk_animation_column = 0; + + P::Dwarf::kAnimations = new QPixmap(":images/dwarf.png"); + + P::Dwarf::kWalk = CreateHorizontalFramesVector( + P::Dwarf::kAnimations, + frame_width, + frame_height, + walk_animation_frames_count, + walk_animation_column * frame_width, + walk_animation_row * frame_height); + + P::Dwarf::kDeath = CreateHorizontalFramesVector( + P::Dwarf::kAnimations, + frame_width, + frame_height, + death_animation_frames_count, + death_animation_column * frame_width, + death_animation_row * frame_height); +} + +void PixmapLoader::LoadExplosionAnimation() { + // file size - 384x48 + // 1 frame rows, 8 frame columns + const int animation_frames_count = 8; + const int frame_width = 384 / animation_frames_count; + const int frame_height = 48 / 1; + // row and column start from 0 + const int animation_row = 0; + const int animation_column = 0; + + P::Explosion::kAnimations = new QPixmap(":images/explosion.png"); + + P::Explosion::kExplosion = CreateHorizontalFramesVector( + P::Explosion::kAnimations, + frame_width, + frame_height, + animation_frames_count, + animation_column * frame_width, + animation_row * frame_height); +} + +void PixmapLoader::LoadUI() { + LoadDefaultTextureBox(); + LoadMenuTextureBox(); + LoadMenu2TextureBox(); + LoadButtonTextureBox(); +} + +void PixmapLoader::LoadDefaultTextureBox() { + kDefaultTexturedBoxPixmaps = TexturedBoxPixmaps{ + new QPixmap(":GUI/Textured boxes/Default/top_left_corner.png"), + new QPixmap(":GUI/Textured boxes/Default/top_right_corner.png"), + new QPixmap(":GUI/Textured boxes/Default/bottom_left_corner.png"), + new QPixmap(":GUI/Textured boxes/Default/bottom_right_corner.png"), + new QPixmap(":GUI/Textured boxes/Default/left_side.png"), + new QPixmap(":GUI/Textured boxes/Default/right_side.png"), + new QPixmap(":GUI/Textured boxes/Default/top_side.png"), + new QPixmap(":GUI/Textured boxes/Default/bottom_side.png"), + new QPixmap(":GUI/Textured boxes/Default/inside.png") + }; +} + +void PixmapLoader::LoadMenuTextureBox() { + kMenuTexturedBoxPixmaps = TexturedBoxPixmaps{ + new QPixmap(":GUI/Textured boxes/Menu/top_left_corner.png"), + new QPixmap(":GUI/Textured boxes/Menu/top_right_corner.png"), + new QPixmap(":GUI/Textured boxes/Menu/bottom_left_corner.png"), + new QPixmap(":GUI/Textured boxes/Menu/bottom_right_corner.png"), + new QPixmap(":GUI/Textured boxes/Menu/left_side.png"), + new QPixmap(":GUI/Textured boxes/Menu/right_side.png"), + new QPixmap(":GUI/Textured boxes/Menu/top_side.png"), + new QPixmap(":GUI/Textured boxes/Menu/bottom_side.png"), + new QPixmap(":GUI/Textured boxes/Menu/inside.png") + }; +} + +void PixmapLoader::LoadMenu2TextureBox() { + kMenu2TexturedBoxPixmaps = TexturedBoxPixmaps{ + new QPixmap(":GUI/Textured boxes/Menu2/top_left_corner.png"), + new QPixmap(":GUI/Textured boxes/Menu2/top_right_corner.png"), + new QPixmap(":GUI/Textured boxes/Menu2/bottom_left_corner.png"), + new QPixmap(":GUI/Textured boxes/Menu2/bottom_right_corner.png"), + new QPixmap(":GUI/Textured boxes/Menu2/left_side.png"), + new QPixmap(":GUI/Textured boxes/Menu2/right_side.png"), + new QPixmap(":GUI/Textured boxes/Menu2/top_side.png"), + new QPixmap(":GUI/Textured boxes/Menu2/bottom_side.png"), + new QPixmap(":GUI/Textured boxes/Menu2/inside.png") + }; +} + +void PixmapLoader::LoadButtonTextureBox() { + kButtonTexturedBoxPixmaps = TexturedBoxPixmaps{ + new QPixmap(":GUI/Textured boxes/Button/top_left_corner.png"), + new QPixmap(":GUI/Textured boxes/Button/top_right_corner.png"), + new QPixmap(":GUI/Textured boxes/Button/bottom_left_corner.png"), + new QPixmap(":GUI/Textured boxes/Button/bottom_right_corner.png"), + new QPixmap(":GUI/Textured boxes/Button/left_side.png"), + new QPixmap(":GUI/Textured boxes/Button/right_side.png"), + new QPixmap(":GUI/Textured boxes/Button/top_side.png"), + new QPixmap(":GUI/Textured boxes/Button/bottom_side.png"), + new QPixmap(":GUI/Textured boxes/Button/inside.png") + }; +} + +void PixmapLoader::LoadBearTrapAnimations() { + // file size - 128x32 + // 1 frame row, 4 frame columns + const int frame_width = 128 / 4; + const int frame_height = 32; + const int idle_animation_frames_count = 1; + const int attack_animation_frames_count = 3; + const int broken_animation_frames_count = 1; + const int repairing_animation_frames_count = 1; + // row and column start from 0 + const int idle_animation_row = 0; + const int idle_animation_column = 0; + const int attack_animation_row = 0; + const int attack_animation_column = 1; + const int broken_animation_row = 0; + const int broken_animation_column = 3; + const int repairing_animation_row = 0; + const int repairing_animation_column = 0; + + P::kBearTrapAnimations = new QPixmap(":images/bear_trap.png"); + + P::kBearTrapIdle = CreateHorizontalFramesVector( + P::kBearTrapAnimations, + frame_width, + frame_height, + idle_animation_frames_count, + idle_animation_column * frame_width, + idle_animation_row * frame_height); + + P::kBearTrapBroken = CreateHorizontalFramesVector( + P::kBearTrapAnimations, + frame_width, + frame_height, + broken_animation_frames_count, + broken_animation_column * frame_width, + broken_animation_row * frame_height); + + P::kBearTrapAttacking = CreateHorizontalFramesVector( + P::kBearTrapAnimations, + frame_width, + frame_height, + attack_animation_frames_count, + attack_animation_column * frame_width, + attack_animation_row * frame_height); + + P::kBearTrapAttacking = CreateHorizontalFramesVector( + P::kBearTrapAnimations, + frame_width, + frame_height, + attack_animation_frames_count, + attack_animation_column * frame_width, + attack_animation_row * frame_height); + + P::kBearTrapRepairing = CreateHorizontalFramesVector( + P::kBearTrapAnimations, + frame_width, + frame_height, + repairing_animation_frames_count, + repairing_animation_column * frame_width, + repairing_animation_row * frame_height); +} + +void PixmapLoader::LoadBombAnimations() { + P::kBombIdleFrame = new QPixmap(":images/bomb0.png"); + P::kBomb0 = new QPixmap(":images/bomb0.png"); + P::kBomb1 = new QPixmap(":images/bomb1.png"); + P::kBomb2 = new QPixmap(":images/bomb2.png"); + P::kBomb3 = new QPixmap(":images/bomb3.png"); + P::kBomb4 = new QPixmap(":images/bomb4.png"); + P::kBomb5 = new QPixmap(":images/bomb5.png"); + P::kBombExplosion.push_back(P::kBomb0); + P::kBombExplosion.push_back(P::kBomb1); + P::kBombExplosion.push_back(P::kBomb2); + P::kBombExplosion.push_back(P::kBomb3); + P::kBombExplosion.push_back(P::kBomb4); + P::kBombExplosion.push_back(P::kBomb5); + P::kBombIdle.push_back(P::kBombIdleFrame); +} + +void PixmapLoader::LoadCoinAnimations() { + // file size - 224x16 + // 1 frame rows, 14 frame columns + const int frame_width = 224 / 14; + const int frame_height = 16; + const int idle_animation_frames_count = 14; + // row and column start from 0 + const int idle_animation_row = 0; + const int idle_animation_column = 0; + + P::kCoinAnimations = new QPixmap(":images/coin.png"); + + P::kCoinIdle = CreateHorizontalFramesVector( + P::kCoinAnimations, + frame_width, + frame_height, + idle_animation_frames_count, + idle_animation_column * frame_width, + idle_animation_row * frame_height); +} + +void PixmapLoader::LoadMagicProjectileAnimations() { + // file size - 48x32 + // 2 frame rows, 3 frame columns + const int frame_width_level1 = 48 / 3; + const int frame_height_level1 = 32 / 2; + const int destroying_animation_frames_count_level1 = 3; + // row and column start from 0 + const int destroying_animation_row_level1 = 1; + const int destroying_animation_column_level1 = 0; + + P::MagicProjectile::kAnimationsLevel1 = + new QPixmap(":images/magic_projectile_animations_level1.png"); + + P::MagicProjectile::kDestroyingLevel1 = CreateHorizontalFramesVector( + P::MagicProjectile::kAnimationsLevel1, + frame_width_level1, + frame_height_level1, + destroying_animation_frames_count_level1, + destroying_animation_column_level1 * frame_width_level1, + destroying_animation_row_level1 * frame_height_level1); + + // file size - 48x32 + // 2 frame rows, 3 frame columns + const int frame_width_level2 = 48 / 3; + const int frame_height_level2 = 32 / 2; + const int destroying_animation_frames_count_level2 = 3; + // row and column start from 0 + const int destroying_animation_row_level2 = 1; + const int destroying_animation_column_level2 = 0; + + P::MagicProjectile::kAnimationsLevel2 = + new QPixmap(":images/magic_projectile_animations_level2.png"); + + P::MagicProjectile::kDestroyingLevel2 = CreateHorizontalFramesVector( + P::MagicProjectile::kAnimationsLevel2, + frame_width_level2, + frame_height_level2, + destroying_animation_frames_count_level2, + destroying_animation_column_level2 * frame_width_level2, + destroying_animation_row_level2 * frame_height_level2); + + // file size - 48x32 + // 2 frame rows, 3 frame columns + const int frame_width_level3 = 48 / 3; + const int frame_height_level3 = 32 / 2; + const int destroying_animation_frames_count_level3 = 3; + // row and column start from 0 + const int destroying_animation_row_level3 = 1; + const int destroying_animation_column_level3 = 0; + + P::MagicProjectile::kAnimationsLevel3 = + new QPixmap(":images/magic_projectile_animations_level3.png"); + + P::MagicProjectile::kDestroyingLevel3 = CreateHorizontalFramesVector( + P::MagicProjectile::kAnimationsLevel3, + frame_width_level3, + frame_height_level3, + destroying_animation_frames_count_level3, + destroying_animation_column_level3 * frame_width_level3, + destroying_animation_row_level3 * frame_height_level3); +} diff --git a/Utilities/Resources/pixmap_loader.h b/Utilities/Resources/pixmap_loader.h new file mode 100644 index 0000000..a91f8ca --- /dev/null +++ b/Utilities/Resources/pixmap_loader.h @@ -0,0 +1,134 @@ +#pragma once + +#include + +#include + +#include "Utilities/animation.h" +#include "Utilities/Resources/textured_box_pixmaps.h" + +class PixmapLoader { + public: + class Pixmaps { + public: + // TODO(jansenin): maybe make this readonly + static QPixmap* kBackground; + static QPixmap* kMagicProjectileLevel1; + static QPixmap* kMagicProjectileLevel2; + static QPixmap* kMagicProjectileLevel3; + static QPixmap* kCannonProjectileLevel1; + static QPixmap* kCannonProjectileLevel2; + static QPixmap* kCannonProjectileLevel3; + static QPixmap* kTowerSlot; + static QPixmap* kMagicTowerLevel1; + static QPixmap* kMagicTowerLevel2; + static QPixmap* kMagicTowerLevel3; + static QPixmap* kCannonTowerLevel1; + static QPixmap* kCannonTowerLevel2; + static QPixmap* kCannonTowerLevel3; + static std::vector kLevelMaps; + static QPixmap* kMoneyIcon; + static QPixmap* kHealthStatus1; + static QPixmap* kHealthStatus2; + static QPixmap* kEmpty; + + static QPixmap* kBearTrap; + static QPixmap* kBearTrapAnimations; + static std::vector kBearTrapIdle; + static std::vector kBearTrapAttacking; + static std::vector kBearTrapBroken; + static std::vector kBearTrapRepairing; + + static std::vector kCoinIdle; + static QPixmap* kCoinAnimations; + + static QPixmap* kBomb0; + static QPixmap* kBomb1; + static QPixmap* kBomb2; + static QPixmap* kBomb3; + static QPixmap* kBomb4; + static QPixmap* kBomb5; + static std::vector kBombExplosion; + static std::vector kBombIdle; + static QPixmap* kBombIdleFrame; + + class FireTotem { + public: + static QPixmap* kAnimations; + static std::vector kIdle; + static std::vector kDisappear; + static std::vector kAppearing; + }; + + class Skeleton { + public: + static std::vector kWalk; + static std::vector kDeath; + }; + class Cobra { + public: + static QPixmap* kAnimations; + static std::vector kWalk; + static std::vector kDeath; + }; + class Hedgehog { + public: + static QPixmap* kAnimations; + static std::vector kWalk; + static std::vector kDeath; + }; + class Dwarf { + public: + static QPixmap* kAnimations; + static std::vector kWalk; + static std::vector kDeath; + }; + class Explosion { + public: + static QPixmap* kAnimations; + static std::vector kExplosion; + }; + class MagicProjectile { + public: + static QPixmap* kAnimationsLevel1; + static QPixmap* kAnimationsLevel2; + static QPixmap* kAnimationsLevel3; + static std::vector kDestroyingLevel1; + static std::vector kDestroyingLevel2; + static std::vector kDestroyingLevel3; + }; + }; + + static TexturedBoxPixmaps kMenuTexturedBoxPixmaps; + static TexturedBoxPixmaps kMenu2TexturedBoxPixmaps; + static TexturedBoxPixmaps kButtonTexturedBoxPixmaps; + static TexturedBoxPixmaps kDefaultTexturedBoxPixmaps; + + static void LoadPixmaps(); + + private: + static std::vector CreateHorizontalFramesVector( + QPixmap* source, + int frame_width, + int frame_height, + int frames_count, + int start_x, + int y); + + static void LoadFireTotemAnimations(); + static void LoadSkeletonAnimations(); + static void LoadCobraAnimations(); + static void LoadHedgehogAnimations(); + static void LoadDwarfAnimations(); + static void LoadExplosionAnimation(); + static void LoadBearTrapAnimations(); + static void LoadBombAnimations(); + static void LoadCoinAnimations(); + static void LoadMagicProjectileAnimations(); + + static void LoadUI(); + static void LoadDefaultTextureBox(); + static void LoadMenuTextureBox(); + static void LoadMenu2TextureBox(); + static void LoadButtonTextureBox(); +}; diff --git a/Utilities/Resources/textured_box_pixmaps.h b/Utilities/Resources/textured_box_pixmaps.h new file mode 100644 index 0000000..e679ce5 --- /dev/null +++ b/Utilities/Resources/textured_box_pixmaps.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +struct TexturedBoxPixmaps { + QPixmap* top_left_corner; + QPixmap* top_right_corner; + QPixmap* bottom_left_corner; + QPixmap* bottom_right_corner; + + QPixmap* left_side; + QPixmap* right_side; + QPixmap* top_side; + QPixmap* bottom_side; + + QPixmap* inside; +}; diff --git a/Utilities/animation.cpp b/Utilities/animation.cpp new file mode 100644 index 0000000..55b7a4c --- /dev/null +++ b/Utilities/animation.cpp @@ -0,0 +1,82 @@ +#include "animation.h" + +#include + +Animation::Animation(std::vector frames, Time time_between_frames) + : frames_(std::move(frames)), + time_between_frames_(time_between_frames), + time_to_next_frame_(time_between_frames), + frame_index_(0), + was_ended_during_previous_update(false) { + assert(!frames_.empty()); +} + +Animation::Animation(QPixmap* pixmap) + // doesn't matter what time between frames is, it just must be non-zero + : Animation(std::vector{pixmap}, 1_ms) {} + +void Animation::Tick(Time delta) { + time_to_next_frame_ -= delta; + if (time_to_next_frame_ < 0_ms) { + int frames_passed = + abs(time_to_next_frame_.ms() / time_between_frames_.ms()); + ++frames_passed; + time_to_next_frame_ += time_between_frames_ * frames_passed; + bool animation_ended = false; + if (frame_index_ + frames_passed >= frames_.size()) { + animation_ended = true; + } + SetIndex(frame_index_ + frames_passed); + was_ended_during_previous_update = animation_ended; + } +} + +const QPixmap* Animation::Frame() const { + return frames_.at(frame_index_); +} + +QPixmap* Animation::Frame() { + return frames_.at(frame_index_); +} + +void Animation::SetIndex(int index) { + was_ended_during_previous_update = false; + if (index >= frames_.size()) { + was_ended_during_previous_update = true; + } + frame_index_ = ((index % frames_.size()) + frames_.size()) % frames_.size(); +} + +void Animation::SetTimeBetweenFrames(Time time) { + time_between_frames_ = time; +} + +Time Animation::TimeBetweenFrames() const { + return time_between_frames_; +} + +Time Animation::TimeToNextFrame() const { + return time_to_next_frame_; +} + +int Animation::FrameIndex() const { + return frame_index_; +} + +void Animation::NextFrame() { + SetIndex(frame_index_ + 1); +} + +void Animation::Reset() { + time_to_next_frame_ = time_between_frames_; + SetIndex(0); + was_ended_during_previous_update = false; +} + +bool Animation::WasEndedDuringPreviousUpdate() const { + return was_ended_during_previous_update; +} + +int Animation::FrameCount() const { + return frames_.size(); +} diff --git a/Utilities/animation.h b/Utilities/animation.h new file mode 100644 index 0000000..512d320 --- /dev/null +++ b/Utilities/animation.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include + +#include "Utilities/time.h" +#include "GameObjects/Interface/tickable.h" + +class Animation : public Tickable { + public: + Animation(std::vector frames, Time time_between_frames); + explicit Animation(QPixmap* pixmap); + + void Tick(Time delta) override; + QPixmap* Frame(); + [[nodiscard]] const QPixmap* Frame() const; + void SetIndex(int index); + void SetTimeBetweenFrames(Time time); + void NextFrame(); + void Reset(); + + [[nodiscard]] bool WasEndedDuringPreviousUpdate() const; + [[nodiscard]] int FrameCount() const; + [[nodiscard]] Time TimeBetweenFrames() const; + [[nodiscard]] Time TimeToNextFrame() const; + [[nodiscard]] int FrameIndex() const; + + protected: + std::vector frames_; + Time time_between_frames_; + Time time_to_next_frame_; + int frame_index_; + bool was_ended_during_previous_update; +}; diff --git a/Utilities/damage.cpp b/Utilities/damage.cpp new file mode 100644 index 0000000..2826a1e --- /dev/null +++ b/Utilities/damage.cpp @@ -0,0 +1,9 @@ +#include "damage.h" + +Damage::Damage(int damage) { + damage_ = damage; +} + +int Damage::GetDamage() const { + return damage_; +} diff --git a/Utilities/damage.h b/Utilities/damage.h new file mode 100644 index 0000000..515b421 --- /dev/null +++ b/Utilities/damage.h @@ -0,0 +1,10 @@ +#pragma once + +class Damage { + public: + explicit Damage(int damage); + [[nodiscard]] int GetDamage() const; + + private: + int damage_ = 0; +}; diff --git a/Utilities/randomaizer.cpp b/Utilities/randomaizer.cpp new file mode 100644 index 0000000..37ea037 --- /dev/null +++ b/Utilities/randomaizer.cpp @@ -0,0 +1,13 @@ +#include "randomaizer.h" +#include +#include +#include + +std::random_device dev; +std::mt19937 rng(dev()); +std::uniform_int_distribution + dist(-100000, 100000); + +int Randomaizer::Random() { + return dist(rng); +} diff --git a/Utilities/randomaizer.h b/Utilities/randomaizer.h new file mode 100644 index 0000000..ec65477 --- /dev/null +++ b/Utilities/randomaizer.h @@ -0,0 +1,8 @@ +#pragma once + +extern int random_seed; + +class Randomaizer { + public: + static int Random(); +}; diff --git a/Utilities/route.cpp b/Utilities/route.cpp new file mode 100644 index 0000000..8077b66 --- /dev/null +++ b/Utilities/route.cpp @@ -0,0 +1,41 @@ +#include "route.h" +#include "Utilities/randomaizer.h" +#include + +bool Route::isEnd(Entity* entity) { + return entity_indexes_[entity] == points_.size() - 1; +} + +void Route::Move(Entity* entity, qreal distance) { + VectorF start(entity->pos()); + VectorF end(points_[entity_indexes_[entity] + 1]); + VectorF direction = end - start; + entity->MoveBy((direction.normalized() * distance)); + ChooseIndex(entity); +} + +void Route::ChooseIndex(Entity* entity) { + qreal square_distance = + pow((points_[entity_indexes_[entity] + 1].x() - entity->pos().x()), 2) + + pow((points_[entity_indexes_[entity] + 1].y() - entity->pos().y()), + 2); + if (square_distance <= kThreshold * kThreshold) { + ++entity_indexes_[entity]; + } +} + +void Route::AddEntity(Entity* entity) { + entity_indexes_[entity] = 0; +} + +VectorF Route::GetStart() const { + return points_[0]; +} + +void Route::RemoveEntity(Entity* entity) { + entity_indexes_.erase(entity); +} + +std::vector Route::GetPoints() const { + return points_; +} diff --git a/Utilities/route.h b/Utilities/route.h new file mode 100644 index 0000000..d0ecf46 --- /dev/null +++ b/Utilities/route.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include + +#include "GameObjects/Interface/entity.h" +#include "Utilities/vector_f.h" + +static constexpr qreal kThreshold = 5; + +class Route { + public: + explicit Route(const std::vector& points) : + points_(points) {} + void Move(Entity* entity, qreal distance); + void AddEntity(Entity* entity); + void RemoveEntity(Entity* entity); + bool isEnd(Entity* entity); + std::vector GetPoints() const; + [[nodiscard]] VectorF GetStart() const; + + private: + void ChooseIndex(Entity* entity); + std::vector points_; + std::map entity_indexes_{}; +}; diff --git a/Utilities/time.cpp b/Utilities/time.cpp new file mode 100644 index 0000000..30864e8 --- /dev/null +++ b/Utilities/time.cpp @@ -0,0 +1,79 @@ +#include "time.h" + +int Time::ms() const { + return ms_; +} + +qreal Time::seconds() const { + return ms_ / 1000.0; +} + +Time::Time(int ms) : ms_(ms) {} + +bool Time::operator<(const Time& rhs) const { + return ms_ < rhs.ms_; +} + +bool Time::operator>(const Time& rhs) const { + return rhs < *this; +} + +bool Time::operator<=(const Time& rhs) const { + return !(rhs < *this); +} + +bool Time::operator>=(const Time& rhs) const { + return !(*this < rhs); +} + +Time Time::operator+(const Time& rhs) const { + return Time(ms_ + rhs.ms_); +} + +Time Time::operator-(const Time& rhs) const { + return Time(ms_ - rhs.ms_); +} + +Time& Time::operator+=(const Time& rhs) { + ms_ += rhs.ms_; + return *this; +} + +Time& Time::operator-=(const Time& rhs) { + ms_ -= rhs.ms_; + return *this; +} + +Time& Time::operator-() { + ms_ = -ms_; + return *this; +} + +Time Time::operator*(int rhs) const { + return Time(ms() * rhs); +} + +Time& Time::operator*=(int rhs) { + ms_ *= rhs; + return *this; +} + +Time operator*(int lhs, const Time& rhs) { + return rhs * lhs; +} + +Time Time::operator*(double rhs) const { + return Time(ms_ * rhs); +} + +Time Time::operator*=(double rhs) const { + return Time(ms_ * rhs); +} + +Time operator*(double lhs, const Time& rhs) { + return Time(rhs.ms_ * lhs); +} + +Time operator "" _ms(unsigned long long int ms) { // NOLINT + return Time(ms); +} diff --git a/Utilities/time.h b/Utilities/time.h new file mode 100644 index 0000000..83ff0bb --- /dev/null +++ b/Utilities/time.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +class Time { + public: + explicit Time(int ms); + + [[nodiscard]] int ms() const; + [[nodiscard]] qreal seconds() const; + + bool operator<(const Time& rhs) const; + bool operator>(const Time& rhs) const; + bool operator<=(const Time& rhs) const; + bool operator>=(const Time& rhs) const; + + Time operator+(const Time& rhs) const; + // there is no check for negative time + Time operator-(const Time& rhs) const; + + Time operator*(int rhs) const; + Time operator*(double rhs) const; + + Time& operator+=(const Time& rhs); + Time& operator-=(const Time& rhs); + + Time& operator*=(int rhs); + Time operator*=(double rhs) const; + + friend Time operator*(int lhs, const Time& rhs); + friend Time operator*(double lhs, const Time& rhs); + + Time& operator-(); + + private: + int ms_; +}; + +Time operator*(int lhs, const Time& rhs); +Time operator*(double lhs, const Time& rhs); + +// cpplint говорит, что unsigned long long int(Тип из си) нужно поменять на +// int16/int32/int64...(тип из C++), но в документации к пользовательским +// литералам сказано, что в них могут использоваться только определённые +// типы(и записи unsigned long long int в C++ я не нашёл, а это единственный +// целочисленный тип, который можно использовать для литерала) +Time operator "" _ms(unsigned long long int ms); // NOLINT diff --git a/Utilities/timer.cpp b/Utilities/timer.cpp new file mode 100644 index 0000000..83e96f3 --- /dev/null +++ b/Utilities/timer.cpp @@ -0,0 +1,23 @@ +#include "timer.h" + +Timer::Timer(const Time& remaining_time) : remaining_time_(remaining_time) {} + +void Timer::Tick(const Time& delta_time) { + remaining_time_ -= delta_time; +} + +Time Timer::RemainingTime() { + return remaining_time_; +} + +bool Timer::IsExpired() { + return remaining_time_ <= Time(0); +} + +void Timer::Start(const Time& remaining_time) { + remaining_time_ = remaining_time; +} + +Time Timer::HowMuchExpired() { + return -remaining_time_; +} diff --git a/Utilities/timer.h b/Utilities/timer.h new file mode 100644 index 0000000..7e6393a --- /dev/null +++ b/Utilities/timer.h @@ -0,0 +1,17 @@ +#pragma once + +#include "time.h" + +class Timer { + public: + explicit Timer(const Time& remaining_time); + + void Tick(const Time& delta_time); + Time RemainingTime(); + bool IsExpired(); + void Start(const Time& remaining_time); + Time HowMuchExpired(); + + private: + Time remaining_time_; +}; diff --git a/Utilities/utility.cpp b/Utilities/utility.cpp new file mode 100644 index 0000000..ac8a87c --- /dev/null +++ b/Utilities/utility.cpp @@ -0,0 +1,24 @@ +#include + +#include "GameObjects/Entities/Mobs/skeleton.h" +#include "GameObjects/Entities/Mobs/cobra.h" +#include "GameObjects/Entities/Mobs/hedgehog.h" +#include "GameObjects/Entities/Mobs/dwarf.h" +#include "constants.h" +#include "utility.h" + +Mob* CreateMobFromType(QString type) { + if (type == Entities::kSkeletonId) { + return new Skeleton(); + } + if (type == Entities::kCobraId) { + return new Cobra(); + } + if (type == Entities::kHedgehogId) { + return new Hedgehog(); + } + if (type == Entities::kDwarfId) { + return new Dwarf(); + } + throw std::invalid_argument("There is no such id: " + type.toStdString()); + } diff --git a/Utilities/utility.h b/Utilities/utility.h new file mode 100644 index 0000000..24f1d1d --- /dev/null +++ b/Utilities/utility.h @@ -0,0 +1,3 @@ +#pragma once + +Mob* CreateMobFromType(QString type); diff --git a/Utilities/vector_f.cpp b/Utilities/vector_f.cpp new file mode 100644 index 0000000..edea52f --- /dev/null +++ b/Utilities/vector_f.cpp @@ -0,0 +1,35 @@ +#include "vector_f.h" +#include + +VectorF::VectorF() {} +VectorF::VectorF(const QPoint& p) : QPointF(p) {} +VectorF::VectorF(qreal x, qreal y) : QPointF(x, y) {} +VectorF::VectorF(const QPointF& p) : QPointF(p) {} + +qreal VectorF::lengthSquared() { + return x() * x() + y() * y(); +} + +qreal VectorF::length() { + return sqrt(lengthSquared()); +} + +void VectorF::normalize() { + *this /= length(); +} + +VectorF VectorF::normalized() { + return *this / length(); +} + +void VectorF::rotate(qreal radians) { + *this = this->rotated(radians); +} + +VectorF VectorF::rotated(qreal radians) { + qreal s = sin(radians); + qreal c = cos(radians); + return VectorF( + x() * c - y() * s, + x() * s + y() * c); +} diff --git a/Utilities/vector_f.h b/Utilities/vector_f.h new file mode 100644 index 0000000..f5edb53 --- /dev/null +++ b/Utilities/vector_f.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +class VectorF : public QPointF { + public: + VectorF(); + explicit VectorF(const QPoint& p); + VectorF(const QPointF& p); // NOLINT + VectorF(qreal x, qreal y); + + qreal lengthSquared(); + qreal length(); + + void normalize(); + VectorF normalized(); + + void rotate(qreal radians); + VectorF rotated(qreal radians); +}; diff --git a/Utilities/wave.cpp b/Utilities/wave.cpp new file mode 100644 index 0000000..c8eeb7a --- /dev/null +++ b/Utilities/wave.cpp @@ -0,0 +1,33 @@ +#include "wave.h" + +#include "Controller/controller.h" + +void Wave::RemoveMobFromWave(Mob* mob) { + mobs_time_to_spawn_.erase(mob); +} + +void Wave::Tick(Time delta) { + if (IsStarted()) { + for (auto i = mobs_time_to_spawn_.begin(); + i != mobs_time_to_spawn_.end();) { + i->second -= delta; + if (i->second.ms() <= 0) { + Controller::Instance()->GetScene()->addItem(i->first); + Mob* mob_to_remove = i->first; + i++; + RemoveMobFromWave(mob_to_remove); + } else { + i++; + } + } + } else { + time_to_start_ -= delta; + } +} + +bool Wave::IsStarted() const { + return time_to_start_.ms() <= 0; +} +bool Wave::IsEnded() const { + return mobs_time_to_spawn_.empty(); +} diff --git a/Utilities/wave.h b/Utilities/wave.h new file mode 100644 index 0000000..f40a180 --- /dev/null +++ b/Utilities/wave.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "GameObjects/Interface/tickable.h" +#include "GameObjects/Entities/Mobs/Basis/mob.h" + +class Wave { + public: + explicit Wave(Time time_to_start, std::map&& mobs) + : time_to_start_(time_to_start), mobs_time_to_spawn_(std::move(mobs)) {} + + void Tick(Time delta); + // TODO(jansenin): we need to use it somewhere + void RemoveMobFromWave(Mob*); + [[nodiscard]] bool IsStarted() const; + [[nodiscard]] bool IsEnded() const; + + private: + Time time_to_start_; + std::map mobs_time_to_spawn_; +}; + diff --git a/constants.cpp b/constants.cpp new file mode 100644 index 0000000..2dbae68 --- /dev/null +++ b/constants.cpp @@ -0,0 +1,121 @@ +#include +#include "constants.h" + +const bool kDebugMode = false; +const int kFPS = 60; +const int kStartBalance = 10000; + +namespace Scene { +const qreal kWidth = 1920; +const qreal kHeight = 1080; +const QRectF kRect{ + -kWidth / 2, + -kHeight / 2, + kWidth, + kHeight}; +const qreal kMapTextureZValue = -10000; +} + +namespace UI { +const qreal kDefaultZValue = 10000; +const QColor kButtonDefaultTextColor = Qt::white; +} + +namespace LevelData { +extern const int kLevelsCount = 1; +} + +namespace Explosions { +const qreal kDefaultRadius = 70.0; +const Damage kDefaultDamage = Damage(50); +const Time kTimeBetweenFrames = 50_ms; +const qreal kZValue = 2000; +} + +namespace Entities { +const int kCircleAttackAreaApproximationPointsCount = 10; +const Time kGrowingSpeed = Time(500); +const QString kSkeletonId = "Skeleton"; +const QString kCobraId = "Cobra"; +const QString kHedgehogId = "Hedgehog"; +const QString kDwarfId = "Dwarf"; +const int kCoinAppearChance = 3; + +namespace MagicTower { +const qreal kAttackRangeLevel1 = 300; +const Time kAttackCooldownLevel1 = Time(4000); +const qreal kAttackRangeLevel2 = 400; +const Time kAttackCooldownLevel2 = Time(3000); +const qreal kAttackRangeLevel3 = 600; +const Time kAttackCooldownLevel3 = Time(2000); +const int kMaxLevel = 3; +const int kPrice = 110; +} + +namespace CannonTower { +const qreal kAttackRangeLevel1 = 200; +const Time kAttackCooldownLevel1 = Time(1250); +const qreal kAttackRangeLevel2 = 300; +const Time kAttackCooldownLevel2 = Time(1000); +const qreal kAttackRangeLevel3 = 500; +const Time kAttackCooldownLevel3 = Time(1000); +const int kMaxLevel = 3; +const int kPrice = 70; +} + +namespace Skeleton { +const Time kTimeBetweenFrames = 50_ms; +const qreal kSpeed = 50; +const int kHealth = 2000; +const int kDamageToBase = 1; +} + +namespace Cobra { +const Time kTimeBetweenFrames = 50_ms; +const qreal kSpeed = 70; +const int kHealth = 160; +const int kDamageToBase = 1; +} + +namespace Hedgehog { +const Time kTimeBetweenFrames = 50_ms; +const qreal kSpeed = 110; +const int kHealth = 80; +const int kDamageToBase = 1; +} + +namespace Dwarf { +const Time kTimeBetweenFrames = 50_ms; +const qreal kSpeed = 80; +const int kHealth = 10000; +const int kDamageToBase = 1; +} + +namespace MagicProjectile { +const Damage kDamageLevel1 = Damage(60); +const Damage kDamageLevel2 = Damage(100); +const Damage kDamageLevel3 = Damage(180); +const qreal kSpeed = 50; +const qreal kAcceleration = 1500; +const qreal kMaxSpeed = 300; +const qreal kEnemyFindDistance = 300; +const Time kTimeBetweenFrames = 50_ms; +} + +namespace CannonProjectile { +const Damage kDamageLevel1 = Damage(10); +const Damage kDamageLevel2 = Damage(20); +const Damage kDamageLevel3 = Damage(40); +const qreal kSpeed = 500; +} +} // namespace Entities + +namespace Costs { +const int kCoinCost = 50; +const int kCannonTowerCost = 200; +const int kMagicTowerCost = 300; +const int kTowerLevel2Upgrade = 100; +const int kTowerLevel3Upgrade = 150; +const int kBearTrapRepairingCost = 50; +const int kBombExplosionCost = 100; +} diff --git a/constants.h b/constants.h new file mode 100644 index 0000000..4dfde81 --- /dev/null +++ b/constants.h @@ -0,0 +1,122 @@ +#pragma once + +#include +#include + +#include "Utilities/time.h" +#include + +extern const bool kDebugMode; +extern const int kFPS; +extern const int kStartBalance; + +namespace Costs { +extern const int kCoinCost; +extern const int kCannonTowerCost; +extern const int kMagicTowerCost; +extern const int kTowerLevel2Upgrade; +extern const int kTowerLevel3Upgrade; +extern const int kBearTrapRepairingCost; +extern const int kBombExplosionCost; +} + +namespace Scene { +extern const qreal kWidth; +extern const qreal kHeight; +extern const QRectF kRect; +extern const qreal kMapTextureZValue; +} + +namespace UI { +extern const qreal kDefaultZValue; +extern const QColor kButtonDefaultTextColor; +} + +namespace LevelData { +extern const int kLevelsCount; +} + +namespace Explosions { +extern const qreal kDefaultRadius; +extern const Damage kDefaultDamage; +extern const Time kTimeBetweenFrames; +extern const qreal kZValue; +} + +namespace Entities { +extern const int kCircleAttackAreaApproximationPointsCount; +extern const Time kGrowingSpeed; +extern const QString kSkeletonId; +extern const QString kCobraId; +extern const QString kHedgehogId; +extern const QString kDwarfId; +extern const int kCoinAppearChance; + +namespace MagicTower { +extern const qreal kAttackRangeLevel1; +extern const Time kAttackCooldownLevel1; +extern const qreal kAttackRangeLevel2; +extern const Time kAttackCooldownLevel2; +extern const qreal kAttackRangeLevel3; +extern const Time kAttackCooldownLevel3; +extern const int kMaxLevel; +extern const int kPrice; +} + +namespace CannonTower { +extern const qreal kAttackRangeLevel1; +extern const Time kAttackCooldownLevel1; +extern const qreal kAttackRangeLevel2; +extern const Time kAttackCooldownLevel2; +extern const qreal kAttackRangeLevel3; +extern const Time kAttackCooldownLevel3; +extern const int kMaxLevel; +extern const int kPrice; +} + +namespace Skeleton { +extern const Time kTimeBetweenFrames; +extern const qreal kSpeed; +extern const int kHealth; +extern const int kDamageToBase; +} + +namespace Cobra { +extern const Time kTimeBetweenFrames; +extern const qreal kSpeed; +extern const int kHealth; +extern const int kDamageToBase; +} + +namespace Hedgehog { +extern const Time kTimeBetweenFrames; +extern const qreal kSpeed; +extern const int kHealth; +extern const int kDamageToBase; +} + +namespace Dwarf { +extern const Time kTimeBetweenFrames; +extern const qreal kSpeed; +extern const int kHealth; +extern const int kDamageToBase; +} + +namespace MagicProjectile { +extern const Damage kDamageLevel1; +extern const Damage kDamageLevel2; +extern const Damage kDamageLevel3; +extern const qreal kSpeed; +extern const qreal kAcceleration; +extern const qreal kMaxSpeed; +extern const qreal kEnemyFindDistance; +extern const Time kTimeBetweenFrames; +} + +namespace CannonProjectile { +extern const Damage kDamageLevel1; +extern const Damage kDamageLevel2; +extern const Damage kDamageLevel3; +extern const qreal kSpeed; +} +} // namespace Entities diff --git a/game_scene.cpp b/game_scene.cpp new file mode 100644 index 0000000..c34f8ec --- /dev/null +++ b/game_scene.cpp @@ -0,0 +1,98 @@ +#include "game_scene.h" + +#include "GameObjects/Interface/graphics_object.h" +#include "game_view.h" +#include "GameObjects/Entities/Mobs/Basis/mob.h" +#include "GameObjects/Entities/Towers/tower.h" +#include "GameObjects/Entities/Towers/TowerSlots/tower_slot.h" +#include "GameObjects/Entities/Projectiles/projectile.h" + +GameScene::GameScene(const QRectF& scene_rect, QObject* parent) + : QGraphicsScene(scene_rect, parent) {} + +GameView* GameScene::view() { + auto result = dynamic_cast(QGraphicsScene::views().at(0)); + assert(result != nullptr); + return result; +} + +std::vector GameScene::Mobs() const { + std::vector result; + for (auto item : items()) { + if (auto mob = dynamic_cast(item)) { + result.push_back(mob); + } + } + return result; +} + +std::vector GameScene::Towers() const { + std::vector result; + for (auto item : items()) { + if (auto tower = dynamic_cast(item)) { + result.push_back(tower); + } + } + return result; +} + +std::vector GameScene::TowerSlots() const { + std::vector result; + for (auto item : items()) { + if (auto tower_slot = dynamic_cast(item)) { + result.push_back(tower_slot); + } + } + return result; +} + +std::vector GameScene::Projectiles() const { + std::vector result; + for (auto item : items()) { + if (auto projectile = dynamic_cast(item)) { + result.push_back(projectile); + } + } + return result; +} + +void GameScene::IncCoinsCount() { + ++coins_count_; +} + +void GameScene::DecCoinsCount() { + if (coins_count_ > 0) { + --coins_count_; + } +} + +void GameScene::IncCannonTowersCount() { + ++cannon_tower_count_; +} + +void GameScene::DecCannonTowersCount() { + if (cannon_tower_count_ > 0) { + --cannon_tower_count_; + } +} + +int GameScene::GetCoinsCount() { + return coins_count_; +} + +int GameScene::GetCannonTowersCount() { + return cannon_tower_count_; +} + +void GameScene::IncMagicTowersCount() { + ++magic_tower_count_; +} + +void GameScene::DecMagicTowersCount() { + --magic_tower_count_; +} + +int GameScene::GetMagicTowersCount() { + return magic_tower_count_; +} + diff --git a/game_scene.h b/game_scene.h new file mode 100644 index 0000000..8cd3c4e --- /dev/null +++ b/game_scene.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include +#include + +class GraphicsObject; +class GameView; +class Mob; +class Tower; +class TowerSlot; +class Projectile; +class Entity; + +class GameScene : public QGraphicsScene { + public: + explicit GameScene(const QRectF& scene_rect, QObject* parent = nullptr); + + GameView* view(); + + void IncCoinsCount(); + void DecCoinsCount(); + int GetCoinsCount(); + + void IncCannonTowersCount(); + void DecCannonTowersCount(); + int GetCannonTowersCount(); + + void IncMagicTowersCount(); + void DecMagicTowersCount(); + int GetMagicTowersCount(); + + + + + [[nodiscard]] std::vector Mobs() const; + [[nodiscard]] std::vector Towers() const; + [[nodiscard]] std::vector TowerSlots() const; + [[nodiscard]] std::vector Projectiles() const; + + private: + int coins_count_ = 0; + int cannon_tower_count_ = 0; + int magic_tower_count_ = 0; +}; diff --git a/game_view.cpp b/game_view.cpp new file mode 100644 index 0000000..59d7a58 --- /dev/null +++ b/game_view.cpp @@ -0,0 +1,26 @@ +#include "game_view.h" + +GameView::GameView(QGraphicsScene* scene, QWidget* parent) + : QGraphicsView(scene, parent) { + scale(1/devicePixelRatio(), 1/devicePixelRatio()); + setInteractive(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + setRenderHint(QPainter::RenderHint::Antialiasing); + setOptimizationFlag( + QGraphicsView::OptimizationFlag::DontSavePainterState); + setViewportUpdateMode( + QGraphicsView::ViewportUpdateMode::FullViewportUpdate); + + setViewportMargins(0, 0, 0, 0); + setContentsMargins(0, 0, 0, 0); + setFrameStyle(QFrame::NoFrame); + + centerOn(0, 0); +} + +GameScene* GameView::scene() { + auto result = dynamic_cast(QGraphicsView::scene()); + assert(result != nullptr); + return result; +} diff --git a/game_view.h b/game_view.h new file mode 100644 index 0000000..1cdcceb --- /dev/null +++ b/game_view.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include "game_scene.h" + +class GameView : public QGraphicsView { + public: + explicit GameView(QGraphicsScene* scene, QWidget* parent = nullptr); + + GameScene* scene(); +}; diff --git a/level.cpp b/level.cpp new file mode 100644 index 0000000..b1899c0 --- /dev/null +++ b/level.cpp @@ -0,0 +1,214 @@ +#include "level.h" + +#include +#include +#include + +#include +#include +#include + +#include "GameObjects/Entities/Towers/TowerSlots/tower_slot.h" +#include "Utilities/utility.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +Level::Level(int level_number) : level_number_(level_number) { + QFile file(":Levels/Level" + + QString::fromStdString(std::to_string(level_number)) + "/level.json"); + + if (!file.open(QIODevice::ReadOnly)) { + throw std::invalid_argument("There is no such level"); + } + + QByteArray level_data = file.readAll(); + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(level_data, &error); + if (error.error != QJsonParseError::NoError) { + throw std::invalid_argument( + "Level json parse error: " + error.errorString().toStdString()); + } + + QJsonObject root = document.object(); + startMoney_ = root.value("startMoney").toInt(); + + QJsonArray tower_slot_positions = + root.value("towerSlotPositions").toArray(); + tower_slots_.reserve(tower_slot_positions.size()); + for (auto tower_slot_position : tower_slot_positions) { + int tower_slot_x; + int tower_slot_y; + QJsonObject tower_slot_pos_object = tower_slot_position.toObject(); + tower_slot_x = tower_slot_pos_object.value("x").toInt(); + tower_slot_y = tower_slot_pos_object.value("y").toInt(); + + tower_slots_.push_back( + new TowerSlot(VectorF(tower_slot_x, tower_slot_y))); + } + + QJsonArray routes = root.value("routes").toArray(); + routes_.reserve(routes.size()); + for (auto route : routes) { + QJsonArray points = route.toObject().value("points").toArray(); + std::vector points_for_route; + points_for_route.reserve(points.size()); + for (auto point : points) { + QJsonObject point_object = point.toObject(); + int x = point_object.value("x").toInt(); + int y = point_object.value("y").toInt(); + points_for_route.emplace_back(x, y); + } + routes_.push_back(new Route(points_for_route)); + } + + QJsonArray waves = root.value("waves").toArray(); + waves_.reserve(waves_.size()); + + Time previous_wave_end_time(0); + for (auto json_wave : waves) { + QJsonObject wave_object = json_wave.toObject(); + Time current_wave_start_time = previous_wave_end_time + + Time(wave_object.value("startTimeRelativeToPrevWave").toInt()); + previous_wave_end_time = current_wave_start_time; + QJsonArray spawn_entries = wave_object.value("spawnEntries").toArray(); + Time wave_duration(0); + std::map mobs; + for (auto json_spawn_entry : spawn_entries) { + QJsonObject object = json_spawn_entry.toObject(); + SpawnEntry spawn_entry(&object); + wave_duration = Time(std::max( + spawn_entry.GetEntryEndTime().ms(), + wave_duration.ms())); + spawn_entry.AddMobsToWave(&mobs, routes_); + } + previous_wave_end_time += wave_duration; + Wave* wave = new Wave(current_wave_start_time, std::move(mobs)); + waves_.push_back(wave); + } + QJsonArray timers_for_grow_array = root.value("GrowTimes").toArray(); + timers_for_grow_.reserve(timers_for_grow_array.size()); + for (auto i : timers_for_grow_array) { + timers_for_grow_.emplace_back(i.toInt()); + } + QJsonArray bear_traps = root.value("BearTraps").toArray(); + bear_traps_.reserve(bear_traps.size()); + for (auto trap : bear_traps) { + QJsonArray points = trap.toObject().value("points").toArray(); + VectorF point_for_trap; + for (auto point : points) { + QJsonObject point_object = point.toObject(); + int x = point_object.value("x").toInt(); + int y = point_object.value("y").toInt(); + point_for_trap.setX(x); + point_for_trap.setY(y); + } + bear_traps_.push_back(new BearTrap(point_for_trap)); + } + QJsonArray bombs = root.value("Bombs").toArray(); + bombs_.reserve(bear_traps.size()); + for (auto bomb : bombs) { + QJsonArray points = bomb.toObject().value("points").toArray(); + VectorF point_for_bomb; + for (auto point : points) { + QJsonObject point_object = point.toObject(); + int x = point_object.value("x").toInt(); + int y = point_object.value("y").toInt(); + point_for_bomb.setX(x); + point_for_bomb.setY(y); + } + bombs_.push_back(new Bomb(point_for_bomb)); + } +} + +void Level::AddObjectsToScene(GameScene* scene) { + QPixmap* map_pixmap = + PixmapLoader::Pixmaps::kLevelMaps.at(level_number_ - 1); + auto map_pixmap_item = scene->addPixmap(*map_pixmap); + map_pixmap_item->setZValue(Scene::kMapTextureZValue); + + qreal pixmap_width = map_pixmap->width(); + qreal pixmap_height = map_pixmap->height(); + + QTransform transform; + QRectF pixmap_rect( + -pixmap_width / 2.0, -pixmap_height / 2.0, + pixmap_width, pixmap_height); + transform.scale( + Scene::kWidth / pixmap_width, + Scene::kHeight / pixmap_height); + pixmap_rect = transform.mapRect(pixmap_rect); + map_pixmap_item->setPos(pixmap_rect.topLeft()); + map_pixmap_item->setTransform(transform); + + for (auto tower_slot : tower_slots_) { + scene->addItem(tower_slot); + } + for (auto bear_trap : bear_traps_) { + scene->addItem(bear_trap); + } + for (auto bomb : bombs_) { + scene->addItem(bomb); + } +} + +void Level::Tick(Time delta) { + for (auto wave : waves_) { + wave->Tick(delta); + } + if (!timers_for_grow_.empty()) { + timers_for_grow_[0] -= delta; + } +} + +const std::vector& Level::GetRoutes() const { + return routes_; +} + +const std::vector& Level::GetWaves() { + return waves_; +} + +int Level::GetLevelNumber() const { + return level_number_; +} + +int Level::GetStartMoney() const { + return startMoney_; +} + +bool Level::IsTimeForGrow() { + if (!timers_for_grow_.empty() && timers_for_grow_[0].ms() <= 0) { + timers_for_grow_.erase(timers_for_grow_.begin()); + return true; + } + return false; +} + +Level::SpawnEntry::SpawnEntry(QJsonObject* spawn_root_object) + : start_time_(Time(spawn_root_object->value("startTime").toInt())), + mob_type_(spawn_root_object->value("mobType").toString()), + count_(spawn_root_object->value("count").toInt()), + entry_duration_(Time(spawn_root_object->value("entryDuration").toInt())), + route_index_(spawn_root_object->value("routeIndex").toInt()) {} + +void Level::SpawnEntry::AddMobsToWave( + std::map* mobs, + const std::vector& routes) const { + if (count_ == 1) { + Mob* mob = CreateMobFromType(mob_type_); + mob->SetRoute(routes.at(route_index_)); + mobs->insert(std::make_pair(mob, start_time_)); + } + for (int i = 0; i < count_; ++i) { + Time spawn_time = start_time_; + spawn_time += entry_duration_ * ((1.0 * i) / (count_ - 1)); + + Mob* mob = CreateMobFromType(mob_type_); + mob->SetRoute(routes.at(route_index_)); + mobs->insert(std::make_pair(mob, spawn_time)); + } +} + +Time Level::SpawnEntry::GetEntryEndTime() const { + return start_time_ + entry_duration_; +} diff --git a/level.h b/level.h new file mode 100644 index 0000000..eb7c92e --- /dev/null +++ b/level.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +#include + +#include "Utilities/vector_f.h" +#include "GameObjects/Entities/Mobs/Basis/mob.h" +#include "GameObjects/Entities/Towers/TowerSlots/tower_slot.h" +#include "Utilities/route.h" +#include "Utilities/wave.h" +#include "GameObjects/Entities/Traps/bear_trap.h" +#include "GameObjects/Entities/Traps/bomb.h" + +class Level { + public: + explicit Level(int level_number); + + void AddObjectsToScene(GameScene* scene); + void Tick(Time delta); + + [[nodiscard]] const std::vector& GetRoutes() const; + [[nodiscard]] const std::vector& GetWaves(); + [[nodiscard]] int GetLevelNumber() const; + [[nodiscard]] int GetStartMoney() const; + bool IsTimeForGrow(); + + private: + class SpawnEntry { + public: + explicit SpawnEntry(QJsonObject* spawn_root_object); + + [[nodiscard]] Time GetEntryEndTime() const; + void AddMobsToWave( + std::map* mobs, + const std::vector& routes) const; + + private: + Time start_time_; + QString mob_type_; + int count_; + Time entry_duration_; + int route_index_; + }; + + std::vector bear_traps_{}; + std::vector bombs_{}; + std::vector tower_slots_{}; + std::vector routes_{}; + std::vector waves_{}; + std::vector