#include "balloon_manager.h" #include // Para remove_if #include #include // Para rand #include // Para accumulate #include "balloon.h" // Para Balloon, Balloon::SCORE.at( )ALLOON_VELX... #include "balloon_formations.h" // Para BalloonFormationParams, BalloonForma... #include "color.h" // Para Zone, Color, flash_color #include "explosions.h" // Para Explosions #include "param.h" // Para Param, ParamGame, param #include "resource.h" // Para Resource #include "screen.h" // Para Screen #include "stage_interface.h" // Para IStageInfo #include "utils.h" // Constructor BalloonManager::BalloonManager(IStageInfo *stage_info) : explosions_(std::make_unique()), balloon_formations_(std::make_unique()), stage_info_(stage_info) { init(); } // Inicializa void BalloonManager::init() { // Limpia balloon_textures_.clear(); balloon_animations_.clear(); explosions_textures_.clear(); explosions_animations_.clear(); // Texturas - Globos balloon_textures_.emplace_back(Resource::get()->getTexture("balloon0.png")); 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("powerball.png")); // Animaciones -- Globos balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon0.ani")); 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("powerball.ani")); // Texturas - Explosiones explosions_textures_.emplace_back(Resource::get()->getTexture("explosion0.png")); 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")); // Animaciones -- Explosiones explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion0.ani")); 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")); // Añade texturas explosions_->addTexture(0, explosions_textures_.at(0), explosions_animations_.at(0)); explosions_->addTexture(1, explosions_textures_.at(1), explosions_animations_.at(1)); explosions_->addTexture(2, explosions_textures_.at(2), explosions_animations_.at(2)); explosions_->addTexture(3, explosions_textures_.at(3), explosions_animations_.at(3)); } // Actualiza (time-based) void BalloonManager::update(float deltaTime) { for (const auto &balloon : balloons_) { balloon->update(deltaTime); } updateBalloonDeployCounter(deltaTime); explosions_->update(deltaTime); } // Renderiza los objetos void BalloonManager::render() { for (auto &balloon : balloons_) { balloon->render(); } explosions_->render(); } // Crea una formación de globos void BalloonManager::deployRandomFormation(int stage) { // Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última if (balloon_deploy_counter_ >= DEFAULT_BALLOON_DEPLOY_COUNTER) { // En este punto se decide entre crear una powerball o una formación enemiga if ((rand() % 100 < 15) && (canPowerBallBeCreated())) { createPowerBall(); // Crea una powerball balloon_deploy_counter_ = -167; // Resetea con pequeño retraso (10 frames = 167ms negativos) } else { // Decrementa el contador de despliegues de globos necesarios para la siguiente PowerBall if (power_ball_counter_ > 0) { --power_ball_counter_; } // Elige una formación enemiga la azar const auto NUM_FORMATIONS = balloon_formations_->getPoolSize(stage); int formation_id = rand() % NUM_FORMATIONS; // Evita repetir la ultima formación enemiga desplegada if (formation_id == last_balloon_deploy_) { ++formation_id %= NUM_FORMATIONS; } last_balloon_deploy_ = formation_id; // Crea los globos de la formación const auto BALLOONS = balloon_formations_->getFormationFromPool(stage, formation_id).balloons; for (auto balloon : BALLOONS) { Balloon::Config config = { .x = balloon.x, .y = balloon.y, .type = balloon.type, .size = balloon.size, .vel_x = balloon.vel_x, .game_tempo = balloon_speed_, .creation_counter = static_cast(creation_time_enabled_ ? balloon.creation_counter : 0)}; createBalloon(config); } // Reinicia el contador para el próximo despliegue balloon_deploy_counter_ = 0; } } } // Crea una formación de globos específica void BalloonManager::deployFormation(int formation_id) { const auto BALLOONS = balloon_formations_->getFormation(formation_id).balloons; for (auto balloon : BALLOONS) { Balloon::Config config = { .x = balloon.x, .y = balloon.y, .type = balloon.type, .size = balloon.size, .vel_x = balloon.vel_x, .game_tempo = balloon_speed_, .creation_counter = balloon.creation_counter}; createBalloon(config); } } // Crea una formación de globos específica a una altura determinada void BalloonManager::deployFormation(int formation_id, float y) { const auto BALLOONS = balloon_formations_->getFormation(formation_id).balloons; for (auto balloon : BALLOONS) { Balloon::Config config = { .x = balloon.x, .y = y, .type = balloon.type, .size = balloon.size, .vel_x = balloon.vel_x, .game_tempo = balloon_speed_, .creation_counter = balloon.creation_counter}; createBalloon(config); } } // Vacia del vector de globos los globos que ya no sirven void BalloonManager::freeBalloons() { auto result = std::ranges::remove_if(balloons_, [](const auto &balloon) { return !balloon->isEnabled(); }); balloons_.erase(result.begin(), balloons_.end()); } // Actualiza la variable enemyDeployCounter (time-based) void BalloonManager::updateBalloonDeployCounter(float deltaTime) { // DeltaTime puro - contador incrementa hasta llegar al umbral balloon_deploy_counter_ += deltaTime; } // Indica si se puede crear una powerball auto BalloonManager::canPowerBallBeCreated() -> bool { return (!power_ball_enabled_) && (calculateScreenPower() > Balloon::POWERBALL_SCREENPOWER_MINIMUM) && (power_ball_counter_ == 0); } // Calcula el poder actual de los globos en pantalla auto BalloonManager::calculateScreenPower() -> int { 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 auto BalloonManager::createBalloon(Balloon::Config config) -> std::shared_ptr { if (can_deploy_balloons_) { const int INDEX = static_cast(config.size); config.play_area = play_area_; config.texture = balloon_textures_.at(INDEX); config.animation = balloon_animations_.at(INDEX); config.sound.enabled = sound_enabled_; config.sound.bouncing_enabled = bouncing_sound_enabled_; config.sound.poping_enabled = poping_sound_enabled_; balloons_.emplace_back(std::make_shared(config)); return balloons_.back(); } return nullptr; } // Crea un globo a partir de otro globo void BalloonManager::createChildBalloon(const std::shared_ptr &balloon, const std::string &direction) { if (can_deploy_balloons_) { // Calcula parametros const int PARENT_HEIGHT = balloon->getHeight(); const int CHILD_HEIGHT = Balloon::WIDTH.at(static_cast(balloon->getSize()) - 1); const int CHILD_WIDTH = CHILD_HEIGHT; const float X = direction == "LEFT" ? balloon->getPosX() + (balloon->getWidth() / 3) : balloon->getPosX() + (2 * (balloon->getWidth() / 3)); const float MIN_X = play_area_.x; const float MAX_X = play_area_.w - CHILD_WIDTH; Balloon::Config config = { .x = std::clamp(X - (CHILD_WIDTH / 2), MIN_X, MAX_X), .y = balloon->getPosY() + ((PARENT_HEIGHT - CHILD_HEIGHT) / 2), .size = static_cast(static_cast(balloon->getSize()) - 1), .vel_x = direction == "LEFT" ? Balloon::VELX_NEGATIVE : Balloon::VELX_POSITIVE, .game_tempo = balloon_speed_, .creation_counter = 0}; // Crea el globo auto b = createBalloon(config); // Establece parametros (deltaTime puro - valores ya en pixels/ms) constexpr float VEL_Y_BALLOON_PER_MS = -0.15F; // -2.50F convertido a pixels/ms b->setVelY(b->getType() == Balloon::Type::BALLOON ? VEL_Y_BALLOON_PER_MS : Balloon::VELX_NEGATIVE * 2.0F); // Herencia de estados if (balloon->isStopped()) { b->stop(); } if (balloon->isUsingReversedColor()) { b->useReverseColor(); } } } // Crea una PowerBall void BalloonManager::createPowerBall() { if (can_deploy_balloons_) { constexpr int VALUES = 6; const int LUCK = rand() % VALUES; const float LEFT = param.game.play_area.rect.x; const float CENTER = param.game.play_area.center_x - (Balloon::WIDTH.at(4) / 2); const float RIGHT = param.game.play_area.rect.w - Balloon::WIDTH.at(4); const std::array POS_X = {LEFT, LEFT, CENTER, CENTER, RIGHT, RIGHT}; const std::array VEL_X = {Balloon::VELX_POSITIVE, Balloon::VELX_POSITIVE, Balloon::VELX_POSITIVE, Balloon::VELX_NEGATIVE, Balloon::VELX_NEGATIVE, Balloon::VELX_NEGATIVE}; Balloon::Config config = { .x = POS_X.at(LUCK), .y = -Balloon::WIDTH.at(4), .type = Balloon::Type::POWERBALL, .size = Balloon::Size::EXTRALARGE, .vel_x = VEL_X.at(LUCK), .game_tempo = balloon_speed_, .creation_counter = 0, .play_area = play_area_, .texture = balloon_textures_.at(4), .animation = balloon_animations_.at(4), .sound = { .bouncing_enabled = bouncing_sound_enabled_, .poping_enabled = poping_sound_enabled_, .enabled = sound_enabled_}}; balloons_.emplace_back(std::make_unique(config)); balloons_.back()->setInvulnerable(true); power_ball_enabled_ = true; power_ball_counter_ = Balloon::POWERBALL_COUNTER; } } // Establece la velocidad de los globos void BalloonManager::setBalloonSpeed(float speed) { balloon_speed_ = speed; for (auto &balloon : balloons_) { balloon->setGameTempo(speed); } } // Explosiona un globo. Lo destruye y crea otros dos si es el caso auto BalloonManager::popBalloon(const std::shared_ptr &balloon) -> int { stage_info_->addPower(1); int score = 0; if (balloon->getType() == Balloon::Type::POWERBALL) { balloon->pop(true); score = destroyAllBalloons(); power_ball_enabled_ = false; balloon_deploy_counter_ = -334; // Resetea con retraso (20 frames = 334ms negativos) } else { score = balloon->getScore(); if (balloon->getSize() != Balloon::Size::SMALL) { 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(true); } return score; } // Explosiona un globo. Lo destruye = no crea otros globos auto BalloonManager::destroyBalloon(std::shared_ptr &balloon) -> int { 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 Balloon::Size::EXTRALARGE: score = Balloon::SCORE.at(3) + (2 * Balloon::SCORE.at(2)) + (4 * Balloon::SCORE.at(1)) + (8 * Balloon::SCORE.at(0)); break; case Balloon::Size::LARGE: score = Balloon::SCORE.at(2) + (2 * Balloon::SCORE.at(1)) + (4 * Balloon::SCORE.at(0)); break; case Balloon::Size::MEDIUM: score = Balloon::SCORE.at(1) + (2 * Balloon::SCORE.at(0)); break; case Balloon::Size::SMALL: score = Balloon::SCORE.at(0); break; default: score = 0; break; } // Aumenta el poder de la fase stage_info_->addPower(balloon->getPower()); // Destruye el globo explosions_->add(balloon->getPosX(), balloon->getPosY(), static_cast(balloon->getSize())); balloon->pop(); return score; } // Destruye todos los globos auto BalloonManager::destroyAllBalloons() -> int { int score = 0; for (auto &balloon : balloons_) { score += destroyBalloon(balloon); } balloon_deploy_counter_ = -5000; // Resetea con retraso grande (300 frames = 5000ms negativos) Screen::get()->flash(Colors::FLASH, 3); Screen::get()->shake(); return score; } // 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(); } } // Crea dos globos gordos void BalloonManager::createTwoBigBalloons() { deployFormation(1); } // Obtiene el nivel de ameza actual generado por los globos auto BalloonManager::getMenace() -> int { return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon) { return sum + (balloon->isEnabled() ? balloon->getMenace() : 0); }); } // Establece el sonido de los globos void BalloonManager::setSounds(bool value) { sound_enabled_ = value; for (auto &balloon : balloons_) { balloon->setSound(value); } } // Activa o desactiva los sonidos de rebote los globos void BalloonManager::setBouncingSounds(bool value) { bouncing_sound_enabled_ = value; for (auto &balloon : balloons_) { balloon->setBouncingSound(value); } } // Activa o desactiva los sonidos de los globos al explotar void BalloonManager::setPoppingSounds(bool value) { poping_sound_enabled_ = value; for (auto &balloon : balloons_) { balloon->setPoppingSound(value); } }