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)
2306 lines
62 KiB
C++
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, ¶m.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, ¶m.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, ¶m.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);
|
|
}
|
|
}
|