412 lines
16 KiB
C++
412 lines
16 KiB
C++
#include "balloon_manager.h"
|
|
|
|
#include <algorithm> // Para remove_if
|
|
#include <array>
|
|
#include <cstdlib> // Para rand
|
|
#include <numeric> // 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<Explosions>()),
|
|
balloon_formations_(std::make_unique<BalloonFormations>()),
|
|
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 el timer ha llegado a cero
|
|
if (balloon_deploy_counter_ <= 0.0f) {
|
|
// 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_ = POWERBALL_DEPLOY_DELAY; // Resetea con pequeño retraso
|
|
} 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 = creation_time_enabled_ ? balloon.creation_counter : 0.0f};
|
|
createBalloon(config);
|
|
}
|
|
|
|
// Reinicia el timer para el próximo despliegue
|
|
balloon_deploy_counter_ = DEFAULT_BALLOON_DEPLOY_DELAY;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 el timer de despliegue de globos (time-based)
|
|
void BalloonManager::updateBalloonDeployCounter(float deltaTime) {
|
|
// DeltaTime en segundos - timer decrementa hasta llegar a cero
|
|
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<Balloon> {
|
|
if (can_deploy_balloons_) {
|
|
const int INDEX = static_cast<int>(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<Balloon>(config));
|
|
return balloons_.back();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Crea un globo a partir de otro globo
|
|
void BalloonManager::createChildBalloon(const std::shared_ptr<Balloon> &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<int>(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),
|
|
.type = balloon->getType(),
|
|
.size = static_cast<Balloon::Size>(static_cast<int>(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 en segundos - velocidades en pixels/segundo)
|
|
constexpr float VEL_Y_BALLOON_PER_S = -150.0F; // -2.50 pixels/frame convertido a pixels/segundo (-2.50 * 60 = -150)
|
|
b->setVelY(b->getType() == Balloon::Type::BALLOON ? VEL_Y_BALLOON_PER_S : 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<float, VALUES> POS_X = {LEFT, LEFT, CENTER, CENTER, RIGHT, RIGHT};
|
|
const std::array<float, VALUES> 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<Balloon>(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> &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_ = BALLOON_POP_DELAY; // Resetea con retraso
|
|
} 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<int>(balloon->getSize()));
|
|
balloon->pop(true);
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
// Explosiona un globo. Lo destruye = no crea otros globos
|
|
auto BalloonManager::destroyBalloon(std::shared_ptr<Balloon> &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<int>(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_ = DEFAULT_BALLOON_DEPLOY_DELAY;
|
|
Screen::get()->flash(Colors::FLASH, 0.05F);
|
|
Screen::get()->shake();
|
|
|
|
return score;
|
|
}
|
|
|
|
// Detiene todos los globos
|
|
void BalloonManager::stopAllBalloons() {
|
|
for (auto &balloon : balloons_) {
|
|
if (!balloon->isBeingCreated()) {
|
|
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);
|
|
}
|
|
} |