Files
coffee_crisis_arcade_edition/source/game.cpp
Sergio Valor 2cb22ed013 fix: els globos tenien un parell de setters mal asignats per culpa de buscar y reemplazar
fix: els globos verds s'inicialitzaven amb vy = 0 per gastar abs en lloc de fabs
fix: corregit un bug milenari que de sempre havia creat els balloons verds al popar al pare amb la meitat de velocitat en y. Lo que jo no se es com anava res. Supose que ara el joc serà un poc mes xungo. Quan rebotaven en el piso ja se'ls posava la velocitat bona (crec)
2024-10-20 22:58:15 +02:00

2306 lines
62 KiB
C++

#include "game.h"
#include <SDL2/SDL_blendmode.h> // for SDL_BLENDMODE_BLEND
#include <SDL2/SDL_events.h> // for SDL_PollEvent, SDL_Event, SDL_KEYDOWN
#include <SDL2/SDL_keycode.h> // for SDLK_1, SDLK_2, SDLK_3, SDLK_4
#include <SDL2/SDL_pixels.h> // for SDL_PIXELFORMAT_RGBA8888
#include <SDL2/SDL_rwops.h> // for SDL_RWFromFile, SDL_RWclose, SDL_R...
#include <SDL2/SDL_timer.h> // for SDL_GetTicks
#include <SDL2/SDL_video.h> // for SDL_WINDOWEVENT_FOCUS_GAINED, SDL_...
#include <stdlib.h> // for rand
#include <algorithm> // for min, remove_if
#include <fstream> // for basic_ostream, operator<<, basic_i...
#include <iostream> // for cout
#include <numeric> // for accumulate
#include <utility> // for move
#include "asset.h" // for Asset
#include "background.h" // for Background
#include "balloon.h" // for Balloon, BALLOON_SCORE_1, BALLOON_...
#include "balloon_formations.h" // for Stage, BalloonFormationParams, Bal...
#include "bullet.h" // for Bullet, BulletType, BulletMoveStatus
#include "explosions.h" // for Explosions
#include "fade.h" // for Fade, FadeType
#include "global_inputs.h" // for check
#include "input.h" // for InputType, Input, INPUT_DO_NOT_ALL...
#include "item.h" // for Item, ItemType::COFFEE_MACHINE, ItemType::CLOCK
#include "jail_audio.h" // for JA_PlaySound
#include "lang.h" // for getText
#include "manage_hiscore_table.h" // for ManageHiScoreTable
#include "notifier.h" // for Notifier
#include "options.h" // for options
#include "param.h" // for param
#include "player.h" // for Player, PlayerStatus
#include "resource.h" // for Resource
#include "scoreboard.h" // for Scoreboard, ScoreboardMode, SCOREB...
#include "screen.h" // for Screen
#include "section.h" // for Name, name, Options, options
#include "smart_sprite.h" // for SpriteSmart
#include "text.h" // for Text, TEXT_CENTER
#include "texture.h" // for Texture
struct JA_Music_t; // lines 35-35
struct JA_Sound_t; // lines 36-36
#include "dbgtxt.h"
// Constructor
Game::Game(int player_id, int current_stage, bool demo)
: current_stage_(current_stage)
{
// Copia los punteros
asset_ = Asset::get();
input_ = Input::get();
screen_ = Screen::get();
renderer_ = screen_->getRenderer();
// Pasa variables
demo_.enabled = demo;
last_stage_reached_ = current_stage_;
difficulty_ = options.game.difficulty;
// Crea los objetos
Scoreboard::init();
scoreboard_ = Scoreboard::get();
fade_ = std::make_unique<Fade>();
background_ = std::make_unique<Background>();
explosions_ = std::make_unique<Explosions>();
balloon_formations_ = std::make_unique<BalloonFormations>();
// Carga los recursos
loadMedia();
// Inicializa los vectores con los datos para la demo
if (demo_.enabled)
{ // Aleatoriza la asignación del fichero
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));
}
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]);
canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.play_area.rect.w, param.game.play_area.rect.h);
SDL_SetTextureBlendMode(canvas_, SDL_BLENDMODE_BLEND);
// Inicializa las variables necesarias para la sección 'Game'
init(player_id);
}
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
// Elimina todos los objetos contenidos en vectores
deleteAllVectorObjects();
// Libera los recursos
unloadMedia();
Scoreboard::destroy();
SDL_DestroyTexture(canvas_);
}
// Inicializa las variables necesarias para la sección 'Game'
void Game::init(int player_id)
{
// Elimina qualquier jugador que hubiese antes de crear los nuevos
players_.clear();
// Crea los dos jugadores
auto player1 = std::make_unique<Player>(1, (param.game.play_area.first_quarter_x * ((0 * 2) + 1)) - 11, param.game.play_area.rect.h - 30, demo_.enabled, &param.game.play_area.rect, player_textures_[0], player_animations_);
player1->setScoreBoardPanel(SCOREBOARD_LEFT_PANEL);
player1->setName(lang::getText(53));
const auto controller1 = getController(player1->getId());
player1->setController(controller1);
players_.push_back(std::move(player1));
auto player2 = std::make_unique<Player>(2, (param.game.play_area.first_quarter_x * ((1 * 2) + 1)) - 11, param.game.play_area.rect.h - 30, demo_.enabled, &param.game.play_area.rect, player_textures_[1], player_animations_);
player2->setScoreBoardPanel(SCOREBOARD_RIGHT_PANEL);
player2->setName(lang::getText(54));
const auto controller2 = getController(player2->getId());
player2->setController(controller2);
players_.push_back(std::move(player2));
// Obtiene mediante "playerID" el jugador que va a empezar jugar
auto main_player = getPlayer(player_id);
// Cambia el estado del jugador seleccionado
main_player->setStatusPlaying(PlayerStatus::PLAYING);
// Como es el principio del juego, empieza sin inmunidad
main_player->setInvulnerable(false);
// Variables relacionadas con la dificultad
switch (difficulty_)
{
case GameDifficulty::EASY:
{
default_balloon_speed_ = BALLOON_SPEED_1;
difficulty_score_multiplier_ = 0.5f;
difficulty_color_ = difficulty_easy_color;
scoreboard_->setColor(difficulty_color_);
break;
}
case GameDifficulty::NORMAL:
{
default_balloon_speed_ = BALLOON_SPEED_1;
difficulty_score_multiplier_ = 1.0f;
difficulty_color_ = difficulty_normal_color;
scoreboard_->setColor(scoreboard_color);
break;
}
case GameDifficulty::HARD:
{
default_balloon_speed_ = BALLOON_SPEED_5;
difficulty_score_multiplier_ = 1.5f;
difficulty_color_ = difficulty_hard_color;
scoreboard_->setColor(difficulty_color_);
break;
}
default:
break;
}
// Variables para el marcador
scoreboard_->setPos({param.scoreboard.x, param.scoreboard.y, param.scoreboard.w, param.scoreboard.h});
for (const auto &player : players_)
{
scoreboard_->setName(player->getScoreBoardPanel(), player->getName());
if (player->isWaiting())
{
scoreboard_->setMode(player->getScoreBoardPanel(), ScoreboardMode::WAITING);
}
}
scoreboard_->setMode(SCOREBOARD_CENTER_PANEL, ScoreboardMode::STAGE_INFO);
// Resto de variables
ticks_ = 0;
hi_score_.score = options.game.hi_score_table[0].score;
hi_score_.name = options.game.hi_score_table[0].name;
paused_ = false;
game_completed_ = false;
game_completed_counter_ = 0;
section::name = section::Name::GAME;
section::options = section::Options::GAME_PLAY_1P;
current_power_ = 0;
menace_current_ = 0;
menace_threshold_ = 0;
hi_score_achieved_ = false;
stage_bitmap_counter_ = STAGE_COUNTER_;
game_over_counter_ = GAME_OVER_COUNTER_;
time_stopped_ = false;
time_stopped_counter_ = 0;
counter_ = 0;
last_ballon_deploy_ = 0;
balloon_deploy_counter_ = 0;
balloon_speed_ = default_balloon_speed_;
helper_.need_coffee = false;
helper_.need_coffee_machine = false;
helper_.need_power_ball = false;
helper_.counter = HELP_COUNTER_;
helper_.item_disk_odds = ITEM_POINTS_1_DISK_ODDS_;
helper_.item_gavina_odds = ITEM_POINTS_2_GAVINA_ODDS_;
helper_.item_pacmar_odds = ITEM_POINTS_3_PACMAR_ODDS_;
helper_.item_clock_odds = ITEM_CLOCK_ODDS_;
helper_.item_coffee_odds = ITEM_COFFEE_ODDS_;
helper_.item_coffee_machine_odds = ITEM_COFFEE_MACHINE_ODDS_;
power_ball_enabled_ = false;
power_ball_counter_ = 0;
coffee_machine_enabled_ = false;
balloons_popped_ = 0;
#ifdef DEBUG
auto_pop_balloons_ = false;
#endif
// Inicializa las variables para el modo DEMO
if (demo_.enabled)
{
// 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 de la demo
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);
}
for (auto &player : players_)
{
// Añade 0, 1 o 2 cafes al jugador
for (int i = 0; i < rand() % 3; ++i)
{
player->giveExtraHit();
}
// Empieza sin inmunidad
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);
}
initPaths();
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;
}
// Modo grabar demo
#ifdef RECORDING
demo_.recording = true;
#else
demo_.recording = false;
#endif
demo_.counter = 0;
// Inicializa el objeto para el fundido
fade_->setColor(fade_color.r, fade_color.g, fade_color.b);
fade_->setPost(param.fade.post_duration);
fade_->setType(FadeType::VENETIAN);
// Con los globos creados, calcula el nivel de amenaza
evaluateAndSetMenace();
// Inicializa los sprites con los textos que aparecen al coger items
smart_sprites_.clear();
}
// Carga los recursos necesarios para la sección 'Game'
void Game::loadMedia()
{
unloadMedia();
// 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"));
}
// 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"));
}
}
// Libera los recursos previamente cargados
void Game::unloadMedia()
{
// Texturas
game_text_textures_.clear();
balloon_textures_.clear();
explosions_textures_.clear();
item_textures_.clear();
player_textures_.clear();
// Animaciones
player_animations_.clear();
balloon_animations_.clear();
explosions_animations_.clear();
item_animations_.clear();
}
// 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 set = rand() % 10;
// Evita repetir la ultima formación enemiga desplegada
if (set == last_ballon_deploy_)
{
++set %= 10;
}
last_ballon_deploy_ = set;
const Stage stage = balloon_formations_->getStage(current_stage_);
const auto numEnemies = stage.balloon_pool->set[set]->number_of_balloons;
for (int i = 0; i < numEnemies; ++i)
{
createBalloon(stage.balloon_pool->set[set]->init[i].x,
stage.balloon_pool->set[set]->init[i].y,
stage.balloon_pool->set[set]->init[i].kind,
stage.balloon_pool->set[set]->init[i].vel_x,
balloon_speed_,
stage.balloon_pool->set[set]->init[i].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, int kind, float velx, float speed, int creation_timer)
{
const auto index = (kind - 1) % 4;
balloons_.emplace_back(std::make_shared<Balloon>(x, y, kind, velx, speed, creation_timer, balloon_textures_[index], balloon_animations_[index]));
return balloons_.back();
}
// Crea una PowerBall
void Game::createPowerBall()
{
constexpr auto values = 6;
constexpr auto posY = -BLOCK;
const auto left = param.game.play_area.rect.x;
const auto center = param.game.play_area.center_x - (BALLOON_WIDTH_4 / 2);
const auto right = param.game.play_area.rect.w - BALLOON_WIDTH_4;
const auto vel_pos = BALLOON_VELX_POSITIVE;
const auto vel_neg = BALLOON_VELX_NEGATIVE;
const auto luck = rand() % values;
const int x[values] = {left, left, center, center, right, right};
const float vx[values] = {vel_pos, vel_pos, vel_pos, vel_neg, vel_neg, vel_neg};
auto b = std::make_unique<Balloon>(x[luck], posY, POWER_BALL, vx[luck], balloon_speed_, 300, balloon_textures_[4], balloon_animations_[4]);
balloons_.push_back(std::move(b));
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 = (float)current_power_ / (float)balloon_formations_->getStage(current_stage_).power_to_complete;
float old_balloon_speed = balloon_speed_;
// Comprueba si se ha de modificar la velocidad de los globos
if (balloon_speed_ == BALLOON_SPEED_1 && percent > 0.2f)
{
balloon_speed_ = BALLOON_SPEED_2;
}
else if (balloon_speed_ == BALLOON_SPEED_2 && percent > 0.4f)
{
balloon_speed_ = BALLOON_SPEED_3;
}
else if (balloon_speed_ == BALLOON_SPEED_3 && percent > 0.6f)
{
balloon_speed_ = BALLOON_SPEED_4;
}
else if (balloon_speed_ == BALLOON_SPEED_4 && percent > 0.8f)
{
balloon_speed_ = BALLOON_SPEED_5;
}
// Si ha habido cambio, se aplica a todos los globos
if (old_balloon_speed != balloon_speed_)
{
setBalloonSpeed(balloon_speed_);
}
}
// Explosiona un globo. Lo destruye y crea otros dos si es el caso
void Game::popBalloon(std::shared_ptr<Balloon> balloon)
{
// Aumenta el poder de la fase
increaseStageCurrentPower(1);
balloons_popped_++;
const auto kind = balloon->getKind();
if (kind == POWER_BALL)
{
destroyAllBalloons();
power_ball_enabled_ = false;
balloon_deploy_counter_ = 20;
}
else
{
const auto size = balloon->getSize();
if (size == BALLOON_SIZE_1)
{ // Si es del tipo más pequeño, simplemente elimina el globo
explosions_->add(balloon->getPosX(), balloon->getPosY(), size);
balloon->pop();
}
else
{ // En cualquier otro caso, crea dos globos de un tipo inferior
auto balloon_left = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, BALLOON_VELX_NEGATIVE, balloon_speed_, 0);
balloon_left->allignTo(balloon->getPosX() + (balloon->getWidth() / 2));
balloon_left->setVelY(balloon_left->getClass() == BALLOON_CLASS ? -2.50f : BALLOON_VELX_NEGATIVE * 2.0f);
auto balloon_right = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, BALLOON_VELX_POSITIVE, balloon_speed_, 0);
balloon_right->allignTo(balloon->getPosX() + (balloon->getWidth() / 2));
balloon_right->setVelY(balloon_right->getClass() == BALLOON_CLASS ? -2.50f : BALLOON_VELX_NEGATIVE * 2.0f);
// Elimina el globo
explosions_->add(balloon->getPosX(), balloon->getPosY(), size);
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)
{
auto score = 0;
// Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos
const auto size = balloon->getSize();
switch (size)
{
case BALLOON_SIZE_4:
{
score = BALLOON_SCORE_4 + (2 * BALLOON_SCORE_3) + (4 * BALLOON_SCORE_2) + (8 * BALLOON_SCORE_1);
break;
}
case BALLOON_SIZE_3:
{
score = BALLOON_SCORE_3 + (2 * BALLOON_SCORE_2) + (4 * BALLOON_SCORE_1);
break;
}
case BALLOON_SIZE_2:
{
score = BALLOON_SCORE_2 + (2 * BALLOON_SCORE_1);
break;
}
case BALLOON_SIZE_1:
{
score = BALLOON_SCORE_1;
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(), size);
balloon->pop();
// Recalcula el nivel de amenaza
evaluateAndSetMenace();
}
// 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(int time)
{
for (auto &balloon : balloons_)
{
if (balloon->isEnabled())
{
balloon->setStop(true);
balloon->setStoppedTimer(time);
}
}
}
// Pone en marcha todos los globos
void Game::startAllBalloons()
{
for (auto &balloon : balloons_)
{
if ((balloon->isEnabled()) && (!balloon->isBeingCreated()))
{
balloon->setStop(false);
balloon->setStoppedTimer(0);
}
}
}
// 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();
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->incScoreMultiplier();
player->addScore(balloon->getScore() * player->getScoreMultiplier() * difficulty_score_multiplier_);
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->isEnabled())
{
if (bullet->move() == BulletMoveStatus::OUT)
{
getPlayer(bullet->getOwner())->decScoreMultiplier();
}
}
}
}
// Pinta las balas activas
void Game::renderBullets()
{
for (auto &bullet : bullets_)
{
if (bullet->isEnabled())
{
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, &(param.game.play_area.rect), 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));
// Inicializa
smart_sprites_.back()->setPos({0, 0, texture->getWidth(), texture->getHeight()});
smart_sprites_.back()->setSpriteClip(smart_sprites_.back()->getPosition());
smart_sprites_.back()->setPosX(x);
smart_sprites_.back()->setPosY(y);
smart_sprites_.back()->setDestX(x);
smart_sprites_.back()->setDestY(y - 25);
smart_sprites_.back()->setVelY(-0.5f);
smart_sprites_.back()->setAccelY(-0.1f);
smart_sprites_.back()->setEnabled(true);
smart_sprites_.back()->setFinishedCounter(100);
}
// Vacia el vector de smartsprites
void Game::freeSpriteSmarts()
{
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);
}
}
}
}
// 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 SpriteSmarts
void Game::updateSpriteSmarts()
{
for (auto &ss : smart_sprites_)
{
ss->update();
}
}
// Pinta los SpriteSmarts activos
void Game::renderSpriteSmarts()
{
for (auto &ss : smart_sprites_)
{
ss->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
if (!demo_.enabled)
{
JA_PauseMusic();
}
stopAllBalloons(10);
JA_PlaySound(Resource::get()->getSound("player_collision.wav"));
screen_->shake();
JA_PlaySound(Resource::get()->getSound("coffeeout.wav"));
player->setStatusPlaying(PlayerStatus::DYING);
if (!demo_.enabled)
{
// En el modo DEMO ni se para la musica ni se añade la puntuación a la tabla
allPlayersAreNotPlaying() ? JA_StopMusic() : JA_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); });
}
// Obtiene el valor de la variable
int Game::getMenace() const
{
return menace_current_;
}
// Establece el valor de la variable
void Game::setTimeStopped(bool value)
{
time_stopped_ = value;
}
// Obtiene el valor de la variable
bool Game::isTimeStopped() const
{
return time_stopped_;
}
// Establece el valor de la variable
void Game::setTimeStoppedCounter(int value)
{
time_stopped_counter_ = value;
}
// Incrementa el valor de la variable
void Game::incTimeStoppedCounter(int value)
{
time_stopped_counter_ += value;
}
// Actualiza y comprueba el valor de la variable
void Game::updateTimeStoppedCounter()
{
if (isTimeStopped())
{
if (time_stopped_counter_ > 0)
{
time_stopped_counter_--;
stopAllBalloons(TIME_STOPPED_COUNTER_);
}
else
{
disableTimeStopItem();
}
}
}
// Actualiza la variable enemyDeployCounter
void Game::updateBalloonDeployCounter()
{
if (balloon_deploy_counter_ > 0)
{
--balloon_deploy_counter_;
}
}
// Actualiza el juego
void Game::update()
{
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
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
updateSpriteSmarts();
// Actualiza los contadores de estado y efectos
updateTimeStoppedCounter();
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();
freeSpriteSmarts();
}
// 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();
renderSpriteSmarts();
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);
}
// Time Stopped
if (time_stopped_)
{
if (time_stopped_counter_ > 100 || time_stopped_counter_ % 10 > 4)
{
text_nokia2_->writeDX(TEXT_CENTER, param.game.play_area.center_x, param.game.play_area.first_quarter_y, lang::getText(36) + std::to_string(time_stopped_counter_ / 10), -1, no_color, 1, shdw_txt_color);
}
if (time_stopped_counter_ > 100)
{
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"));
}
}
}
// 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(TIME_STOPPED_COUNTER_);
setTimeStopped(true);
incTimeStoppedCounter(TIME_STOPPED_COUNTER_);
if (JA_GetMusicState() == JA_MUSIC_PLAYING && !demo_.enabled)
{
JA_PauseMusic();
}
}
// Deshabilita el efecto del item de detener el tiempo
void Game::disableTimeStopItem()
{
time_stopped_ = false;
setTimeStoppedCounter(0);
startAllBalloons();
if (JA_GetMusicState() == JA_MUSIC_PAUSED && !demo_.enabled)
{
JA_ResumeMusic();
}
}
// 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)
{
stage_bitmap_path_[i] = (sin[(int)((i * 1.8f) + 90)] * (distance) + center_point);
}
for (int i = first_part; i < second_part; ++i)
{
stage_bitmap_path_[i] = (int)center_point;
}
for (int i = second_part; i < STAGE_COUNTER_; ++i)
{
stage_bitmap_path_[i] = (sin[(int)(((i - 149) * 1.8f) + 90)] * (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:
{
if (!demo_.enabled)
{
pause(true);
}
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)
{
switch (event.key.keysym.sym)
{
// Crea una powerball
case SDLK_1:
{
createPowerBall();
break;
}
// Crea dos BALLON4
case SDLK_2:
{
const auto set = 0;
const auto stage = balloon_formations_->getStage(0);
const auto numEnemies = stage.balloon_pool->set[set]->number_of_balloons;
for (int i = 0; i < numEnemies; ++i)
{
createBalloon(stage.balloon_pool->set[set]->init[i].x,
stage.balloon_pool->set[set]->init[i].y,
stage.balloon_pool->set[set]->init[i].kind,
stage.balloon_pool->set[set]->init[i].vel_x,
balloon_speed_,
stage.balloon_pool->set[set]->init[i].creation_counter);
}
}
break;
// Activa el modo para pasar el juego automaticamente
case SDLK_3:
{
auto_pop_balloons_ = !auto_pop_balloons_;
Notifier::get()->showText("auto_pop_balloons_ " + boolToString(auto_pop_balloons_));
break;
}
default:
break;
}
}
#endif
}
}
// Elimina todos los objetos contenidos en vectores
void Game::deleteAllVectorObjects()
{
players_.clear();
balloons_.clear();
bullets_.clear();
items_.clear();
smart_sprites_.clear();
}
// 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;
screen_->attenuate(paused_);
}
// 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)
{
for (const auto &player : players_)
{
if (player->getId() == id)
{
return player;
}
}
return nullptr;
}
// Obtiene un controlador a partir del "id" del jugador
int Game::getController(int player_id)
{
for (int i = 0; i < (int)options.controller.size(); ++i)
{
if (options.controller[i].player_id == player_id)
{
return i;
}
}
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()
{
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;
}
}
}
// Gestiona las entradas de los jugadores en el modo demo, incluyendo movimientos y disparos automáticos.
void Game::handleDemoMode()
{
int i = 0;
for (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.
// Incluye movimientos (izquierda, derecha, sin movimiento) y disparos automáticos.
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) - 4, 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 (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);
}
}