#include "balloon_manager.h" #include // Para accumulate #include // Para find_if, clamp, min, remove_if #include "balloon.h" // Para Balloon, BALLOON_SCORE, BALLOON_VE... #include "balloon_formations.h" // Para BalloonFormations, BalloonFormatio... #include "resource.h" #include "screen.h" #include "explosions.h" // Para Explosions #include "jail_audio.h" // Constructor BalloonManager::BalloonManager() : explosions_(std::make_unique()), balloon_formations_(std::make_unique()) { // Texturas - Globos { balloon_textures_.emplace_back(Resource::get()->getTexture("balloon1.png")); balloon_textures_.emplace_back(Resource::get()->getTexture("balloon2.png")); balloon_textures_.emplace_back(Resource::get()->getTexture("balloon3.png")); balloon_textures_.emplace_back(Resource::get()->getTexture("balloon4.png")); balloon_textures_.emplace_back(Resource::get()->getTexture("powerball.png")); } // Texturas - Explosiones { explosions_textures_.emplace_back(Resource::get()->getTexture("explosion1.png")); explosions_textures_.emplace_back(Resource::get()->getTexture("explosion2.png")); explosions_textures_.emplace_back(Resource::get()->getTexture("explosion3.png")); explosions_textures_.emplace_back(Resource::get()->getTexture("explosion4.png")); } // Animaciones -- Globos { balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon1.ani")); balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon2.ani")); balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon3.ani")); balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon4.ani")); balloon_animations_.emplace_back(Resource::get()->getAnimation("powerball.ani")); } // Animaciones -- Explosiones { explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion1.ani")); explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion2.ani")); explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion3.ani")); explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion4.ani")); } explosions_->addTexture(1, explosions_textures_[0], explosions_animations_[0]); explosions_->addTexture(2, explosions_textures_[1], explosions_animations_[1]); explosions_->addTexture(3, explosions_textures_[2], explosions_animations_[2]); explosions_->addTexture(4, explosions_textures_[3], explosions_animations_[3]); } // Actualiza void BalloonManager::update() { for (auto balloon : balloons_) { balloon->update(); } } // Renderiza los globos void BalloonManager::renderBalloons() { for (auto &balloon : balloons_) { balloon->render(); } } // Crea una formación de enemigos void BalloonManager::deployBalloonFormation() { // Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última if (balloon_deploy_counter_ == 0) { // En este punto se decide entre crear una powerball o una formación enemiga if ((rand() % 100 < 15) && (canPowerBallBeCreated())) { // Crea una powerball createPowerBall(); // Da un poco de margen para que se creen mas enemigos balloon_deploy_counter_ = 300; } else { // Decrementa el contador de despliegues enemigos de la PowerBall power_ball_counter_ = (power_ball_counter_ > 0) ? (power_ball_counter_ - 1) : 0; // Elige una formación enemiga la azar auto formation = rand() % 10; // Evita repetir la ultima formación enemiga desplegada if (formation == last_balloon_deploy_) { ++formation %= 10; } last_balloon_deploy_ = formation; const auto set = balloon_formations_->getStage(current_stage_).balloon_pool.set[formation]; const auto numEnemies = set.number_of_balloons; for (int i = 0; i < numEnemies; ++i) { auto p = set.init[i]; createBalloon(p.x, p.y, p.type, p.size, p.vel_x, balloon_speed_, p.creation_counter); } balloon_deploy_counter_ = 300; } } } // Gestiona el nivel de amenaza void BalloonManager::updateMenace() { const auto stage = balloon_formations_->getStage(current_stage_); const float percent = current_power_ / stage.power_to_complete; const int difference = stage.max_menace - stage.min_menace; // Aumenta el nivel de amenaza en función de la puntuación menace_threshold_ = stage.min_menace + (difference * percent); // Si el nivel de amenza es inferior al umbral if (menace_current_ < menace_threshold_) { // Crea una formación de enemigos deployBalloonFormation(); // Recalcula el nivel de amenaza con el nuevo globo evaluateAndSetMenace(); } } // Vacia del vector de globos los globos que ya no sirven void BalloonManager::freeBalloons() { auto it = std::remove_if(balloons_.begin(), balloons_.end(), [](const auto &balloon) { return !balloon->isEnabled(); }); balloons_.erase(it, balloons_.end()); } // Calcula y establece el valor de amenaza en funcion de los globos activos void BalloonManager::evaluateAndSetMenace() { menace_current_ = std::accumulate( balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon) { return sum + (balloon->isEnabled() ? balloon->getMenace() : 0); }); } // Actualiza la variable enemyDeployCounter void BalloonManager::updateBalloonDeployCounter() { if (balloon_deploy_counter_ > 0) --balloon_deploy_counter_; } // Indica si se puede crear una powerball bool BalloonManager::canPowerBallBeCreated() { return (!power_ball_enabled_) && (calculateScreenPower() > POWERBALL_SCREENPOWER_MINIMUM) && (power_ball_counter_ == 0); } // Calcula el poder actual de los globos en pantalla int BalloonManager::calculateScreenPower() { return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon) { return sum + (balloon->isEnabled() ? balloon->getPower() : 0); }); } // Crea un globo nuevo en el vector de globos std::shared_ptr BalloonManager::createBalloon(float x, int y, BalloonType type, BalloonSize size, float velx, float speed, int creation_timer) { const int index = static_cast(size); balloons_.emplace_back(std::make_shared(x, y, type, size, velx, speed, creation_timer, balloon_textures_.at(index), balloon_animations_.at(index))); return balloons_.back(); } // Crea un globo a partir de otro globo void BalloonManager::createChildBalloon(const std::shared_ptr &balloon, const std::string &direction) { const float vx = direction == "LEFT" ? BALLOON_VELX_NEGATIVE : BALLOON_VELX_POSITIVE; const auto lower_size = static_cast(static_cast(balloon->getSize()) - 1); auto b = createBalloon(0, balloon->getPosY(), balloon->getType(), lower_size, vx, balloon_speed_, 0); b->alignTo(balloon->getPosX() + (balloon->getWidth() / 2)); b->setVelY(b->getType() == BalloonType::BALLOON ? -2.50f : BALLOON_VELX_NEGATIVE * 2.0f); if (balloon->isStopped()) b->stop(); if (balloon->isUsingReversedColor()) b->useReverseColor(); } // Crea una PowerBall void BalloonManager::createPowerBall() { constexpr auto values = 6; constexpr auto pos_y = -BLOCK; constexpr int creation_time = 300; const auto left = param.game.play_area.rect.x; const auto center = param.game.play_area.center_x - (BALLOON_SIZE[3] / 2); const auto right = param.game.play_area.rect.w - BALLOON_SIZE[3]; const auto luck = rand() % values; const int x[values] = {left, left, center, center, right, right}; const float vx[values] = {BALLOON_VELX_POSITIVE, BALLOON_VELX_POSITIVE, BALLOON_VELX_POSITIVE, BALLOON_VELX_NEGATIVE, BALLOON_VELX_NEGATIVE, BALLOON_VELX_NEGATIVE}; balloons_.emplace_back(std::make_unique(x[luck], pos_y, BalloonType::POWERBALL, BalloonSize::SIZE4, vx[luck], balloon_speed_, creation_time, balloon_textures_[4], balloon_animations_[4])); power_ball_enabled_ = true; power_ball_counter_ = POWERBALL_COUNTER; } // Establece la velocidad de los globos void BalloonManager::setBalloonSpeed(float speed) { for (auto &balloon : balloons_) balloon->setSpeed(speed); } // Actualiza la velocidad de los globos en funcion del poder acumulado de la fase void BalloonManager::checkAndUpdateBalloonSpeed() { if (difficulty_ != GameDifficulty::NORMAL) return; const float percent = static_cast(current_power_) / balloon_formations_->getStage(current_stage_).power_to_complete; const float thresholds[] = {0.2f, 0.4f, 0.6f, 0.8f}; for (size_t i = 0; i < std::size(thresholds); ++i) if (balloon_speed_ == BALLOON_SPEED[i] && percent > thresholds[i]) { balloon_speed_ = BALLOON_SPEED[i + 1]; setBalloonSpeed(balloon_speed_); break; // Salir del bucle una vez actualizada la velocidad y aplicada } } // Explosiona un globo. Lo destruye y crea otros dos si es el caso void BalloonManager::popBalloon(std::shared_ptr balloon) { increaseStageCurrentPower(1); balloons_popped_++; if (balloon->getType() == BalloonType::POWERBALL) { destroyAllBalloons(); power_ball_enabled_ = false; balloon_deploy_counter_ = 20; } else { if (balloon->getSize() != BalloonSize::SIZE1) { createChildBalloon(balloon, "LEFT"); createChildBalloon(balloon, "RIGHT"); } // Agrega la explosión y elimina el globo explosions_->add(balloon->getPosX(), balloon->getPosY(), static_cast(balloon->getSize())); balloon->pop(); } // Recalcula el nivel de amenaza evaluateAndSetMenace(); } // Explosiona un globo. Lo destruye = no crea otros globos void BalloonManager::destroyBalloon(std::shared_ptr &balloon) { int score = 0; // Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos switch (balloon->getSize()) { case BalloonSize::SIZE4: score = BALLOON_SCORE[3] + (2 * BALLOON_SCORE[2]) + (4 * BALLOON_SCORE[1]) + (8 * BALLOON_SCORE[0]); break; case BalloonSize::SIZE3: score = BALLOON_SCORE[2] + (2 * BALLOON_SCORE[1]) + (4 * BALLOON_SCORE[0]); break; case BalloonSize::SIZE2: score = BALLOON_SCORE[1] + (2 * BALLOON_SCORE[0]); break; case BalloonSize::SIZE1: score = BALLOON_SCORE[0]; break; default: score = 0; break; } // Otorga los puntos correspondientes al globo for (auto &player : players_) player->addScore(score * player->getScoreMultiplier() * difficulty_score_multiplier_); updateHiScore(); // Aumenta el poder de la fase const auto power = balloon->getPower(); increaseStageCurrentPower(power); balloons_popped_ += power; // Destruye el globo explosions_->add(balloon->getPosX(), balloon->getPosY(), static_cast(balloon->getSize())); balloon->pop(); } // Destruye todos los globos void BalloonManager::destroyAllBalloons() { for (auto &balloon : balloons_) destroyBalloon(balloon); balloon_deploy_counter_ = 300; JA_PlaySound(Resource::get()->getSound("powerball.wav")); Screen::get()->flash(flash_color, 100); Screen::get()->shake(); } // Detiene todos los globos void BalloonManager::stopAllBalloons() { for (auto &balloon : balloons_) { balloon->stop(); } } // Pone en marcha todos los globos void BalloonManager::startAllBalloons() { for (auto &balloon : balloons_) { if (!balloon->isBeingCreated()) { balloon->start(); } } } // Cambia el color de todos los globos void BalloonManager::reverseColorsToAllBalloons() { for (auto &balloon : balloons_) { if (balloon->isStopped()) { balloon->useReverseColor(); } } } // Cambia el color de todos los globos void BalloonManager::normalColorsToAllBalloons() { for (auto &balloon : balloons_) { balloon->useNormalColor(); } }