Files
coffee_crisis_arcade_edition/source/game.cpp
Sergio Valor 787cb6366f Pasaeta de include-what-you-use
Acabada de perfilar la classe PathSprite
Menjeades declaracions de utils.h als fitxers que toca
2024-10-28 20:45:24 +01:00

2096 lines
60 KiB
C++

#include "game.h"
#include <SDL2/SDL_blendmode.h> // Para SDL_BLENDMODE_BLEND
#include <SDL2/SDL_events.h> // Para SDL_PollEvent, SDL_Event, SDL_KEYDOWN
#include <SDL2/SDL_keycode.h> // Para SDLK_1, SDLK_2, SDLK_3, SDLK_4
#include <SDL2/SDL_pixels.h> // Para SDL_PIXELFORMAT_RGBA8888
#include <SDL2/SDL_timer.h> // Para SDL_GetTicks
#include <SDL2/SDL_video.h> // Para SDL_WINDOWEVENT_FOCUS_GAINED, SDL_...
#include <stdlib.h> // Para rand, size_t
#include <algorithm> // Para find_if, clamp, min, remove_if
#include <iterator> // Para distance, size
#include <numeric> // Para accumulate
#include "asset.h" // Para Asset
#include "background.h" // Para Background
#include "balloon.h" // Para Balloon, BALLOON_SCORE, BALLOON_VE...
#include "balloon_formations.h" // Para BalloonFormations, BalloonFormatio...
#include "bullet.h" // Para Bullet, BulletType, BulletMoveStatus
#include "explosions.h" // Para Explosions
#include "fade.h" // Para Fade, FadeType
#include "global_inputs.h" // Para check
#include "input.h" // Para InputType, Input, INPUT_DO_NOT_ALL...
#include "item.h" // Para Item, ItemType
#include "jail_audio.h" // Para JA_PlaySound, JA_GetMusicState
#include "lang.h" // Para getText
#include "manage_hiscore_table.h" // Para ManageHiScoreTable
#include "notifier.h" // Para Notifier
#include "param.h" // Para Param, param, ParamGame, ParamFade
#include "path_sprite.h" // Para PathSprite, PathType
#include "player.h" // Para Player, PlayerStatus
#include "resource.h" // Para Resource
#include "scoreboard.h" // Para Scoreboard, ScoreboardMode, SCOREB...
#include "screen.h" // Para Screen
#include "section.h" // Para Name, name, Options, options
#include "smart_sprite.h" // Para SmartSprite
#include "text.h" // Para Text, TEXT_CENTER
#include "texture.h" // Para Texture
struct JA_Sound_t; // lines 40-40
// Constructor
Game::Game(int player_id, int current_stage, bool demo)
: renderer_(Screen::get()->getRenderer()),
screen_(Screen::get()),
asset_(Asset::get()),
input_(Input::get()),
background_(std::make_unique<Background>()),
explosions_(std::make_unique<Explosions>()),
balloon_formations_(std::make_unique<BalloonFormations>()),
canvas_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.play_area.rect.w, param.game.play_area.rect.h)),
fade_(std::make_unique<Fade>()),
current_stage_(current_stage),
last_stage_reached_(current_stage)
{
// Pasa variables
demo_.enabled = demo;
// Otras variables
section::name = section::Name::GAME;
section::options = section::Options::GAME_PLAY_1P;
// Asigna texturas y animaciones
setResources();
// Crea y configura los objetos
Scoreboard::init();
scoreboard_ = Scoreboard::get();
fade_->setColor(fade_color.r, fade_color.g, fade_color.b);
fade_->setPost(param.fade.post_duration);
fade_->setType(FadeType::VENETIAN);
background_->setPos(param.game.play_area.rect);
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]);
SDL_SetTextureBlendMode(canvas_, SDL_BLENDMODE_BLEND);
// Inicializa el resto de variables
initPlayers(player_id);
initScoreboard();
initDifficultyVars();
initDemo(player_id);
initPaths();
setTotalPower();
// Crea los primeros globos
if (!demo_.enabled)
{
createTwoBigBalloons();
evaluateAndSetMenace();
}
}
Game::~Game()
{
// Guarda las puntuaciones en un fichero
if (!demo_.enabled)
{
auto manager = std::make_unique<ManageHiScoreTable>(options.game.hi_score_table);
manager->saveToFile(asset_->get("score.bin"));
}
#ifdef RECORDING
saveDemoFile(Asset::get()->get("demo1.bin"), demo_.data.at(0));
#endif
Scoreboard::destroy();
SDL_DestroyTexture(canvas_);
}
// Asigna texturas y animaciones
void Game::setResources()
{
// Texturas
{
bullet_texture_ = Resource::get()->getTexture("bullet.png");
}
// Texturas - Game_text
{
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_1000_points.png"));
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_2500_points.png"));
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_5000_points.png"));
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_powerup.png"));
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_one_hit.png"));
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_stop.png"));
}
// 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"));
}
// Texturas - Items
{
item_textures_.emplace_back(Resource::get()->getTexture("item_points1_disk.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_points2_gavina.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_points3_pacmar.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_clock.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_coffee.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_coffee_machine.png"));
}
// Texturas - Player1
{
std::vector<std::shared_ptr<Texture>> player_texture;
player_texture.emplace_back(Resource::get()->getTexture("player1.gif"));
player_texture.emplace_back(Resource::get()->getTexture("player1_power.png"));
player_textures_.push_back(player_texture);
}
// Texturas - Player2
{
std::vector<std::shared_ptr<Texture>> player_texture;
player_texture.emplace_back(Resource::get()->getTexture("player2.gif"));
player_texture.emplace_back(Resource::get()->getTexture("player2_power.png"));
player_textures_.push_back(player_texture);
}
// Animaciones -- Jugador
{
player_animations_.emplace_back(Resource::get()->getAnimation("player.ani"));
player_animations_.emplace_back(Resource::get()->getAnimation("player_power.ani"));
}
// 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"));
}
// Animaciones -- Items
{
item_animations_.emplace_back(Resource::get()->getAnimation("item_points1_disk.ani"));
item_animations_.emplace_back(Resource::get()->getAnimation("item_points2_gavina.ani"));
item_animations_.emplace_back(Resource::get()->getAnimation("item_points3_pacmar.ani"));
item_animations_.emplace_back(Resource::get()->getAnimation("item_clock.ani"));
item_animations_.emplace_back(Resource::get()->getAnimation("item_coffee.ani"));
item_animations_.emplace_back(Resource::get()->getAnimation("item_coffee_machine.ani"));
}
// Texto
{
text_ = std::make_unique<Text>(Resource::get()->getTexture("smb2.gif"), Resource::get()->getTextFile("smb2.txt"));
text_big_ = std::make_unique<Text>(Resource::get()->getTexture("smb2_big.png"), Resource::get()->getTextFile("smb2_big.txt"));
text_nokia2_ = std::make_unique<Text>(Resource::get()->getTexture("nokia2.png"), Resource::get()->getTextFile("nokia2.txt"));
text_nokia2_big_ = std::make_unique<Text>(Resource::get()->getTexture("nokia_big2.png"), Resource::get()->getTextFile("nokia_big2.txt"));
}
}
// Crea una formación de enemigos
void Game::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;
}
}
}
// Aumenta el poder de la fase
void Game::increaseStageCurrentPower(int power)
{
current_power_ += power;
}
// Actualiza el valor de hiScore en caso necesario
void Game::updateHiScore()
{
// Si la puntuación actual es mayor que la máxima puntuación
for (const auto &player : players_)
{
if (player->getScore() > hi_score_.score)
{
// Actualiza la máxima puntuación
hi_score_.score = player->getScore();
// Si se supera la máxima puntuación emite sonido
if (hi_score_achieved_ == false)
{
hi_score_achieved_ = true;
JA_PlaySound(Resource::get()->getSound("hiscore.wav"));
}
}
}
}
// Actualiza las variables del jugador
void Game::updatePlayers()
{
for (auto &player : players_)
{
player->update();
if (player->isPlaying())
{
// Comprueba la colisión entre el jugador y los globos
if (checkPlayerBalloonCollision(player))
{
killPlayer(player);
if (demo_.enabled && allPlayersAreNotPlaying())
{
fade_->setType(FadeType::RANDOM_SQUARE);
fade_->activate();
}
}
// Comprueba las colisiones entre el jugador y los items
checkPlayerItemCollision(player);
}
}
}
// Dibuja a los jugadores
void Game::renderPlayers()
{
for (auto &player : players_)
{
if (!player->isWaiting())
{
player->render();
#ifdef DEBUG
// SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
// const Circle c = player->getCollider();
// DrawCircle(renderer, c.x, c.y, c.r);
#endif
}
}
}
// Comprueba si hay cambio de fase y actualiza las variables
void Game::updateStage()
{
if (current_power_ >= balloon_formations_->getStage(current_stage_).power_to_complete)
{
// Cambio de fase
current_stage_++;
current_power_ = 0;
last_stage_reached_ = current_stage_;
if (current_stage_ == 10)
{ // Ha llegado al final el juego
game_completed_ = true; // Marca el juego como completado
current_stage_ = 9; // Deja el valor dentro de los limites
destroyAllBalloons(); // Destruye a todos los enemigos
current_power_ = 0; // Vuelve a dejar el poder a cero, por lo que hubiera podido subir al destruir todos lo globos
menace_current_ = 255; // Sube el nivel de amenaza para que no cree mas globos
for (auto &player : players_)
{ // Añade un millon de puntos a los jugadores que queden vivos
if (player->isPlaying())
{
player->addScore(1000000);
}
}
updateHiScore();
JA_StopMusic();
}
JA_PlaySound(Resource::get()->getSound("stage_change.wav"));
stage_bitmap_counter_ = 0;
balloon_speed_ = default_balloon_speed_;
setBalloonSpeed(balloon_speed_);
screen_->flash(flash_color, 5);
screen_->shake();
}
// Incrementa el contador del bitmap que aparece mostrando el cambio de fase
if (stage_bitmap_counter_ < STAGE_COUNTER_)
{
stage_bitmap_counter_++;
}
// Si el juego se ha completado, el bitmap se detiene en el centro de la pantalla
if (game_completed_)
{
if (stage_bitmap_counter_ > 100)
{
stage_bitmap_counter_ = 100;
}
}
}
// Actualiza el estado de fin de la partida
void Game::updateGameOver()
{
// Si todos estan en estado de Game Over
if (allPlayersAreGameOver())
{
if (game_over_counter_ > 0)
{
game_over_counter_--;
if ((game_over_counter_ == 250) || (game_over_counter_ == 200) || (game_over_counter_ == 180) || (game_over_counter_ == 120) || (game_over_counter_ == 60))
{
// Hace sonar aleatoriamente uno de los 4 sonidos de burbujas
const auto index = rand() % 4;
JA_Sound_t *sound[4] = {
Resource::get()->getSound("bubble1.wav"),
Resource::get()->getSound("bubble2.wav"),
Resource::get()->getSound("bubble3.wav"),
Resource::get()->getSound("bubble4.wav")};
JA_PlaySound(sound[index], 0);
}
if (game_over_counter_ == 150)
{
fade_->activate();
}
}
if (fade_->hasEnded())
{
section::name = section::Name::HI_SCORE_TABLE;
}
}
}
// Actualiza los globos
void Game::updateBalloons()
{
for (auto balloon : balloons_)
{
balloon->update();
}
}
// Pinta en pantalla todos los globos activos
void Game::renderBalloons()
{
for (auto &balloon : balloons_)
{
balloon->render();
}
}
// Crea un globo nuevo en el vector de globos
std::shared_ptr<Balloon> Game::createBalloon(float x, int y, BalloonType type, BalloonSize size, float velx, float speed, int creation_timer)
{
const int index = static_cast<int>(size);
balloons_.emplace_back(std::make_shared<Balloon>(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 Game::createChildBalloon(const std::shared_ptr<Balloon> &balloon, const std::string &direction)
{
const float vx = direction == "LEFT" ? BALLOON_VELX_NEGATIVE : BALLOON_VELX_POSITIVE;
const auto lower_size = static_cast<BalloonSize>(static_cast<int>(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 : vx * 2.0f);
if (balloon->isStopped())
b->stop();
if (balloon->isUsingReversedColor())
b->useReverseColor();
}
// Crea una PowerBall
void Game::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<Balloon>(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 Game::setBalloonSpeed(float speed)
{
for (auto &balloon : balloons_)
{
if (balloon->isEnabled())
{
balloon->setSpeed(speed);
}
}
}
// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
void Game::updateBalloonSpeed()
{
const float percent = static_cast<float>(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 Game::popBalloon(std::shared_ptr<Balloon> 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<int>(balloon->getSize()));
balloon->pop();
}
// Recalcula el nivel de amenaza
evaluateAndSetMenace();
}
// Explosiona un globo. Lo destruye = no crea otros globos
void Game::destroyBalloon(std::shared_ptr<Balloon> &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<int>(balloon->getSize()));
balloon->pop();
}
// Destruye todos los globos
void Game::destroyAllBalloons()
{
for (auto &balloon : balloons_)
if (balloon->canBeDestroyed())
destroyBalloon(balloon);
balloon_deploy_counter_ = 300;
JA_PlaySound(Resource::get()->getSound("powerball.wav"));
screen_->flash(flash_color, 5);
screen_->shake();
}
// Detiene todos los globos
void Game::stopAllBalloons()
{
for (auto &balloon : balloons_)
balloon->stop();
}
// Pone en marcha todos los globos
void Game::startAllBalloons()
{
for (auto &balloon : balloons_)
if (!balloon->isBeingCreated())
balloon->start();
}
// Cambia el color de todos los globos
void Game::reverseColorsToAllBalloons()
{
for (auto &balloon : balloons_)
if (balloon->isStopped())
balloon->useReverseColor();
}
// Cambia el color de todos los globos
void Game::normalColorsToAllBalloons()
{
for (auto &balloon : balloons_)
balloon->useNormalColor();
}
// Vacia del vector de globos los globos que ya no sirven
void Game::freeBalloons()
{
auto it = std::remove_if(balloons_.begin(), balloons_.end(),
[](const auto &balloon)
{ return !balloon->isEnabled(); });
balloons_.erase(it, balloons_.end());
}
// Comprueba la colisión entre el jugador y los globos activos
bool Game::checkPlayerBalloonCollision(std::shared_ptr<Player> &player)
{
for (auto &balloon : balloons_)
if ((balloon->isEnabled()) && !(balloon->isStopped()) && !(balloon->isInvulnerable()))
if (checkCollision(player->getCollider(), balloon->getCollider()))
return true;
return false;
}
// Comprueba la colisión entre el jugador y los items
void Game::checkPlayerItemCollision(std::shared_ptr<Player> &player)
{
if (!player->isPlaying())
return;
for (auto &item : items_)
{
if (item->isEnabled())
{
if (checkCollision(player->getCollider(), item->getCollider()))
{
switch (item->getType())
{
case ItemType::DISK:
{
player->addScore(1000);
createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[0]->getWidth() / 2), player->getPosY(), game_text_textures_[0]);
break;
}
case ItemType::GAVINA:
{
player->addScore(2500);
createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[1]->getWidth() / 2), player->getPosY(), game_text_textures_[1]);
break;
}
case ItemType::PACMAR:
{
player->addScore(5000);
createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[2]->getWidth() / 2), player->getPosY(), game_text_textures_[2]);
break;
}
case ItemType::CLOCK:
{
enableTimeStopItem();
createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[5]->getWidth() / 2), player->getPosY(), game_text_textures_[5]);
break;
}
case ItemType::COFFEE:
{
if (player->getCoffees() == 2)
{
player->addScore(5000);
createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[2]->getWidth() / 2), player->getPosY(), game_text_textures_[2]);
}
else
{
player->giveExtraHit();
createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[4]->getWidth() / 2), player->getPosY(), game_text_textures_[4]);
}
break;
}
case ItemType::COFFEE_MACHINE:
{
player->setPowerUp();
coffee_machine_enabled_ = false;
createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (game_text_textures_[3]->getWidth() / 2), player->getPosY(), game_text_textures_[3]);
break;
}
default:
break;
}
updateHiScore();
JA_PlaySound(Resource::get()->getSound("itempickup.wav"));
item->disable();
}
}
}
}
// Comprueba y procesa la colisión entre las balas y los globos
void Game::checkBulletBalloonCollision()
{
for (auto &bullet : bullets_)
{
for (auto &balloon : balloons_)
{
if (balloon->isEnabled() && (!balloon->isInvulnerable()) && bullet->isEnabled())
{
if (checkCollision(balloon->getCollider(), bullet->getCollider()))
{
// Otorga los puntos correspondientes al globo al jugador que disparó la bala
auto player = getPlayer(bullet->getOwner());
if (!player)
{
return;
}
player->addScore(balloon->getScore() * player->getScoreMultiplier() * difficulty_score_multiplier_);
player->incScoreMultiplier();
updateHiScore();
// Suelta el item si se da el caso
const auto droppeditem = dropItem();
if (droppeditem != ItemType::NONE && !demo_.recording)
{
if (droppeditem != ItemType::COFFEE_MACHINE)
{
createItem(droppeditem, balloon->getPosX(), balloon->getPosY());
JA_PlaySound(Resource::get()->getSound("itemdrop.wav"));
}
else
{
createItem(droppeditem, player->getPosX(), 0);
coffee_machine_enabled_ = true;
}
}
// Explota el globo
popBalloon(balloon);
// Sonido de explosión
JA_PlaySound(Resource::get()->getSound("balloon.wav"));
// Deshabilita la bala
bullet->disable();
break;
}
}
}
}
}
// Mueve las balas activas
void Game::moveBullets()
{
for (auto &bullet : bullets_)
if (bullet->move() == BulletMoveStatus::OUT)
getPlayer(bullet->getOwner())->decScoreMultiplier();
}
// Pinta las balas activas
void Game::renderBullets()
{
for (auto &bullet : bullets_)
bullet->render();
}
// Crea un objeto bala
void Game::createBullet(int x, int y, BulletType kind, bool powered_up, int owner)
{
bullets_.emplace_back(std::make_unique<Bullet>(x, y, kind, powered_up, owner, bullet_texture_));
}
// Vacia el vector de balas
void Game::freeBullets()
{
if (!bullets_.empty())
for (int i = bullets_.size() - 1; i >= 0; --i)
if (!bullets_[i]->isEnabled())
bullets_.erase(bullets_.begin() + i);
}
// Actualiza los items
void Game::updateItems()
{
for (auto &item : items_)
if (item->isEnabled())
{
item->update();
if (item->isOnFloor())
{
JA_PlaySound(Resource::get()->getSound("title.wav"));
screen_->shake();
}
}
}
// Pinta los items activos
void Game::renderItems()
{
for (auto &item : items_)
item->render();
}
// Devuelve un item al azar y luego segun sus probabilidades
ItemType Game::dropItem()
{
const auto lucky_number = rand() % 100;
const auto item = rand() % 6;
switch (item)
{
case 0:
if (lucky_number < helper_.item_disk_odds)
{
return ItemType::DISK;
}
break;
case 1:
if (lucky_number < helper_.item_gavina_odds)
{
return ItemType::GAVINA;
}
break;
case 2:
if (lucky_number < helper_.item_pacmar_odds)
{
return ItemType::GAVINA;
}
break;
case 3:
if (lucky_number < helper_.item_clock_odds)
{
return ItemType::CLOCK;
}
break;
case 4:
if (lucky_number < helper_.item_coffee_odds)
{
helper_.item_coffee_odds = ITEM_COFFEE_ODDS_;
return ItemType::COFFEE;
}
else
{
if (helper_.need_coffee)
helper_.item_coffee_odds++;
}
break;
case 5:
if (lucky_number < helper_.item_coffee_machine_odds)
{
helper_.item_coffee_machine_odds = ITEM_COFFEE_MACHINE_ODDS_;
if (!coffee_machine_enabled_ && helper_.need_coffee_machine)
return ItemType::COFFEE_MACHINE;
}
else
{
if (helper_.need_coffee_machine)
helper_.item_coffee_machine_odds++;
}
break;
default:
break;
}
return ItemType::NONE;
}
// Crea un objeto item
void Game::createItem(ItemType type, float x, float y)
{
items_.emplace_back(std::make_unique<Item>(type, x, y, param.game.play_area.rect, item_textures_[static_cast<int>(type) - 1], item_animations_[static_cast<int>(type) - 1]));
}
// Vacia el vector de items
void Game::freeItems()
{
if (!items_.empty())
for (int i = items_.size() - 1; i >= 0; --i)
if (!items_[i]->isEnabled())
items_.erase(items_.begin() + i);
}
// Crea un objeto SpriteSmart para mostrar la puntuación al coger un objeto
void Game::createItemScoreSprite(int x, int y, std::shared_ptr<Texture> texture)
{
smart_sprites_.emplace_back(std::make_unique<SmartSprite>(texture));
const auto w = texture->getWidth();
const auto h = texture->getHeight();
// Ajusta para que no se dibuje fuera de pantalla
x = std::clamp(x, 0, param.game.play_area.rect.w - w);
// Inicializa
smart_sprites_.back()->setPos({0, 0, w, h});
smart_sprites_.back()->setSpriteClip({0, 0, w, h});
smart_sprites_.back()->setPosX(x);
smart_sprites_.back()->setPosY(param.game.play_area.rect.h - h);
smart_sprites_.back()->setDestX(x);
smart_sprites_.back()->setDestY(y - 35);
smart_sprites_.back()->setVelY(-0.5f);
smart_sprites_.back()->setAccelY(-0.1f);
smart_sprites_.back()->setEnabled(true);
smart_sprites_.back()->setFinishedCounter(100);
}
// Crea un objeto PathSprite
void Game::createPathSprite(std::shared_ptr<Texture> texture)
{
path_sprites_.emplace_back(std::make_unique<PathSprite>(texture));
const auto w = texture->getWidth();
const auto h = texture->getHeight();
// Inicializa
path_sprites_.back()->setWidth(w);
path_sprites_.back()->setHeight(h);
path_sprites_.back()->setSpriteClip({0, 0, w, h});
path_sprites_.back()->enable();
}
// Vacia el vector de smartsprites
void Game::freeSmartSprites()
{
if (!smart_sprites_.empty())
for (int i = smart_sprites_.size() - 1; i >= 0; --i)
if (smart_sprites_[i]->hasFinished())
smart_sprites_.erase(smart_sprites_.begin() + i);
}
// Vacia el vector de pathsprites
void Game::freePathSprites()
{
if (!path_sprites_.empty())
for (int i = path_sprites_.size() - 1; i >= 0; --i)
if (path_sprites_[i]->hasFinished())
path_sprites_.erase(path_sprites_.begin() + i);
}
// Crea un SpriteSmart para arrojar el item café al recibir un impacto
void Game::throwCoffee(int x, int y)
{
smart_sprites_.emplace_back(std::make_unique<SmartSprite>(item_textures_[4]));
smart_sprites_.back()->setPosX(x - 8);
smart_sprites_.back()->setPosY(y - 8);
smart_sprites_.back()->setWidth(param.game.item_size);
smart_sprites_.back()->setHeight(param.game.item_size);
smart_sprites_.back()->setVelX(-1.0f + ((rand() % 5) * 0.5f));
smart_sprites_.back()->setVelY(-4.0f);
smart_sprites_.back()->setAccelX(0.0f);
smart_sprites_.back()->setAccelY(0.2f);
smart_sprites_.back()->setDestX(x + (smart_sprites_.back()->getVelX() * 50));
smart_sprites_.back()->setDestY(param.game.height + 1);
smart_sprites_.back()->setEnabled(true);
smart_sprites_.back()->setFinishedCounter(1);
smart_sprites_.back()->setSpriteClip(0, param.game.item_size, param.game.item_size, param.game.item_size);
smart_sprites_.back()->enableRotate();
smart_sprites_.back()->setRotateSpeed(10);
smart_sprites_.back()->setRotateAmount(90.0);
}
// Actualiza los SmartSprites
void Game::updateSmartSprites()
{
for (auto &sprite : smart_sprites_)
sprite->update();
}
// Pinta los SmartSprites activos
void Game::renderSmartSprites()
{
for (auto &sprite : smart_sprites_)
sprite->render();
}
// Actualiza los PathSprites
void Game::updatePathSprites()
{
for (auto &sprite : path_sprites_)
sprite->update();
}
// Pinta los PathSprites activos
void Game::renderPathSprites()
{
for (auto &sprite : path_sprites_)
sprite->render();
}
// Acciones a realizar cuando el jugador muere
void Game::killPlayer(std::shared_ptr<Player> &player)
{
if (!player->isPlaying() || player->isInvulnerable())
{ // Si no está jugando o tiene inmunidad, no hace nada
return;
}
// Si tiene cafes
if (player->hasExtraHit())
{
// Lo pierde
player->removeExtraHit();
throwCoffee(player->getPosX() + (player->getWidth() / 2), player->getPosY() + (player->getHeight() / 2));
JA_PlaySound(Resource::get()->getSound("coffeeout.wav"));
screen_->shake();
}
else
{
// Si no tiene cafes, muere
pauseMusic();
stopAllBalloons();
JA_PlaySound(Resource::get()->getSound("player_collision.wav"));
screen_->shake();
JA_PlaySound(Resource::get()->getSound("coffeeout.wav"));
player->setStatusPlaying(PlayerStatus::DYING);
allPlayersAreNotPlaying() ? stopMusic() : resumeMusic();
}
}
// Calcula y establece el valor de amenaza en funcion de los globos activos
void Game::evaluateAndSetMenace()
{
menace_current_ = std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon)
{ return sum + (balloon->isEnabled() ? balloon->getMenace() : 0); });
}
// Actualiza y comprueba el valor de la variable
void Game::updateTimeStopped()
{
if (time_stopped_counter_ > 0)
{
time_stopped_counter_--;
if (time_stopped_counter_ > 120)
{
if (time_stopped_counter_ % 30 == 0)
JA_PlaySound(Resource::get()->getSound("clock.wav"));
}
else
{
if (time_stopped_counter_ % 15 == 0)
JA_PlaySound(Resource::get()->getSound("clock.wav"));
if (time_stopped_counter_ % 30 == 0)
normalColorsToAllBalloons();
if (time_stopped_counter_ % 30 == 15)
reverseColorsToAllBalloons();
}
}
else
disableTimeStopItem();
}
// Actualiza la variable enemyDeployCounter
void Game::updateBalloonDeployCounter()
{
if (balloon_deploy_counter_ > 0)
--balloon_deploy_counter_;
}
// Actualiza el juego
void Game::update()
{
constexpr int TICKS_SPEED = 15;
if (SDL_GetTicks() - ticks_ > TICKS_SPEED)
{
// Actualiza el contador de ticks
ticks_ = SDL_GetTicks();
// Actualiza el contador de juego
counter_++;
if (demo_.enabled)
{
// Incrementa el contador de la demo
if (demo_.counter < TOTAL_DEMO_DATA)
demo_.counter++;
// Activa el fundido antes de acabar con los datos de la demo
if (demo_.counter == TOTAL_DEMO_DATA - 200)
{
fade_->setType(FadeType::RANDOM_SQUARE);
fade_->activate();
}
// Si ha terminado el fundido, cambia de sección
if (fade_->hasEnded())
{
section::name = section::Name::HI_SCORE_TABLE;
return;
}
}
#ifdef RECORDING
// Solo mira y guarda el input en cada update
checkInput();
// Incrementa el contador de la demo
if (demo_.counter < TOTAL_DEMO_DATA)
demo_.counter++;
// Si se ha llenado el vector con datos, sale del programa
else
{
section::name = section::Name::QUIT;
return;
}
#endif
#ifdef DEBUG
if (auto_pop_balloons_ && !game_completed_)
{
balloons_popped_++;
increaseStageCurrentPower(1);
}
#endif
if (!paused_)
{
// Actualiza el objeto fade
fade_->update();
// Actualiza las variables del jugador
updatePlayers();
// Actualiza el marcador
checkPlayersStatusPlaying();
updateScoreboard();
// Actualiza el fondo
updateBackground();
// Mueve los globos
updateBalloons();
// Actualiza el objeto encargado de las explosiones
explosions_->update();
// Mueve las balas
moveBullets();
// Actualiza los items
updateItems();
// Comprueba si hay cambio de fase y actualiza las variables
updateStage();
// Actualiza el estado de muerte
updateGameOver();
// Actualiza los SpriteSmarts
updateSmartSprites();
// Actualiza los PathSmarts
updatePathSprites();
// Actualiza los contadores de estado y efectos
updateTimeStopped();
updateBalloonDeployCounter();
// Actualiza el ayudante
updateHelper();
// Comprueba las colisiones entre globos y balas
checkBulletBalloonCollision();
// Comprueba el nivel de amenaza para ver si se han de crear nuevos enemigos
updateMenace();
// Actualiza la velocidad de los enemigos
if (difficulty_ == GameDifficulty::NORMAL)
{
updateBalloonSpeed();
}
// Actualiza el tramo final de juego, una vez completado
updateGameCompleted();
// Vacia los vectores
freeBullets();
freeBalloons();
freeItems();
freeSmartSprites();
freePathSprites();
}
// Comprueba si la música ha de estar sonando
checkMusicStatus();
// Actualiza el objeto screen
screen_->update();
// Dibuja los graficos de la zona de juego en la textura
fillCanvas();
}
}
// Actualiza el fondo
void Game::updateBackground()
{
// Si el juego está completado, se reduce la velocidad de las nubes
if (game_completed_)
balloons_popped_ = (balloons_popped_ > 400) ? (balloons_popped_ - 25) : 200;
// Calcula la velocidad en función de los globos explotados y el total de globos a explotar para acabar el juego
constexpr auto clouds_initial_speed = 0.05f;
constexpr auto clouds_final_speed = 2.00f - clouds_initial_speed;
const float cloudsSpeed = (-clouds_initial_speed) + (-clouds_final_speed * ((float)balloons_popped_ / (float)total_power_to_complete_game_));
background_->setCloudsSpeed(cloudsSpeed);
// Calcula la transición de los diferentes fondos
const float gradient_number = std::min(((float)balloons_popped_ / 1250.0f), 3.0f);
const float percent = gradient_number - (int)gradient_number;
background_->setGradientNumber((int)gradient_number);
background_->setTransition(percent);
// Actualiza el objeto
background_->update();
}
// Dibuja los elementos de la zona de juego en su textura
void Game::fillCanvas()
{
// Dibujamos el contenido de la zona de juego en su textura
auto temp = SDL_GetRenderTarget(renderer_);
SDL_SetRenderTarget(renderer_, canvas_);
// Dibuja los objetos
background_->render();
renderItems();
renderSmartSprites();
renderPathSprites();
explosions_->render();
renderBalloons();
renderBullets();
renderMessages();
renderPlayers();
// Deja el renderizador apuntando donde estaba
SDL_SetRenderTarget(renderer_, temp);
}
// Dibuja el juego
void Game::render()
{
// Prepara para empezar a dibujar en la textura de juego
screen_->start();
// Copia la textura con la zona de juego a la pantalla
SDL_RenderCopy(renderer_, canvas_, nullptr, &param.game.play_area.rect);
// Dibuja el marcador
scoreboard_->render();
// Dibuja el fade
fade_->render();
// Vuelca el contenido del renderizador en pantalla
screen_->blit();
}
// Gestiona el nivel de amenaza
void Game::updateMenace()
{
if (game_completed_)
return;
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();
}
}
// Pinta diferentes mensajes en la pantalla
void Game::renderMessages()
{
// GetReady
if (counter_ < STAGE_COUNTER_ && !demo_.enabled)
text_nokia2_big_->write((int)get_ready_bitmap_path_[counter_], param.game.play_area.center_y - 8, lang::getText(75), -2);
// STAGE NUMBER
if (stage_bitmap_counter_ < STAGE_COUNTER_)
{
const auto stage_number = balloon_formations_->getStage(current_stage_).number;
std::string text;
if (stage_number == 10)
{
// Ultima fase
text = lang::getText(79);
}
else
{
// X fases restantes
text = std::to_string(11 - stage_number) + lang::getText(38);
}
if (!game_completed_)
{
// Escribe el número de fases restantes
text_nokia2_big_->writeDX(TEXT_CENTER, param.game.play_area.center_x, stage_bitmap_path_[stage_bitmap_counter_], text, -2, no_color, 2, shdw_txt_color);
}
else
{
// Escribe el texto de juego completado
text = lang::getText(50);
text_nokia2_big_->writeDX(TEXT_CENTER, param.game.play_area.center_x, stage_bitmap_path_[stage_bitmap_counter_], text, -2, no_color, 1, shdw_txt_color);
text_nokia2_->writeDX(TEXT_CENTER, param.game.play_area.center_x, stage_bitmap_path_[stage_bitmap_counter_] + text_nokia2_big_->getCharacterSize() + 2, lang::getText(76), -1, no_color, 1, shdw_txt_color);
}
}
}
// Habilita el efecto del item de detener el tiempo
void Game::enableTimeStopItem()
{
stopAllBalloons();
reverseColorsToAllBalloons();
time_stopped_counter_ = TIME_STOPPED_COUNTER_;
}
// Deshabilita el efecto del item de detener el tiempo
void Game::disableTimeStopItem()
{
time_stopped_counter_ = 0;
startAllBalloons();
normalColorsToAllBalloons();
}
// Comprueba si la música ha de estar sonando
void Game::checkMusicStatus()
{
// Si la música no está sonando
if (JA_GetMusicState() == JA_MUSIC_INVALID || JA_GetMusicState() == JA_MUSIC_STOPPED)
// Si se ha completado el juego o los jugadores han terminado, detiene la música
game_completed_ || allPlayersAreGameOver() ? JA_StopMusic() : JA_PlayMusic(Resource::get()->getMusic("playing.ogg"));
}
// Bucle para el juego
void Game::run()
{
while (section::name == section::Name::GAME)
{
#ifndef RECORDING
checkInput();
#endif
update();
checkEvents(); // Tiene que ir antes del render
render();
}
// Vuelve a dejar el sonido como estaba
(demo_.enabled) ? JA_EnableSound(options.audio.sound.enabled) : JA_StopMusic();
}
// Indica si se puede crear una powerball
bool Game::canPowerBallBeCreated()
{
return (!power_ball_enabled_) && (calculateScreenPower() > POWERBALL_SCREENPOWER_MINIMUM) && (power_ball_counter_ == 0);
}
// Calcula el poder actual de los globos en pantalla
int Game::calculateScreenPower()
{
return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon)
{ return sum + (balloon->isEnabled() ? balloon->getPower() : 0); });
}
// Inicializa las variables que contienen puntos de ruta para mover objetos
void Game::initPaths()
{
// Vector con los valores del seno para 360 grados
float sin[360];
for (int i = 0; i < 360; ++i)
sin[i] = SDL_sinf((float)i * 3.14f / 180.0f);
// Letrero de STAGE #
constexpr auto first_part = STAGE_COUNTER_ / 4; // 50
constexpr auto second_part = first_part * 3; // 150
const auto center_point = param.game.play_area.center_y - (BLOCK * 2);
const auto distance = (param.game.play_area.rect.h) - (param.game.play_area.center_y - 16);
for (int i = 0; i < first_part; ++i)
{
int index = static_cast<int>((i * 1.8f) + 90) % 360;
stage_bitmap_path_[i] = sin[index] * distance + center_point;
}
for (int i = first_part; i < second_part; ++i)
stage_bitmap_path_[i] = center_point;
for (int i = second_part; i < STAGE_COUNTER_; ++i)
{
int index = static_cast<int>(((i - 149) * 1.8f) + 90) % 360;
stage_bitmap_path_[i] = sin[index] * (center_point + 17) - 17;
}
// Letrero de GetReady
const auto size = text_nokia2_big_->lenght(lang::getText(75), -2);
const float start1 = param.game.play_area.rect.x - size;
const float finish1 = param.game.play_area.center_x - (size / 2);
const float finish2 = param.game.play_area.rect.w;
const float distance1 = finish1 - start1;
const float distance2 = finish2 - finish1;
for (int i = 0; i < first_part; ++i)
{
get_ready_bitmap_path_[i] = sin[(int)(i * 1.8f)];
get_ready_bitmap_path_[i] *= distance1;
get_ready_bitmap_path_[i] -= size;
}
for (int i = first_part; i < second_part; ++i)
get_ready_bitmap_path_[i] = (int)finish1;
for (int i = second_part; i < STAGE_COUNTER_; ++i)
{
get_ready_bitmap_path_[i] = sin[(int)((i - second_part) * 1.8f)];
get_ready_bitmap_path_[i] *= distance2;
get_ready_bitmap_path_[i] += finish1;
}
}
// Actualiza el tramo final de juego, una vez completado
void Game::updateGameCompleted()
{
if (game_completed_)
game_completed_counter_++;
if (game_completed_counter_ == GAME_COMPLETED_END_)
{
section::name = section::Name::TITLE;
section::options = section::Options::TITLE_1;
}
}
// Actualiza las variables de ayuda
void Game::updateHelper()
{
// Solo ofrece ayuda cuando la amenaza es elevada
if (menace_current_ > 15)
{
helper_.need_coffee = true;
helper_.need_coffee_machine = true;
for (const auto &player : players_)
{
if (player->isPlaying())
{
helper_.need_coffee &= (player->getCoffees() == 0);
helper_.need_coffee_machine &= (!player->isPowerUp());
}
}
}
else
helper_.need_coffee = helper_.need_coffee_machine = false;
}
// Comprueba si todos los jugadores han terminado de jugar
bool Game::allPlayersAreWaitingOrGameOver()
{
auto success = true;
for (const auto &player : players_)
success &= player->isWaiting() || player->isGameOver();
return success;
}
// Comprueba si todos los jugadores han terminado de jugar
bool Game::allPlayersAreGameOver()
{
auto success = true;
for (const auto &player : players_)
success &= player->isGameOver();
return success;
}
// Comprueba si todos los jugadores han terminado de jugar
bool Game::allPlayersAreNotPlaying()
{
auto success = true;
for (const auto &player : players_)
success &= !player->isPlaying();
return success;
}
// Comprueba los eventos que hay en cola
void Game::checkEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
// Evento de salida de la aplicación
if (event.type == SDL_QUIT)
{
section::name = section::Name::QUIT;
section::options = section::Options::QUIT_FROM_EVENT;
break;
}
else if (event.type == SDL_WINDOWEVENT)
{
switch (event.window.event)
{
case SDL_WINDOWEVENT_FOCUS_LOST:
{
pause(!demo_.enabled);
break;
}
case SDL_WINDOWEVENT_FOCUS_GAINED:
{
pause(false);
break;
}
case SDL_WINDOWEVENT_SIZE_CHANGED:
{
reloadTextures();
break;
}
default:
break;
}
}
#ifdef DEBUG
else if (event.type == SDL_KEYDOWN && event.key.repeat == 0)
{
switch (event.key.keysym.sym)
{
case SDLK_1: // Crea una powerball
{
createPowerBall();
break;
}
case SDLK_2: // Crea dos globos gordos
{
createTwoBigBalloons();
}
break;
case SDLK_3: // Activa el modo para pasar el juego automaticamente
{
auto_pop_balloons_ = !auto_pop_balloons_;
Notifier::get()->showText("auto_pop_balloons_ " + boolToString(auto_pop_balloons_));
break;
}
case SDLK_4: // Suelta un item
{
createItem(ItemType::CLOCK, players_.at(0)->getPosX(), players_.at(0)->getPosY() - 40);
break;
}
case SDLK_5: // Crea un PathSprite
{
const auto x = players_.at(0)->getPosX();
createPathSprite(game_text_textures_.at(3));
path_sprites_.back()->addPath(220, 160, PathType::VERTICAL, x, 100, easeOutQuint, 0);
path_sprites_.back()->addPath(160, -21, PathType::VERTICAL, x, 80, easeInQuint, 0);
break;
}
default:
break;
}
}
#endif
}
}
// Recarga las texturas
void Game::reloadTextures()
{
for (auto &texture : item_textures_)
texture->reLoad();
for (auto &texture : balloon_textures_)
texture->reLoad();
for (auto &textures : player_textures_)
for (auto &texture : textures)
texture->reLoad();
for (auto &texture : game_text_textures_)
texture->reLoad();
bullet_texture_->reLoad();
background_->reloadTextures();
}
// Actualiza el marcador
void Game::updateScoreboard()
{
for (const auto &player : players_)
{
scoreboard_->setScore(player->getScoreBoardPanel(), player->getScore());
scoreboard_->setMult(player->getScoreBoardPanel(), player->getScoreMultiplier());
}
// Resto de marcador
scoreboard_->setStage(balloon_formations_->getStage(current_stage_).number);
scoreboard_->setPower((float)current_power_ / (float)balloon_formations_->getStage(current_stage_).power_to_complete);
scoreboard_->setHiScore(hi_score_.score);
scoreboard_->setHiScoreName(hi_score_.name);
// Lógica del marcador
scoreboard_->update();
}
// Pausa el juego
void Game::pause(bool value)
{
paused_ = value;
#ifndef DEBUG
screen_->attenuate(paused_);
#endif
}
// Añade una puntuación a la tabla de records
void Game::addScoreToScoreBoard(const std::string &name, int score)
{
const auto entry = HiScoreEntry(trim(name), score);
auto manager = std::make_unique<ManageHiScoreTable>(options.game.hi_score_table);
manager->add(entry);
manager->saveToFile(asset_->get("score.bin"));
}
// Saca del estado de GAME OVER al jugador si el otro está activo
void Game::checkAndUpdatePlayerStatus(int activePlayerIndex, int inactivePlayerIndex)
{
if (players_[activePlayerIndex]->isGameOver() &&
!players_[inactivePlayerIndex]->isGameOver() &&
!players_[inactivePlayerIndex]->isWaiting())
{
players_[activePlayerIndex]->setStatusPlaying(PlayerStatus::WAITING);
}
}
// Comprueba el estado de los jugadores
void Game::checkPlayersStatusPlaying()
{
if (demo_.enabled)
{
return;
}
// Comprueba si todos los jugadores estan esperando
if (allPlayersAreWaitingOrGameOver())
{
// Entonces los pone en estado de Game Over
for (auto &player : players_)
{
player->setStatusPlaying(PlayerStatus::GAME_OVER);
}
}
// Comprobar estado de ambos jugadores
checkAndUpdatePlayerStatus(0, 1);
checkAndUpdatePlayerStatus(1, 0);
}
// Obtiene un jugador a partir de su "id"
std::shared_ptr<Player> Game::getPlayer(int id)
{
auto it = std::find_if(players_.begin(), players_.end(), [id](const auto &player)
{ return player->getId() == id; });
if (it != players_.end())
{
return *it;
}
return nullptr;
}
// Obtiene un controlador a partir del "id" del jugador
int Game::getController(int player_id)
{
auto it = std::find_if(options.controller.begin(), options.controller.end(),
[player_id](const auto &controller)
{
return controller.player_id == player_id;
});
if (it != options.controller.end())
{
return std::distance(options.controller.begin(), it);
}
return -1;
}
// Gestiona la entrada durante el juego
void Game::checkInput()
{
checkPauseInput(); // Verifica si se debe pausar el juego.
if (demo_.enabled)
handleDemoMode(); // Controla el comportamiento de los jugadores en modo demo.
else
handlePlayersInput(); // Gestiona el input normal de los jugadores.
screen_->checkInput(); // Verifica el input en la pantalla del juego.
globalInputs::check(); // Verifica los inputs globales.
}
// Verifica si alguno de los controladores ha solicitado una pausa y actualiza el estado de pausa del juego.
void Game::checkPauseInput()
{
// Comprueba los mandos
for (int i = 0; i < input_->getNumControllers(); ++i)
if (input_->checkModInput(InputType::SERVICE, InputType::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT, INPUT_USE_GAMECONTROLLER, i))
{
pause(!paused_); // Alterna entre pausado y no pausado.
return;
}
// Comprueba el teclado
if (input_->checkInput(InputType::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT, INPUT_USE_KEYBOARD))
{
pause(!paused_); // Alterna entre pausado y no pausado.
return;
}
}
// Gestiona las entradas de los jugadores en el modo demo, incluyendo movimientos y disparos automáticos.
void Game::handleDemoMode()
{
int i = 0;
for (const auto &player : players_)
{
if (player->isPlaying())
handleDemoPlayerInput(player, i); // Maneja el input específico del jugador en modo demo.
if (input_->checkAnyButtonPressed())
{
section::name = section::Name::TITLE; // Salir del modo demo y regresar al menú principal.
return;
}
i++;
}
}
// Procesa las entradas para un jugador específico durante el modo demo.
void Game::handleDemoPlayerInput(const std::shared_ptr<Player> &player, int index)
{
const auto &demoData = demo_.data[index][demo_.counter];
if (demoData.left == 1)
player->setInput(InputType::LEFT);
else if (demoData.right == 1)
player->setInput(InputType::RIGHT);
else if (demoData.no_input == 1)
player->setInput(InputType::NONE);
if (demoData.fire == 1)
handleFireInput(player, BulletType::UP);
else if (demoData.fire_left == 1)
handleFireInput(player, BulletType::LEFT);
else if (demoData.fire_right == 1)
handleFireInput(player, BulletType::RIGHT);
}
// Maneja el disparo de un jugador, incluyendo la creación de balas y la gestión del tiempo de espera entre disparos.
void Game::handleFireInput(const std::shared_ptr<Player> &player, BulletType bulletType)
{
if (player->canFire())
{
player->setInput(bulletType == BulletType::UP ? InputType::FIRE_CENTER : bulletType == BulletType::LEFT ? InputType::FIRE_LEFT
: InputType::FIRE_RIGHT);
createBullet(player->getPosX() + (player->getWidth() / 2) - 6, player->getPosY() + (player->getHeight() / 2), bulletType, player->isPowerUp(), player->getId());
JA_PlaySound(Resource::get()->getSound("bullet.wav"));
player->setFireCooldown(10); // Establece un tiempo de espera para el próximo disparo.
}
}
// Gestiona las entradas de todos los jugadores en el modo normal (fuera del modo demo).
void Game::handlePlayersInput()
{
for (const auto &player : players_)
{
if (player->isPlaying())
handleNormalPlayerInput(player); // Maneja el input de los jugadores en modo normal.
else if (player->isContinue() || player->isWaiting())
handlePlayerContinue(player); // Gestiona la continuación del jugador.
else if (player->isEnteringName())
handleNameInput(player); // Gestiona la introducción del nombre del jugador.
}
}
// Maneja las entradas de movimiento y disparo para un jugador en modo normal.
void Game::handleNormalPlayerInput(const std::shared_ptr<Player> &player)
{
const auto controllerIndex = player->getController();
const bool autofire = player->isPowerUp() || options.game.autofire;
if (input_->checkInput(InputType::LEFT, INPUT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
player->setInput(InputType::LEFT);
#ifdef RECORDING
demo_.keys.left = 1;
#endif
}
else if (input_->checkInput(InputType::RIGHT, INPUT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
player->setInput(InputType::RIGHT);
#ifdef RECORDING
demo_.keys.right = 1;
#endif
}
else
{
player->setInput(InputType::NONE);
#ifdef RECORDING
demo_.keys.no_input = 1;
#endif
}
handleFireInputs(player, autofire, controllerIndex); // Verifica y maneja todas las posibles entradas de disparo.
}
// Procesa las entradas de disparo del jugador, permitiendo disparos automáticos si está habilitado.
void Game::handleFireInputs(const std::shared_ptr<Player> &player, bool autofire, int controllerIndex)
{
if (input_->checkInput(InputType::FIRE_CENTER, autofire, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
handleFireInput(player, BulletType::UP);
#ifdef RECORDING
demo_.keys.fire = 1;
#endif
}
else if (input_->checkInput(InputType::FIRE_LEFT, autofire, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
handleFireInput(player, BulletType::LEFT);
#ifdef RECORDING
demo_.keys.fire_left = 1;
#endif
}
else if (input_->checkInput(InputType::FIRE_RIGHT, autofire, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
handleFireInput(player, BulletType::RIGHT);
#ifdef RECORDING
demo_.keys.fire_right = 1;
#endif
}
}
// Maneja la continuación del jugador cuando no está jugando, permitiendo que continúe si se pulsa el botón de inicio.
void Game::handlePlayerContinue(const std::shared_ptr<Player> &player)
{
const auto controllerIndex = player->getController();
if (input_->checkInput(InputType::START, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
player->setStatusPlaying(PlayerStatus::PLAYING);
}
// Disminuye el contador de continuación si se presiona cualquier botón de disparo.
if (input_->checkInput(InputType::FIRE_LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index) ||
input_->checkInput(InputType::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index) ||
input_->checkInput(InputType::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
player->decContinueCounter();
}
}
// Procesa las entradas para la introducción del nombre del jugador.
void Game::handleNameInput(const std::shared_ptr<Player> &player)
{
const auto controllerIndex = player->getController();
if (input_->checkInput(InputType::FIRE_LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index) ||
input_->checkInput(InputType::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index) ||
input_->checkInput(InputType::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
if (player->getRecordNamePos() == 7)
{
player->setInput(InputType::START);
addScoreToScoreBoard(player->getRecordName(), player->getScore());
player->setStatusPlaying(PlayerStatus::CONTINUE);
}
else
{
player->setInput(InputType::RIGHT);
}
}
else if (input_->checkInput(InputType::UP, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
player->setInput(InputType::UP);
}
else if (input_->checkInput(InputType::DOWN, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
player->setInput(InputType::DOWN);
}
else if (input_->checkInput(InputType::LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
player->setInput(InputType::LEFT);
}
else if (input_->checkInput(InputType::RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
player->setInput(InputType::RIGHT);
}
else if (input_->checkInput(InputType::START, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
{
player->setInput(InputType::START);
addScoreToScoreBoard(player->getRecordName(), player->getScore());
player->setStatusPlaying(PlayerStatus::CONTINUE);
}
}
// Inicializa las variables para el modo DEMO
void Game::initDemo(int player_id)
{
if (demo_.enabled)
{
// Aleatoriza la asignación del fichero con los datos del modo demostracion
const auto demo1 = rand() % 2;
const auto demo2 = (demo1 == 0) ? 1 : 0;
demo_.data.emplace_back(Resource::get()->getDemoData(demo1));
demo_.data.emplace_back(Resource::get()->getDemoData(demo2));
// Selecciona una pantalla al azar
{
constexpr auto demos = 3;
const auto demo = rand() % demos;
const int stages[demos] = {0, 3, 5};
current_stage_ = stages[demo];
}
// Actualiza el numero de globos explotados según la fase del modo demostración
for (int i = 0; i < current_stage_; ++i)
{
balloons_popped_ += balloon_formations_->getStage(i).power_to_complete;
}
// Activa o no al otro jugador
if (rand() % 2 == 0)
{
const auto other_player_id = player_id == 1 ? 2 : 1;
auto other_player = getPlayer(other_player_id);
other_player->setStatusPlaying(PlayerStatus::PLAYING);
}
// Asigna cafes a los jugadores
for (auto &player : players_)
{
for (int i = 0; i < rand() % 3; ++i)
player->giveExtraHit();
player->setInvulnerable(false);
}
// Deshabilita los sonidos
JA_EnableSound(false);
// Configura los marcadores
scoreboard_->setMode(SCOREBOARD_LEFT_PANEL, ScoreboardMode::DEMO);
scoreboard_->setMode(SCOREBOARD_RIGHT_PANEL, ScoreboardMode::DEMO);
}
// Modo grabar demo
#ifdef RECORDING
demo_.recording = true;
#else
demo_.recording = false;
#endif
demo_.counter = 0;
}
// Calcula el poder total necesario para completar el juego
void Game::setTotalPower()
{
total_power_to_complete_game_ = 0;
for (int i = 0; i < 10; ++i)
{
total_power_to_complete_game_ += balloon_formations_->getStage(i).power_to_complete;
}
}
// Inicializa el marcador
void Game::initScoreboard()
{
scoreboard_->setPos({param.scoreboard.x, param.scoreboard.y, param.scoreboard.w, param.scoreboard.h});
scoreboard_->setMode(SCOREBOARD_CENTER_PANEL, ScoreboardMode::STAGE_INFO);
for (const auto &player : players_)
{
scoreboard_->setName(player->getScoreBoardPanel(), player->getName());
if (player->isWaiting())
scoreboard_->setMode(player->getScoreBoardPanel(), ScoreboardMode::WAITING);
}
}
// Inicializa las opciones relacionadas con la dificultad
void Game::initDifficultyVars()
{
// Variables relacionadas con la dificultad
switch (difficulty_)
{
case GameDifficulty::EASY:
{
default_balloon_speed_ = BALLOON_SPEED[0];
difficulty_score_multiplier_ = 0.5f;
scoreboard_->setColor(scoreboard_easy_color);
break;
}
case GameDifficulty::NORMAL:
{
default_balloon_speed_ = BALLOON_SPEED[0];
difficulty_score_multiplier_ = 1.0f;
scoreboard_->setColor(scoreboard_normal_color);
break;
}
case GameDifficulty::HARD:
{
default_balloon_speed_ = BALLOON_SPEED[4];
difficulty_score_multiplier_ = 1.5f;
scoreboard_->setColor(scoreboard_hard_color);
break;
}
default:
break;
}
balloon_speed_ = default_balloon_speed_;
}
// Inicializa los jugadores
void Game::initPlayers(int player_id)
{
// Crea los dos jugadores
const int y = param.game.play_area.rect.h - 30;
players_.emplace_back(std::make_unique<Player>(1, param.game.play_area.first_quarter_x - 15, y, demo_.enabled, param.game.play_area.rect, player_textures_[0], player_animations_));
players_.back()->setScoreBoardPanel(SCOREBOARD_LEFT_PANEL);
players_.back()->setName(lang::getText(53));
players_.back()->setController(getController(players_.back()->getId()));
players_.emplace_back(std::make_unique<Player>(2, param.game.play_area.third_quarter_x - 15, y, demo_.enabled, param.game.play_area.rect, player_textures_[1], player_animations_));
players_.back()->setScoreBoardPanel(SCOREBOARD_RIGHT_PANEL);
players_.back()->setName(lang::getText(54));
players_.back()->setController(getController(players_.back()->getId()));
// Activa el jugador que coincide con el "player_id"
auto player = getPlayer(player_id);
player->setStatusPlaying(PlayerStatus::PLAYING);
player->setInvulnerable(false);
}
// Crea dos globos gordos
void Game::createTwoBigBalloons()
{
const auto set = balloon_formations_->getStage(0).balloon_pool.set[1];
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);
}
}
// Pausa la música
void Game::pauseMusic()
{
if (JA_GetMusicState() == JA_MUSIC_PLAYING && !demo_.enabled)
JA_PauseMusic();
}
// Reanuda la música
void Game::resumeMusic()
{
if (JA_GetMusicState() == JA_MUSIC_PAUSED && !demo_.enabled)
JA_ResumeMusic();
}
// Detiene la música
void Game::stopMusic()
{
if (!demo_.enabled)
JA_StopMusic();
}