1902 lines
68 KiB
C++
1902 lines
68 KiB
C++
#include "game.h"
|
|
|
|
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_SetRenderTarget, SDL_EventType, SDL_CreateTexture, SDL_Delay, SDL_DestroyTexture, SDL_Event, SDL_GetRenderTarget, SDL_PollEvent, SDL_RenderTexture, SDL_SetTextureBlendMode, SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, SDLK_7, SDLK_8, SDLK_9, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_Point, SDL_TextureAccess
|
|
|
|
#include <algorithm> // Para max, find_if, clamp, find, min
|
|
#include <array> // Para array
|
|
#include <cstdlib> // Para rand, size_t
|
|
#include <functional> // Para function
|
|
#include <iterator> // Para distance, size
|
|
|
|
#include "asset.h" // Para Asset
|
|
#include "audio.h" // Para Audio
|
|
#include "background.h" // Para Background
|
|
#include "balloon.h" // Para Balloon, BALLOON_SPEED
|
|
#include "balloon_manager.h" // Para BalloonManager
|
|
#include "bullet.h" // Para Bullet, BulletType, BulletMoveStatus
|
|
#include "color.h" // Para Color, FLASH_COLOR
|
|
#include "difficulty.h" // Para Code
|
|
#include "fade.h" // Para Fade, FadeType, FadeMode
|
|
#include "global_events.h" // Para check
|
|
#include "global_inputs.h" // Para check
|
|
#include "input.h" // Para InputAction, Input, INPUT_DO_NOT_ALLOW_REPEAT, INPUT_ALLOW_REPEAT, InputDevice
|
|
#include "item.h" // Para Item, ItemType
|
|
#include "lang.h" // Para getText
|
|
#include "manage_hiscore_table.h" // Para ManageHiScoreTable, HiScoreEntry
|
|
#include "param.h" // Para Param, param, ParamGame, ParamScoreboard, ParamFade, ParamBalloon
|
|
#include "path_sprite.h" // Para Path, PathSprite, createPath, PathType
|
|
#include "player.h" // Para Player, PlayerState
|
|
#include "resource.h" // Para Resource
|
|
#include "scoreboard.h" // Para Scoreboard, ScoreboardMode, SCOREBOARD_LEFT_PANEL, SCOREBOARD_RIGHT_PANEL, SCOREBOARD_CENTER_PANEL
|
|
#include "screen.h" // Para Screen
|
|
#include "section.hpp" // Para Name, name, AttractMode, Options, attract_mode, options
|
|
#include "smart_sprite.h" // Para SmartSprite
|
|
#include "stage.h" // Para number, Stage, get, total_power, power, addPower, init, power_can_be_added, stages
|
|
#include "tabe.h" // Para Tabe, TabeState
|
|
#include "text.h" // Para Text
|
|
#include "texture.h" // Para Texture
|
|
#include "ui/notifier.h" // Para Notifier
|
|
#include "ui/service_menu.h" // Para ServiceMenu
|
|
|
|
// Constructor
|
|
Game::Game(int player_id, int current_stage, bool demo)
|
|
: renderer_(Screen::get()->getRenderer()),
|
|
screen_(Screen::get()),
|
|
input_(Input::get()),
|
|
background_(std::make_unique<Background>()),
|
|
canvas_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.play_area.rect.w, param.game.play_area.rect.h)),
|
|
fade_in_(std::make_unique<Fade>()),
|
|
fade_out_(std::make_unique<Fade>()),
|
|
balloon_manager_(std::make_unique<BalloonManager>()),
|
|
tabe_(std::make_unique<Tabe>()) {
|
|
// Pasa variables
|
|
demo_.enabled = demo;
|
|
|
|
// Otras variables
|
|
Section::name = Section::Name::GAME;
|
|
Section::options = Section::Options::NONE;
|
|
Stage::init();
|
|
Stage::number = current_stage;
|
|
|
|
// Asigna texturas y animaciones
|
|
setResources();
|
|
|
|
// Crea y configura los objetos
|
|
Scoreboard::init();
|
|
scoreboard_ = Scoreboard::get();
|
|
|
|
fade_in_->setColor(param.fade.color);
|
|
fade_in_->setPreDuration(demo_.enabled ? 80 : 0);
|
|
fade_in_->setPostDuration(0);
|
|
fade_in_->setType(FadeType::RANDOM_SQUARE);
|
|
fade_in_->setMode(FadeMode::IN);
|
|
fade_in_->activate();
|
|
|
|
fade_out_->setColor(param.fade.color);
|
|
fade_out_->setPostDuration(param.fade.post_duration);
|
|
fade_out_->setType(FadeType::VENETIAN);
|
|
|
|
background_->setPos(param.game.play_area.rect);
|
|
|
|
balloon_manager_->setBouncingSounds(param.balloon.bouncing_sound);
|
|
|
|
SDL_SetTextureBlendMode(canvas_, SDL_BLENDMODE_BLEND);
|
|
|
|
// Inicializa el resto de variables
|
|
initPlayers(player_id);
|
|
initScoreboard();
|
|
initDifficultyVars();
|
|
initDemo(player_id);
|
|
initPaths();
|
|
setTotalPower();
|
|
|
|
#ifdef _DEBUG
|
|
// Si se empieza en una fase que no es la primera
|
|
if (!demo_.enabled) {
|
|
for (int i = 0; i < Stage::number; ++i) {
|
|
Stage::total_power += Stage::get(i).power_to_complete;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Game::~Game() {
|
|
// [Modo JUEGO] Guarda puntuaciones y transita a modo título
|
|
if (!demo_.enabled) {
|
|
auto manager = std::make_unique<ManageHiScoreTable>(Options::settings.hi_score_table);
|
|
manager->saveToFile(Asset::get()->get("score.bin"));
|
|
Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO;
|
|
Audio::get()->stopMusic();
|
|
}
|
|
|
|
#ifdef RECORDING
|
|
saveDemoFile(Asset::get()->get("demo1.bin"), demo_.data.at(0));
|
|
#endif
|
|
|
|
Scoreboard::destroy();
|
|
SDL_DestroyTexture(canvas_);
|
|
}
|
|
|
|
// Asigna texturas y animaciones
|
|
void Game::setResources() {
|
|
// Texturas - Game_text
|
|
{
|
|
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_1000_points"));
|
|
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_2500_points"));
|
|
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_5000_points"));
|
|
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_powerup"));
|
|
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_one_hit"));
|
|
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_stop"));
|
|
game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_100000_points"));
|
|
}
|
|
|
|
// 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_debian.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 -- 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_debian.ani"));
|
|
item_animations_.emplace_back(Resource::get()->getAnimation("item_coffee_machine.ani"));
|
|
}
|
|
}
|
|
|
|
// Actualiza el valor de hiScore en caso necesario
|
|
void Game::updateHiScore() {
|
|
hi_score_.name = Options::settings.hi_score_table.front().name;
|
|
|
|
// 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();
|
|
hi_score_.name.clear();
|
|
|
|
// Si se supera la máxima puntuación emite sonido
|
|
if (!hi_score_achieved_) {
|
|
hi_score_achieved_ = true;
|
|
playSound("hi_score_achieved.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
|
|
auto balloon = checkPlayerBalloonCollision(player);
|
|
|
|
// Si hay colisión
|
|
if (balloon) {
|
|
// Si el globo está parado y el temporizador activo, lo explota
|
|
if (balloon->isStopped() && time_stopped_counter_ > 0) {
|
|
balloon_manager_->popBalloon(balloon);
|
|
}
|
|
// En caso contrario, el jugador ha sido golpeado por un globo activo
|
|
else {
|
|
handlePlayerCollision(player);
|
|
|
|
if (demo_.enabled && allPlayersAreNotPlaying()) {
|
|
fade_out_->setType(FadeType::RANDOM_SQUARE);
|
|
fade_out_->activate();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Comprueba las colisiones entre el jugador y los items
|
|
checkPlayerItemCollision(player);
|
|
}
|
|
}
|
|
|
|
// Organiza la lista de jugadores
|
|
movePlayersToFront();
|
|
}
|
|
|
|
// Dibuja a los jugadores
|
|
void Game::renderPlayers() {
|
|
for (auto &player : players_) {
|
|
if (!player->isWaiting()) {
|
|
player->render();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Comprueba si hay cambio de fase y actualiza las variables
|
|
void Game::updateStage() {
|
|
if (Stage::power >= Stage::get(Stage::number).power_to_complete) {
|
|
// Cambio de fase
|
|
Stage::power = Stage::get(Stage::number).power_to_complete - Stage::power;
|
|
++Stage::number;
|
|
playSound("stage_change.wav");
|
|
balloon_manager_->resetBalloonSpeed();
|
|
screen_->flash(FLASH_COLOR, 3);
|
|
screen_->shake();
|
|
|
|
// Escribe el texto por pantalla
|
|
if (Stage::number < 10) {
|
|
std::vector<Path> paths = {paths_.at(2), paths_.at(3)};
|
|
if (Stage::number == 9) {
|
|
createMessage(paths, Resource::get()->getTexture("game_text_last_stage"));
|
|
} else {
|
|
auto text = Resource::get()->getText("04b_25_2x");
|
|
const std::string CAPTION = std::to_string(10 - Stage::number) + Lang::getText("[GAME_TEXT] 2");
|
|
createMessage(paths, text->writeToTexture(CAPTION, 1, -4));
|
|
}
|
|
}
|
|
|
|
// Modifica el color de fondo al llegar a la Fase 10
|
|
if (Stage::number == 9) {
|
|
background_->setColor(Color(0xdd, 0x19, 0x1d).DARKEN());
|
|
background_->setAlpha(96);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Actualiza el estado de fin de la partida
|
|
void Game::updateGameStateGameOver() {
|
|
fade_out_->update();
|
|
updatePlayers();
|
|
updateScoreboard();
|
|
updateBackground();
|
|
balloon_manager_->update();
|
|
tabe_->update();
|
|
updateBullets();
|
|
updateItems();
|
|
updateSmartSprites();
|
|
updatePathSprites();
|
|
updateTimeStopped();
|
|
checkBulletCollision();
|
|
cleanVectors();
|
|
|
|
if (game_over_counter_ > 0) {
|
|
if (game_over_counter_ == GAME_OVER_COUNTER) {
|
|
createMessage({paths_.at(2), paths_.at(3)}, Resource::get()->getTexture("game_text_game_over"));
|
|
Audio::get()->fadeOutMusic(1000);
|
|
balloon_manager_->setBouncingSounds(true);
|
|
}
|
|
|
|
game_over_counter_--;
|
|
|
|
if (game_over_counter_ == 150) {
|
|
fade_out_->activate();
|
|
}
|
|
}
|
|
|
|
if (fade_out_->isEnabled()) {
|
|
if (Options::audio.enabled) {
|
|
const float VOL = static_cast<float>(64 * (100 - fade_out_->getValue())) / 100.0F;
|
|
Audio::get()->setSoundVolume(static_cast<int>(VOL), Audio::Group::GAME);
|
|
}
|
|
}
|
|
|
|
if (fade_out_->hasEnded()) {
|
|
if (game_completed_counter_ > 0) {
|
|
// Los jugadores han completado el juego
|
|
Section::name = Section::Name::CREDITS;
|
|
} else {
|
|
// La partida ha terminado con la derrota de los jugadores
|
|
Section::name = Section::Name::HI_SCORE_TABLE;
|
|
}
|
|
Section::options = Section::Options::HI_SCORE_AFTER_PLAYING;
|
|
if (Options::audio.enabled) {
|
|
Audio::get()->stopAllSounds();
|
|
Audio::get()->setSoundVolume(Options::audio.sound.volume, Audio::Group::GAME);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gestiona eventos para el estado del final del juego
|
|
void Game::updateGameStateCompleted() {
|
|
constexpr int START_CELEBRATIONS = 400;
|
|
constexpr int END_CELEBRATIONS = START_CELEBRATIONS + 300;
|
|
|
|
updatePlayers();
|
|
updateScoreboard();
|
|
updateBackground();
|
|
balloon_manager_->update();
|
|
tabe_->update();
|
|
updateBullets();
|
|
updateItems();
|
|
updateSmartSprites();
|
|
updatePathSprites();
|
|
cleanVectors();
|
|
|
|
// Para la música y elimina todos los globos e items
|
|
if (game_completed_counter_ == 0) {
|
|
stopMusic();
|
|
Stage::number = 9; // Deja el valor dentro de los limites
|
|
balloon_manager_->destroyAllBalloons(); // Destruye a todos los globos
|
|
playSound("power_ball_explosion.wav");
|
|
destroyAllItems(); // Destruye todos los items
|
|
Stage::power = 0; // Vuelve a dejar el poder a cero, por lo que hubiera podido subir al destruir todos los globos
|
|
background_->setAlpha(0); // Elimina el tono rojo de las últimas pantallas
|
|
}
|
|
|
|
// Comienza las celebraciones
|
|
// Muestra el mensaje de felicitación y da los puntos a los jugadores
|
|
if (game_completed_counter_ == START_CELEBRATIONS) {
|
|
createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("game_text_congratulations"));
|
|
createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("game_text_1000000_points"));
|
|
|
|
for (auto &player : players_) {
|
|
if (player->isPlaying()) {
|
|
player->addScore(1000000);
|
|
player->setPlayingState(PlayerState::CELEBRATING);
|
|
} else {
|
|
player->setPlayingState(PlayerState::GAME_OVER);
|
|
}
|
|
}
|
|
|
|
updateHiScore();
|
|
}
|
|
|
|
// Termina las celebraciones
|
|
if (game_completed_counter_ == END_CELEBRATIONS) {
|
|
for (auto &player : players_) {
|
|
if (player->isCelebrating()) {
|
|
player->setPlayingState(player->isEligibleForHighScore() ? PlayerState::ENTERING_NAME_GAME_COMPLETED : PlayerState::LEAVING_SCREEN);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Si los jugadores ya no estan y no quedan mensajes en pantalla
|
|
if (allPlayersAreGameOver() && path_sprites_.empty()) {
|
|
setState(GameState::GAME_OVER);
|
|
}
|
|
|
|
// Incrementa el contador al final
|
|
++game_completed_counter_;
|
|
}
|
|
|
|
// Comprueba el estado del juego
|
|
void Game::checkState() {
|
|
if (state_ != GameState::COMPLETED && Stage::number == 10) {
|
|
setState(GameState::COMPLETED);
|
|
}
|
|
|
|
if (state_ != GameState::GAME_OVER && allPlayersAreGameOver()) {
|
|
setState(GameState::GAME_OVER);
|
|
}
|
|
}
|
|
|
|
// Destruye todos los items
|
|
void Game::destroyAllItems() {
|
|
for (auto &item : items_) {
|
|
item->disable();
|
|
}
|
|
}
|
|
|
|
// Comprueba la colisión entre el jugador y los globos activos
|
|
auto Game::checkPlayerBalloonCollision(std::shared_ptr<Player> &player) -> std::shared_ptr<Balloon> {
|
|
for (auto &balloon : balloon_manager_->getBalloons()) {
|
|
if (!balloon->isInvulnerable() && !balloon->isPowerBall()) {
|
|
if (checkCollision(player->getCollider(), balloon->getCollider())) {
|
|
return balloon; // Devuelve el globo con el que se ha producido la colisión
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr; // No se ha producido ninguna colisión
|
|
}
|
|
|
|
// 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);
|
|
const auto X = item->getPosX() + (item->getWidth() - game_text_textures_.at(0)->getWidth()) / 2;
|
|
createItemText(X, game_text_textures_.at(0));
|
|
playSound("item_pickup.wav");
|
|
break;
|
|
}
|
|
case ItemType::GAVINA: {
|
|
player->addScore(2500);
|
|
const auto X = item->getPosX() + (item->getWidth() - game_text_textures_.at(1)->getWidth()) / 2;
|
|
createItemText(X, game_text_textures_.at(1));
|
|
playSound("item_pickup.wav");
|
|
break;
|
|
}
|
|
case ItemType::PACMAR: {
|
|
player->addScore(5000);
|
|
const auto X = item->getPosX() + (item->getWidth() - game_text_textures_.at(2)->getWidth()) / 2;
|
|
createItemText(X, game_text_textures_.at(2));
|
|
playSound("item_pickup.wav");
|
|
break;
|
|
}
|
|
case ItemType::DEBIAN: {
|
|
player->addScore(100000);
|
|
const auto X = item->getPosX() + (item->getWidth() - game_text_textures_.at(6)->getWidth()) / 2;
|
|
createItemText(X, game_text_textures_.at(6));
|
|
playSound("debian_pickup.wav");
|
|
break;
|
|
}
|
|
case ItemType::CLOCK: {
|
|
enableTimeStopItem();
|
|
const auto X = item->getPosX() + (item->getWidth() - game_text_textures_.at(5)->getWidth()) / 2;
|
|
createItemText(X, game_text_textures_.at(5));
|
|
playSound("item_pickup.wav");
|
|
break;
|
|
}
|
|
case ItemType::COFFEE: {
|
|
if (player->getCoffees() == 2) {
|
|
player->addScore(5000);
|
|
const auto X = item->getPosX() + (item->getWidth() - game_text_textures_.at(2)->getWidth()) / 2;
|
|
createItemText(X, game_text_textures_.at(2));
|
|
} else {
|
|
player->giveExtraHit();
|
|
const auto X = item->getPosX() + (item->getWidth() - game_text_textures_.at(4)->getWidth()) / 2;
|
|
createItemText(X, game_text_textures_.at(4));
|
|
}
|
|
playSound("voice_coffee.wav");
|
|
break;
|
|
}
|
|
case ItemType::COFFEE_MACHINE: {
|
|
player->setPowerUp();
|
|
coffee_machine_enabled_ = false;
|
|
const auto X = item->getPosX() + (item->getWidth() - game_text_textures_.at(3)->getWidth()) / 2;
|
|
createItemText(X, game_text_textures_.at(3));
|
|
playSound("voice_power_up.wav");
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
updateHiScore();
|
|
item->disable();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Comprueba y procesa la colisión de las balas
|
|
void Game::checkBulletCollision() {
|
|
for (auto &bullet : bullets_) {
|
|
if (!bullet->isEnabled()) {
|
|
continue;
|
|
}
|
|
|
|
if (checkBulletTabeCollision(bullet)) {
|
|
break; // Exit early if bullet hit Tabe
|
|
}
|
|
|
|
if (checkBulletBalloonCollision(bullet)) {
|
|
break; // Exit early if bullet hit balloon
|
|
}
|
|
}
|
|
}
|
|
|
|
// Maneja la colisión entre bala y Tabe
|
|
auto Game::checkBulletTabeCollision(std::shared_ptr<Bullet> bullet) -> bool {
|
|
if (!tabe_->isEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
if (!checkCollision(bullet->getCollider(), tabe_->getCollider())) {
|
|
return false;
|
|
}
|
|
|
|
tabe_->setState(TabeState::HIT);
|
|
bullet->disable();
|
|
|
|
handleTabeHitEffects();
|
|
return true;
|
|
}
|
|
|
|
// Maneja los efectos de golpear al Tabe
|
|
void Game::handleTabeHitEffects() {
|
|
auto pos = tabe_->getCollider();
|
|
|
|
if (tabe_->tryToGetBonus()) {
|
|
createItem(ItemType::DEBIAN, pos.x, pos.y);
|
|
playSound("debian_drop.wav");
|
|
} else {
|
|
if (rand() % 3 == 0) {
|
|
createItem(ItemType::COFFEE, pos.x, pos.y);
|
|
}
|
|
playSound("tabe_hit.wav");
|
|
}
|
|
}
|
|
|
|
// Maneja la colisión entre bala y globos
|
|
auto Game::checkBulletBalloonCollision(std::shared_ptr<Bullet> bullet) -> bool {
|
|
for (auto &balloon : balloon_manager_->getBalloons()) {
|
|
if (!balloon->isEnabled() || balloon->isInvulnerable()) {
|
|
continue;
|
|
}
|
|
|
|
if (!checkCollision(balloon->getCollider(), bullet->getCollider())) {
|
|
continue;
|
|
}
|
|
|
|
processBalloonHit(bullet, balloon);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Procesa el impacto en un globo
|
|
void Game::processBalloonHit(std::shared_ptr<Bullet> bullet, std::shared_ptr<Balloon> balloon) {
|
|
auto player = getPlayer(bullet->getOwner());
|
|
|
|
handleItemDrop(balloon, player);
|
|
handleBalloonDestruction(balloon, player);
|
|
|
|
bullet->disable();
|
|
}
|
|
|
|
// Maneja la caída de items cuando se destruye un globo
|
|
void Game::handleItemDrop(std::shared_ptr<Balloon> balloon, std::shared_ptr<Player> player) {
|
|
const auto DROPPED_ITEM = dropItem();
|
|
if (DROPPED_ITEM == ItemType::NONE || demo_.recording) {
|
|
return;
|
|
}
|
|
|
|
if (DROPPED_ITEM != ItemType::COFFEE_MACHINE) {
|
|
createItem(DROPPED_ITEM, balloon->getPosX(), balloon->getPosY());
|
|
} else {
|
|
createItem(DROPPED_ITEM, player->getPosX(), param.game.game_area.rect.y - Item::COFFEE_MACHINE_HEIGHT);
|
|
coffee_machine_enabled_ = true;
|
|
}
|
|
}
|
|
|
|
// Maneja la destrucción del globo y puntuación
|
|
void Game::handleBalloonDestruction(std::shared_ptr<Balloon> balloon, std::shared_ptr<Player> player) {
|
|
const auto SCORE = balloon_manager_->popBalloon(balloon);
|
|
evaluateAndSetMenace();
|
|
|
|
if (player->isPlaying()) {
|
|
player->addScore(SCORE * player->getScoreMultiplier() * difficulty_score_multiplier_);
|
|
player->incScoreMultiplier();
|
|
}
|
|
updateHiScore();
|
|
}
|
|
|
|
// Mueve las balas activas
|
|
void Game::updateBullets() {
|
|
for (auto &bullet : bullets_) {
|
|
if (bullet->update() == BulletMoveStatus::OUT) {
|
|
getPlayer(bullet->getOwner())->decScoreMultiplier();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pinta las balas activas
|
|
void Game::renderBullets() {
|
|
for (auto &bullet : bullets_) {
|
|
bullet->render();
|
|
}
|
|
}
|
|
|
|
// Crea un objeto bala
|
|
void Game::createBullet(int x, int y, BulletType kind, bool powered_up, int owner) {
|
|
bullets_.emplace_back(std::make_shared<Bullet>(x, y, kind, powered_up, owner));
|
|
}
|
|
|
|
// 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()) {
|
|
playSound("title.wav");
|
|
screen_->shake(1, 2, 4);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pinta los items activos
|
|
void Game::renderItems() {
|
|
for (auto &item : items_) {
|
|
item->render();
|
|
}
|
|
}
|
|
|
|
// Devuelve un item al azar y luego segun sus probabilidades
|
|
auto Game::dropItem() -> ItemType {
|
|
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]));
|
|
playSound("item_drop.wav");
|
|
}
|
|
|
|
// 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 PathSprite
|
|
void Game::createItemText(int x, std::shared_ptr<Texture> texture) {
|
|
path_sprites_.emplace_back(std::make_unique<PathSprite>(texture));
|
|
|
|
const auto W = texture->getWidth();
|
|
const auto H = texture->getHeight();
|
|
|
|
const int Y0 = param.game.play_area.rect.h - H;
|
|
const int Y1 = 160 - (H / 2);
|
|
const int Y2 = -H;
|
|
|
|
// Ajusta para que no se dibuje fuera de pantalla
|
|
x = std::clamp(x, 2, static_cast<int>(param.game.play_area.rect.w) - W - 2);
|
|
|
|
// Inicializa
|
|
path_sprites_.back()->setWidth(W);
|
|
path_sprites_.back()->setHeight(H);
|
|
path_sprites_.back()->setSpriteClip({0, 0, static_cast<float>(W), static_cast<float>(H)});
|
|
path_sprites_.back()->addPath(Y0, Y1, PathType::VERTICAL, x, 100, easeOutQuint, 0);
|
|
path_sprites_.back()->addPath(Y1, Y2, PathType::VERTICAL, x, 80, easeInQuint, 0);
|
|
path_sprites_.back()->enable();
|
|
}
|
|
|
|
// Crea un objeto PathSprite
|
|
void Game::createMessage(const std::vector<Path> &paths, std::shared_ptr<Texture> texture) {
|
|
path_sprites_.emplace_back(std::make_unique<PathSprite>(texture));
|
|
|
|
// Inicializa
|
|
for (const auto &path : paths) {
|
|
path_sprites_.back()->addPath(path, true);
|
|
}
|
|
path_sprites_.back()->enable();
|
|
}
|
|
|
|
// Vacia el vector de smartsprites
|
|
void Game::freeSmartSprites() {
|
|
if (!smart_sprites_.empty()) {
|
|
for (int i = smart_sprites_.size() - 1; i >= 0; --i) {
|
|
if (smart_sprites_[i]->hasFinished()) {
|
|
smart_sprites_.erase(smart_sprites_.begin() + i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Vacia el vector de pathsprites
|
|
void Game::freePathSprites() {
|
|
if (!path_sprites_.empty()) {
|
|
for (int i = path_sprites_.size() - 1; i >= 0; --i) {
|
|
if (path_sprites_[i]->hasFinished()) {
|
|
path_sprites_.erase(path_sprites_.begin() + i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Crea un SpriteSmart para arrojar el item café al recibir un impacto
|
|
void Game::throwCoffee(int x, int y) {
|
|
smart_sprites_.emplace_back(std::make_unique<SmartSprite>(item_textures_[4]));
|
|
|
|
smart_sprites_.back()->setPosX(x - 8);
|
|
smart_sprites_.back()->setPosY(y - 8);
|
|
smart_sprites_.back()->setWidth(param.game.item_size);
|
|
smart_sprites_.back()->setHeight(param.game.item_size);
|
|
smart_sprites_.back()->setVelX(-1.0F + ((rand() % 5) * 0.5F));
|
|
smart_sprites_.back()->setVelY(-4.0F);
|
|
smart_sprites_.back()->setAccelX(0.0F);
|
|
smart_sprites_.back()->setAccelY(0.2F);
|
|
smart_sprites_.back()->setDestX(x + (smart_sprites_.back()->getVelX() * 50));
|
|
smart_sprites_.back()->setDestY(param.game.height + 1);
|
|
smart_sprites_.back()->setEnabled(true);
|
|
smart_sprites_.back()->setFinishedCounter(1);
|
|
smart_sprites_.back()->setSpriteClip(0, param.game.item_size, param.game.item_size, param.game.item_size);
|
|
smart_sprites_.back()->setRotatingCenter({param.game.item_size / 2, param.game.item_size / 2});
|
|
smart_sprites_.back()->setRotate(true);
|
|
smart_sprites_.back()->setRotateSpeed(10);
|
|
smart_sprites_.back()->setRotateAmount(90.0);
|
|
}
|
|
|
|
// Actualiza los SmartSprites
|
|
void Game::updateSmartSprites() {
|
|
for (auto &sprite : smart_sprites_) {
|
|
sprite->update();
|
|
}
|
|
}
|
|
|
|
// Pinta los SmartSprites activos
|
|
void Game::renderSmartSprites() {
|
|
for (auto &sprite : smart_sprites_) {
|
|
sprite->render();
|
|
}
|
|
}
|
|
|
|
// Actualiza los PathSprites
|
|
void Game::updatePathSprites() {
|
|
for (auto &sprite : path_sprites_) {
|
|
sprite->update();
|
|
}
|
|
}
|
|
|
|
// Pinta los PathSprites activos
|
|
void Game::renderPathSprites() {
|
|
for (auto &sprite : path_sprites_) {
|
|
sprite->render();
|
|
}
|
|
}
|
|
|
|
// Acciones a realizar cuando el jugador colisiona con un globo
|
|
void Game::handlePlayerCollision(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));
|
|
playSound("coffee_out.wav");
|
|
screen_->shake();
|
|
} else {
|
|
// Si no tiene cafes, muere
|
|
playSound("player_collision.wav");
|
|
if (param.game.hit_stop) {
|
|
SDL_Delay(param.game.hit_stop_ms);
|
|
}
|
|
screen_->shake();
|
|
playSound("voice_no.wav");
|
|
player->setPlayingState(PlayerState::ROLLING);
|
|
players_to_reorder_.push_back(player);
|
|
if (allPlayersAreNotPlaying()) {
|
|
// No se puede subir poder de fase si no hay nadie jugando
|
|
Stage::power_can_be_added = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Actualiza y comprueba el valor de la variable
|
|
void Game::updateTimeStopped() {
|
|
if (time_stopped_counter_ > 0) {
|
|
time_stopped_counter_--;
|
|
if (time_stopped_counter_ > 120) {
|
|
if (time_stopped_counter_ % 30 == 0) {
|
|
playSound("clock.wav");
|
|
}
|
|
} else {
|
|
if (time_stopped_counter_ % 30 == 0) {
|
|
balloon_manager_->normalColorsToAllBalloons();
|
|
playSound("clock.wav");
|
|
} else if (time_stopped_counter_ % 30 == 15) {
|
|
balloon_manager_->reverseColorsToAllBalloons();
|
|
playSound("clock.wav");
|
|
}
|
|
}
|
|
} else {
|
|
disableTimeStopItem();
|
|
}
|
|
}
|
|
|
|
// Actualiza el juego
|
|
void Game::update() {
|
|
if (SDL_GetTicks() - ticks_ > param.game.speed) {
|
|
ticks_ = SDL_GetTicks();
|
|
|
|
checkServiceMenu();
|
|
updateDemo();
|
|
#ifdef RECORDING
|
|
updateRecording();
|
|
#endif
|
|
if (!paused_) {
|
|
switch (state_) {
|
|
case GameState::FADE_IN:
|
|
updateGameStateFadeIn();
|
|
break;
|
|
case GameState::ENTERING_PLAYER:
|
|
updateGameStateEnteringPlayer();
|
|
break;
|
|
case GameState::SHOWING_GET_READY_MESSAGE:
|
|
updateGameStateShowingGetReadyMessage();
|
|
break;
|
|
case GameState::PLAYING:
|
|
updateGameStatePlaying();
|
|
break;
|
|
case GameState::COMPLETED:
|
|
updateGameStateCompleted();
|
|
break;
|
|
case GameState::GAME_OVER:
|
|
updateGameStateGameOver();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
screen_->update();
|
|
fillCanvas();
|
|
}
|
|
}
|
|
|
|
// Actualiza el fondo
|
|
void Game::updateBackground() {
|
|
// Si el juego está completado, se reduce la velocidad de las nubes
|
|
if (state_ == GameState::COMPLETED) {
|
|
Stage::total_power = (Stage::total_power > 200) ? (Stage::total_power - 25) : 200;
|
|
}
|
|
|
|
// Calcula la velocidad en función de los globos explotados y el total de globos a explotar para acabar el juego
|
|
constexpr float CLOUDS_INITIAL_SPEED = 0.05F;
|
|
constexpr float CLOUDS_FINAL_SPEED = 2.00F - CLOUDS_INITIAL_SPEED;
|
|
const float CLOUDS_SPEED = (-CLOUDS_INITIAL_SPEED) + (-CLOUDS_FINAL_SPEED * (static_cast<float>(Stage::total_power) / total_power_to_complete_game_));
|
|
background_->setCloudsSpeed(CLOUDS_SPEED);
|
|
|
|
// Calcula la transición de los diferentes fondos
|
|
constexpr float NUM = 1525.0F; // total_power_to_complete div 4
|
|
const float GRADIENT_NUMBER = std::min(Stage::total_power / NUM, 3.0F);
|
|
const float PERCENT = GRADIENT_NUMBER - static_cast<int>(GRADIENT_NUMBER);
|
|
background_->setGradientNumber(static_cast<int>(GRADIENT_NUMBER));
|
|
background_->setTransition(PERCENT);
|
|
|
|
// Calcula la posición del sol
|
|
constexpr float SUN_FINAL_POWER = NUM * 2;
|
|
background_->setSunProgression(Stage::total_power / SUN_FINAL_POWER);
|
|
background_->setMoonProgression(Stage::total_power / static_cast<float>(total_power_to_complete_game_));
|
|
|
|
// 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();
|
|
renderPlayers();
|
|
renderSmartSprites();
|
|
renderItems();
|
|
balloon_manager_->render();
|
|
tabe_->render();
|
|
renderBullets();
|
|
renderPathSprites();
|
|
|
|
// 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_RenderTexture(renderer_, canvas_, nullptr, ¶m.game.play_area.rect);
|
|
|
|
// Dibuja el marcador
|
|
scoreboard_->render();
|
|
|
|
// Dibuja el fade
|
|
fade_in_->render();
|
|
fade_out_->render();
|
|
|
|
// Vuelca el contenido del renderizador en pantalla
|
|
screen_->render();
|
|
}
|
|
|
|
// Habilita el efecto del item de detener el tiempo
|
|
void Game::enableTimeStopItem() {
|
|
balloon_manager_->stopAllBalloons();
|
|
balloon_manager_->reverseColorsToAllBalloons();
|
|
time_stopped_counter_ = TIME_STOPPED_COUNTER;
|
|
}
|
|
|
|
// Deshabilita el efecto del item de detener el tiempo
|
|
void Game::disableTimeStopItem() {
|
|
time_stopped_counter_ = 0;
|
|
balloon_manager_->startAllBalloons();
|
|
balloon_manager_->normalColorsToAllBalloons();
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
// Inicializa las variables que contienen puntos de ruta para mover objetos
|
|
void Game::initPaths() {
|
|
// Recorrido para el texto de "Get Ready!" (0,1)
|
|
{
|
|
const auto &texture = Resource::get()->getTexture("game_text_get_ready");
|
|
const auto W = texture->getWidth();
|
|
const int X0 = -W;
|
|
const int X1 = param.game.play_area.center_x - W / 2;
|
|
const int X2 = param.game.play_area.rect.w;
|
|
const int Y = param.game.play_area.center_y;
|
|
paths_.emplace_back(createPath(X0, X1, PathType::HORIZONTAL, Y, 80, easeOutQuint), 20);
|
|
paths_.emplace_back(createPath(X1, X2, PathType::HORIZONTAL, Y, 80, easeInQuint), 0);
|
|
}
|
|
|
|
// Recorrido para el texto de "Last Stage!" o de "X stages left" o "Game Over" (2,3)
|
|
{
|
|
const auto &texture = Resource::get()->getTexture("game_text_last_stage");
|
|
const auto H = texture->getHeight();
|
|
const int Y0 = param.game.play_area.rect.h - H;
|
|
const int Y1 = param.game.play_area.center_y - H / 2;
|
|
const int Y2 = -H;
|
|
const int X = param.game.play_area.center_x;
|
|
paths_.emplace_back(createPath(Y0, Y1, PathType::VERTICAL, X, 80, easeOutQuint), 20);
|
|
paths_.emplace_back(createPath(Y1, Y2, PathType::VERTICAL, X, 80, easeInQuint), 0);
|
|
}
|
|
|
|
// Recorrido para el texto de "Congratulations!!" (3,4)
|
|
{
|
|
const auto &texture = Resource::get()->getTexture("game_text_congratulations");
|
|
const auto W = texture->getWidth();
|
|
const auto H = texture->getHeight();
|
|
const int X0 = -W;
|
|
const int X1 = param.game.play_area.center_x - W / 2;
|
|
const int X2 = param.game.play_area.rect.w;
|
|
const int Y = param.game.play_area.center_y - H / 2 - 20;
|
|
paths_.emplace_back(createPath(X0, X1, PathType::HORIZONTAL, Y, 80, easeOutQuint), 400);
|
|
paths_.emplace_back(createPath(X1, X2, PathType::HORIZONTAL, Y, 80, easeInQuint), 0);
|
|
}
|
|
|
|
// Recorrido para el texto de "1.000.000 points!" (5,6)
|
|
{
|
|
const auto &texture = Resource::get()->getTexture("game_text_1000000_points");
|
|
const auto W = texture->getWidth();
|
|
const auto H = texture->getHeight();
|
|
const int X0 = param.game.play_area.rect.w;
|
|
const int X1 = param.game.play_area.center_x - W / 2;
|
|
const int X2 = -W;
|
|
const int Y = param.game.play_area.center_y + H / 2 - 20;
|
|
paths_.emplace_back(createPath(X0, X1, PathType::HORIZONTAL, Y, 80, easeOutQuint), 400);
|
|
paths_.emplace_back(createPath(X1, X2, PathType::HORIZONTAL, Y, 80, easeInQuint), 0);
|
|
}
|
|
}
|
|
|
|
// 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
|
|
auto Game::allPlayersAreWaitingOrGameOver() -> bool {
|
|
auto success = true;
|
|
for (const auto &player : players_) {
|
|
success &= player->isWaiting() || player->isGameOver();
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// Comprueba si todos los jugadores han terminado de jugar
|
|
auto Game::allPlayersAreGameOver() -> bool {
|
|
auto success = true;
|
|
for (const auto &player : players_) {
|
|
success &= player->isGameOver();
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// Comprueba si todos los jugadores han terminado de jugar
|
|
auto Game::allPlayersAreNotPlaying() -> bool {
|
|
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)) {
|
|
switch (event.type) {
|
|
case SDL_EVENT_WINDOW_FOCUS_LOST: {
|
|
pause(!demo_.enabled);
|
|
break;
|
|
}
|
|
case SDL_EVENT_WINDOW_FOCUS_GAINED: {
|
|
pause(false);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
checkDebugEvents(event);
|
|
#endif
|
|
GlobalEvents::check(event);
|
|
}
|
|
}
|
|
|
|
// 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(Stage::number + 1);
|
|
scoreboard_->setPower((float)Stage::power / (float)Stage::get(Stage::number).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_);
|
|
tabe_->pauseTimer(paused_);
|
|
}
|
|
|
|
// Añade una puntuación a la tabla de records
|
|
void Game::addScoreToScoreBoard(const std::shared_ptr<Player> &player) {
|
|
const auto ENTRY = HiScoreEntry(trim(player->getLastEnterName()), player->getScore(), player->get1CC());
|
|
auto manager = std::make_unique<ManageHiScoreTable>(Options::settings.hi_score_table);
|
|
Options::settings.last_hi_score_entry.at(player->getId() - 1) = manager->add(ENTRY);
|
|
manager->saveToFile(Asset::get()->get("score.bin"));
|
|
hi_score_.name = Options::settings.hi_score_table.front().name;
|
|
}
|
|
|
|
// Saca del estado de GAME OVER al jugador si el otro está activo
|
|
void Game::checkAndUpdatePlayerStatus(int active_player_index, int inactive_player_index) {
|
|
if (players_[active_player_index]->isGameOver() && !players_[inactive_player_index]->isGameOver() && !players_[inactive_player_index]->isWaiting()) {
|
|
players_[active_player_index]->setPlayingState(PlayerState::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->setPlayingState(PlayerState::GAME_OVER);
|
|
}
|
|
}
|
|
|
|
// Comprobar estado de ambos jugadores
|
|
checkAndUpdatePlayerStatus(0, 1);
|
|
checkAndUpdatePlayerStatus(1, 0);
|
|
}
|
|
|
|
// Obtiene un jugador a partir de su "id"
|
|
auto Game::getPlayer(int id) -> std::shared_ptr<Player> {
|
|
auto it = std::find_if(players_.begin(), players_.end(), [id](const auto &player) { return player->getId() == id; });
|
|
|
|
if (it != players_.end()) {
|
|
return *it;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Obtiene un controlador a partir del "id" del jugador
|
|
auto Game::getController(int player_id) -> int {
|
|
auto it = std::find_if(Options::controllers.begin(), Options::controllers.end(), [player_id](const auto &controller) { return controller.player_id == player_id; });
|
|
|
|
if (it != Options::controllers.end()) {
|
|
return std::distance(Options::controllers.begin(), it);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Gestiona la entrada durante el juego
|
|
void Game::checkInput() {
|
|
Input::get()->update();
|
|
|
|
// Comprueba las entradas si no está el menú de servicio activo
|
|
if (!ServiceMenu::get()->isEnabled()) {
|
|
checkPauseInput();
|
|
demo_.enabled ? demoHandlePassInput() : handlePlayersInput();
|
|
}
|
|
|
|
// Mueve los jugadores en el modo demo
|
|
if (demo_.enabled) {
|
|
demoHandleInput();
|
|
}
|
|
|
|
// Verifica los inputs globales.
|
|
GlobalInputs::check();
|
|
}
|
|
|
|
// Verifica si alguno de los controladores ha solicitado una pausa y actualiza el estado de pausa del juego.
|
|
void Game::checkPauseInput() {
|
|
// Comprueba los mandos
|
|
for (int i = 0; i < input_->getNumControllers(); ++i) {
|
|
if (input_->checkInput(InputAction::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT, InputDevice::CONTROLLER, i)) {
|
|
pause(!paused_);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Comprueba el teclado
|
|
if (input_->checkInput(InputAction::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT, InputDevice::KEYBOARD)) {
|
|
pause(!paused_);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Gestiona las entradas de los jugadores en el modo demo para saltarse la demo.
|
|
void Game::demoHandlePassInput() {
|
|
if (input_->checkAnyButton() != 0) {
|
|
Section::name = Section::Name::TITLE; // Salir del modo demo y regresar al menú principal.
|
|
Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO; // El juego volverá a mostrar la demo
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Gestiona las entradas de los jugadores en el modo demo, incluyendo movimientos y disparos automáticos.
|
|
void Game::demoHandleInput() {
|
|
int index = 0;
|
|
for (const auto &player : players_) {
|
|
if (player->isPlaying()) {
|
|
// Maneja el input específico del jugador en modo demo.
|
|
demoHandlePlayerInput(player, index);
|
|
}
|
|
++index;
|
|
}
|
|
}
|
|
|
|
// Procesa las entradas para un jugador específico durante el modo demo.
|
|
void Game::demoHandlePlayerInput(const std::shared_ptr<Player> &player, int index) {
|
|
const auto &demo_data = demo_.data[index][demo_.counter];
|
|
|
|
if (demo_data.left == 1) {
|
|
player->setInput(InputAction::LEFT);
|
|
} else if (demo_data.right == 1) {
|
|
player->setInput(InputAction::RIGHT);
|
|
} else if (demo_data.no_input == 1) {
|
|
player->setInput(InputAction::NONE);
|
|
}
|
|
|
|
if (demo_data.fire == 1) {
|
|
handleFireInput(player, BulletType::UP);
|
|
} else if (demo_data.fire_left == 1) {
|
|
handleFireInput(player, BulletType::LEFT);
|
|
} else if (demo_data.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 bullet_type) {
|
|
if (player->canFire()) {
|
|
SDL_Point bullet = {0, 0};
|
|
switch (bullet_type) {
|
|
case BulletType::UP:
|
|
player->setInput(InputAction::FIRE_CENTER);
|
|
bullet.x = 2 + player->getPosX() + (player->getWidth() - Bullet::WIDTH) / 2;
|
|
bullet.y = player->getPosY() - (Bullet::HEIGHT / 2);
|
|
break;
|
|
case BulletType::LEFT:
|
|
player->setInput(InputAction::FIRE_LEFT);
|
|
bullet.x = player->getPosX() - (Bullet::WIDTH / 2);
|
|
bullet.y = player->getPosY();
|
|
break;
|
|
case BulletType::RIGHT:
|
|
player->setInput(InputAction::FIRE_RIGHT);
|
|
bullet.x = player->getPosX() + player->getWidth() - (Bullet::WIDTH / 2);
|
|
bullet.y = player->getPosY();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
createBullet(bullet.x, bullet.y, bullet_type, player->isPowerUp(), player->getId());
|
|
playSound("bullet.wav");
|
|
|
|
// Establece un tiempo de espera para el próximo disparo.
|
|
constexpr int POWERUP_COOLDOWN = 5;
|
|
constexpr int AUTOFIRE_COOLDOWN = 10;
|
|
constexpr int NORMAL_COOLDOWN = 7;
|
|
|
|
int cant_fire_counter;
|
|
if (player->isPowerUp()) {
|
|
cant_fire_counter = POWERUP_COOLDOWN;
|
|
} else if (Options::settings.autofire) {
|
|
cant_fire_counter = AUTOFIRE_COOLDOWN;
|
|
} else {
|
|
cant_fire_counter = NORMAL_COOLDOWN;
|
|
}
|
|
|
|
player->setCantFireCounter(cant_fire_counter);
|
|
}
|
|
}
|
|
|
|
// Gestiona las entradas de todos los jugadores en el modo normal (fuera del modo demo).
|
|
void Game::handlePlayersInput() {
|
|
for (const auto &player : players_) {
|
|
if (player->isPlaying()) {
|
|
// Maneja el input de los jugadores en modo normal.
|
|
handleNormalPlayerInput(player);
|
|
} else if (player->isContinue() || player->isWaiting()) {
|
|
// Gestiona la continuación del jugador.
|
|
handlePlayerContinue(player);
|
|
} else if (player->isEnteringName() || player->isEnteringNameGameCompleted() || player->isShowingName()) {
|
|
// Gestiona la introducción del nombre del jugador.
|
|
handleNameInput(player);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Maneja las entradas de movimiento y disparo para un jugador en modo normal.
|
|
void Game::handleNormalPlayerInput(const std::shared_ptr<Player> &player) {
|
|
const auto &controller = Options::controllers.at(player->getController());
|
|
const bool AUTOFIRE = player->isPowerUp() || Options::settings.autofire;
|
|
|
|
if (input_->checkInput(InputAction::LEFT, INPUT_ALLOW_REPEAT, controller.type, controller.index)) {
|
|
player->setInput(InputAction::LEFT);
|
|
#ifdef RECORDING
|
|
demo_.keys.left = 1;
|
|
#endif
|
|
} else if (input_->checkInput(InputAction::RIGHT, INPUT_ALLOW_REPEAT, controller.type, controller.index)) {
|
|
player->setInput(InputAction::RIGHT);
|
|
#ifdef RECORDING
|
|
demo_.keys.right = 1;
|
|
#endif
|
|
} else {
|
|
player->setInput(InputAction::NONE);
|
|
#ifdef RECORDING
|
|
demo_.keys.no_input = 1;
|
|
#endif
|
|
}
|
|
|
|
handleFireInputs(player, AUTOFIRE, player->getController()); // 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 controller_index) {
|
|
if (input_->checkInput(InputAction::FIRE_CENTER, autofire, Options::controllers[controller_index].type, Options::controllers[controller_index].index)) {
|
|
handleFireInput(player, BulletType::UP);
|
|
#ifdef RECORDING
|
|
demo_.keys.fire = 1;
|
|
#endif
|
|
} else if (input_->checkInput(InputAction::FIRE_LEFT, autofire, Options::controllers[controller_index].type, Options::controllers[controller_index].index)) {
|
|
handleFireInput(player, BulletType::LEFT);
|
|
#ifdef RECORDING
|
|
demo_.keys.fire_left = 1;
|
|
#endif
|
|
} else if (input_->checkInput(InputAction::FIRE_RIGHT, autofire, Options::controllers[controller_index].type, Options::controllers[controller_index].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 CONTROLLER_INDEX = player->getController();
|
|
if (input_->checkInput(InputAction::START, INPUT_DO_NOT_ALLOW_REPEAT, Options::controllers[CONTROLLER_INDEX].type, Options::controllers[CONTROLLER_INDEX].index)) {
|
|
player->setPlayingState(PlayerState::RESPAWNING);
|
|
}
|
|
|
|
// Disminuye el contador de continuación si se presiona cualquier botón de disparo.
|
|
if (input_->checkInput(InputAction::FIRE_LEFT, INPUT_DO_NOT_ALLOW_REPEAT, Options::controllers[CONTROLLER_INDEX].type, Options::controllers[CONTROLLER_INDEX].index) ||
|
|
input_->checkInput(InputAction::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, Options::controllers[CONTROLLER_INDEX].type, Options::controllers[CONTROLLER_INDEX].index) ||
|
|
input_->checkInput(InputAction::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, Options::controllers[CONTROLLER_INDEX].type, Options::controllers[CONTROLLER_INDEX].index)) {
|
|
if (player->getContinueCounter() < param.scoreboard.skip_countdown_value) {
|
|
player->decContinueCounter();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Procesa las entradas para la introducción del nombre del jugador.
|
|
void Game::handleNameInput(const std::shared_ptr<Player> &player) {
|
|
const auto CONTROLLER_INDEX = player->getController();
|
|
if (input_->checkInput(InputAction::FIRE_LEFT, INPUT_DO_NOT_ALLOW_REPEAT, Options::controllers[CONTROLLER_INDEX].type, Options::controllers[CONTROLLER_INDEX].index)) {
|
|
if (player->isShowingName()) {
|
|
player->setPlayingState(PlayerState::CONTINUE);
|
|
} else if (player->getEnterNamePositionOverflow()) {
|
|
player->setInput(InputAction::START);
|
|
addScoreToScoreBoard(player);
|
|
player->setPlayingState(PlayerState::SHOWING_NAME);
|
|
} else {
|
|
player->setInput(InputAction::RIGHT);
|
|
}
|
|
} else if (input_->checkInput(InputAction::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, Options::controllers[CONTROLLER_INDEX].type, Options::controllers[CONTROLLER_INDEX].index) ||
|
|
input_->checkInput(InputAction::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, Options::controllers[CONTROLLER_INDEX].type, Options::controllers[CONTROLLER_INDEX].index)) {
|
|
if (player->isShowingName()) {
|
|
player->setPlayingState(PlayerState::CONTINUE);
|
|
} else {
|
|
player->setInput(InputAction::LEFT);
|
|
}
|
|
} else if (input_->checkInput(InputAction::UP, INPUT_DO_NOT_ALLOW_REPEAT, Options::controllers[CONTROLLER_INDEX].type, Options::controllers[CONTROLLER_INDEX].index)) {
|
|
player->setInput(InputAction::UP);
|
|
} else if (input_->checkInput(InputAction::DOWN, INPUT_DO_NOT_ALLOW_REPEAT, Options::controllers[CONTROLLER_INDEX].type, Options::controllers[CONTROLLER_INDEX].index)) {
|
|
player->setInput(InputAction::DOWN);
|
|
} else if (input_->checkInput(InputAction::START, INPUT_DO_NOT_ALLOW_REPEAT, Options::controllers[CONTROLLER_INDEX].type, Options::controllers[CONTROLLER_INDEX].index)) {
|
|
if (player->isShowingName()) {
|
|
player->setPlayingState(PlayerState::CONTINUE);
|
|
} else {
|
|
player->setInput(InputAction::START);
|
|
addScoreToScoreBoard(player);
|
|
player->setPlayingState(PlayerState::SHOWING_NAME);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Inicializa las variables para el modo DEMO
|
|
void Game::initDemo(int player_id) {
|
|
if (demo_.enabled) {
|
|
// Cambia el estado del juego
|
|
setState(GameState::PLAYING);
|
|
|
|
// Aleatoriza la asignación del fichero con los datos del modo demostracion
|
|
{
|
|
const auto DEMO1 = rand() % 2;
|
|
const auto DEMO2 = (DEMO1 == 0) ? 1 : 0;
|
|
demo_.data.emplace_back(Resource::get()->getDemoData(DEMO1));
|
|
demo_.data.emplace_back(Resource::get()->getDemoData(DEMO2));
|
|
}
|
|
|
|
// Selecciona una pantalla al azar
|
|
{
|
|
constexpr auto NUM_DEMOS = 3;
|
|
const auto DEMO = rand() % NUM_DEMOS;
|
|
constexpr std::array<int, NUM_DEMOS> STAGES = {0, 3, 5};
|
|
Stage::number = STAGES[DEMO];
|
|
}
|
|
|
|
// Actualiza el numero de globos explotados según la fase del modo demostración
|
|
for (int i = 0; i < Stage::number; ++i) {
|
|
Stage::total_power += Stage::get(i).power_to_complete;
|
|
}
|
|
|
|
// Activa o no al otro jugador
|
|
if (rand() % 3 != 0) {
|
|
const auto OTHER_PLAYER_ID = player_id == 1 ? 2 : 1;
|
|
auto other_player = getPlayer(OTHER_PLAYER_ID);
|
|
other_player->setPlayingState(PlayerState::PLAYING);
|
|
}
|
|
|
|
// Asigna cafes a los jugadores
|
|
for (auto &player : players_) {
|
|
for (int i = 0; i < rand() % 3; ++i) {
|
|
player->giveExtraHit();
|
|
}
|
|
|
|
player->setInvulnerable(true);
|
|
}
|
|
|
|
// Configura los marcadores
|
|
scoreboard_->setMode(SCOREBOARD_LEFT_PANEL, ScoreboardMode::DEMO);
|
|
scoreboard_->setMode(SCOREBOARD_RIGHT_PANEL, ScoreboardMode::DEMO);
|
|
|
|
// Silencia los globos
|
|
balloon_manager_->setSounds(false);
|
|
}
|
|
|
|
// Modo grabar demo
|
|
#ifdef RECORDING
|
|
demo_.recording = true;
|
|
#else
|
|
demo_.recording = false;
|
|
#endif
|
|
demo_.counter = 0;
|
|
}
|
|
|
|
// Calcula el poder total necesario para completar el juego
|
|
void Game::setTotalPower() {
|
|
total_power_to_complete_game_ = 0;
|
|
for (const auto &stage : Stage::stages) {
|
|
total_power_to_complete_game_ += stage.power_to_complete;
|
|
}
|
|
}
|
|
|
|
// Inicializa el marcador
|
|
void Game::initScoreboard() {
|
|
scoreboard_->setPos(param.scoreboard.rect);
|
|
scoreboard_->setMode(SCOREBOARD_CENTER_PANEL, ScoreboardMode::STAGE_INFO);
|
|
for (const auto &player : players_) {
|
|
scoreboard_->setName(player->getScoreBoardPanel(), player->getName());
|
|
if (player->isWaiting()) {
|
|
scoreboard_->setMode(player->getScoreBoardPanel(), ScoreboardMode::WAITING);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Inicializa las opciones relacionadas con la dificultad
|
|
void Game::initDifficultyVars() {
|
|
// Variables relacionadas con la dificultad
|
|
switch (difficulty_) {
|
|
case Difficulty::Code::EASY: {
|
|
balloon_manager_->setDefaultBalloonSpeed(BALLOON_SPEED[0]);
|
|
difficulty_score_multiplier_ = 0.5F;
|
|
scoreboard_->setColor(param.scoreboard.easy_color);
|
|
break;
|
|
}
|
|
|
|
case Difficulty::Code::NORMAL: {
|
|
balloon_manager_->setDefaultBalloonSpeed(BALLOON_SPEED[0]);
|
|
difficulty_score_multiplier_ = 1.0F;
|
|
scoreboard_->setColor(param.scoreboard.normal_color);
|
|
break;
|
|
}
|
|
|
|
case Difficulty::Code::HARD: {
|
|
balloon_manager_->setDefaultBalloonSpeed(BALLOON_SPEED[4]);
|
|
difficulty_score_multiplier_ = 1.5F;
|
|
scoreboard_->setColor(param.scoreboard.hard_color);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
balloon_manager_->resetBalloonSpeed();
|
|
}
|
|
|
|
// Inicializa los jugadores
|
|
void Game::initPlayers(int player_id) {
|
|
// Crea los dos jugadores
|
|
constexpr int PLAYER_HEIGHT = 32;
|
|
constexpr int PLAYER_WIDTH = 32;
|
|
const int Y = param.game.play_area.rect.h - PLAYER_HEIGHT + 1;
|
|
players_.emplace_back(std::make_unique<Player>(1, param.game.play_area.first_quarter_x - (PLAYER_WIDTH / 2), Y, demo_.enabled, param.game.play_area.rect, player_textures_[0], player_animations_));
|
|
players_.back()->setScoreBoardPanel(SCOREBOARD_LEFT_PANEL);
|
|
players_.back()->setName(Lang::getText("[SCOREBOARD] 1"));
|
|
players_.back()->setController(getController(players_.back()->getId()));
|
|
|
|
players_.emplace_back(std::make_unique<Player>(2, param.game.play_area.third_quarter_x - (PLAYER_WIDTH / 2), Y, demo_.enabled, param.game.play_area.rect, player_textures_[1], player_animations_));
|
|
players_.back()->setScoreBoardPanel(SCOREBOARD_RIGHT_PANEL);
|
|
players_.back()->setName(Lang::getText("[SCOREBOARD] 2"));
|
|
players_.back()->setController(getController(players_.back()->getId()));
|
|
|
|
// Activa el jugador que coincide con el "player_id" o ambos si es "0"
|
|
if (player_id == 0) {
|
|
// Activa ambos jugadores
|
|
getPlayer(1)->setPlayingState(demo_.enabled ? PlayerState::PLAYING : PlayerState::ENTERING_SCREEN);
|
|
getPlayer(2)->setPlayingState(demo_.enabled ? PlayerState::PLAYING : PlayerState::ENTERING_SCREEN);
|
|
} else {
|
|
getPlayer(player_id)->setPlayingState(demo_.enabled ? PlayerState::PLAYING : PlayerState::ENTERING_SCREEN);
|
|
}
|
|
}
|
|
|
|
// Hace sonar la música
|
|
void Game::playMusic() {
|
|
Audio::get()->playMusic("playing.ogg");
|
|
}
|
|
|
|
// Detiene la música
|
|
void Game::stopMusic() const {
|
|
if (!demo_.enabled) {
|
|
Audio::get()->stopMusic();
|
|
}
|
|
}
|
|
|
|
// Actualiza las variables durante el modo demo
|
|
void Game::updateDemo() {
|
|
if (demo_.enabled) {
|
|
balloon_manager_->setCreationTimeEnabled(balloon_manager_->getNumBalloons() != 0);
|
|
|
|
// Actualiza ambos fades
|
|
fade_in_->update();
|
|
fade_out_->update();
|
|
|
|
// 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_out_->setType(FadeType::RANDOM_SQUARE);
|
|
fade_out_->activate();
|
|
}
|
|
|
|
// Si ha terminado el fundido, cambia de sección
|
|
if (fade_out_->hasEnded()) {
|
|
Section::name = Section::Name::HI_SCORE_TABLE;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef RECORDING
|
|
// Actualiza las variables durante el modo de grabación
|
|
void Game::updateRecording() {
|
|
// 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
|
|
|
|
// Actualiza las variables durante dicho estado
|
|
void Game::updateGameStateFadeIn() {
|
|
fade_in_->update();
|
|
updateScoreboard();
|
|
updateBackground();
|
|
if (fade_in_->hasEnded()) {
|
|
setState(GameState::ENTERING_PLAYER);
|
|
balloon_manager_->createTwoBigBalloons();
|
|
evaluateAndSetMenace();
|
|
}
|
|
}
|
|
|
|
// Actualiza las variables durante dicho estado
|
|
void Game::updateGameStateEnteringPlayer() {
|
|
balloon_manager_->update();
|
|
updatePlayers();
|
|
updateScoreboard();
|
|
updateBackground();
|
|
for (const auto &player : players_) {
|
|
if (player->isPlaying()) {
|
|
setState(GameState::SHOWING_GET_READY_MESSAGE);
|
|
createMessage({paths_.at(0), paths_.at(1)}, Resource::get()->getTexture("game_text_get_ready"));
|
|
playSound("voice_get_ready.wav");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Actualiza las variables durante dicho estado
|
|
void Game::updateGameStateShowingGetReadyMessage() {
|
|
balloon_manager_->update();
|
|
updatePathSprites();
|
|
updatePlayers();
|
|
updateBullets();
|
|
updateScoreboard();
|
|
updateBackground();
|
|
freePathSprites();
|
|
if (path_sprites_.empty()) {
|
|
setState(GameState::PLAYING);
|
|
}
|
|
if (counter_ == 100) {
|
|
playMusic();
|
|
}
|
|
++counter_;
|
|
}
|
|
|
|
// Actualiza las variables durante el transcurso normal del juego
|
|
void Game::updateGameStatePlaying() {
|
|
#ifdef _DEBUG
|
|
if (auto_pop_balloons_) {
|
|
Stage::addPower(5);
|
|
}
|
|
#endif
|
|
updatePlayers();
|
|
checkPlayersStatusPlaying();
|
|
updateScoreboard();
|
|
updateBackground();
|
|
balloon_manager_->update();
|
|
tabe_->update();
|
|
updateBullets();
|
|
updateItems();
|
|
updateStage();
|
|
updateSmartSprites();
|
|
updatePathSprites();
|
|
updateTimeStopped();
|
|
updateHelper();
|
|
checkBulletCollision();
|
|
updateMenace();
|
|
checkAndUpdateBalloonSpeed();
|
|
checkState();
|
|
cleanVectors();
|
|
// playMusic();
|
|
}
|
|
|
|
// Vacía los vectores de elementos deshabilitados
|
|
void Game::cleanVectors() {
|
|
freeBullets();
|
|
balloon_manager_->freeBalloons();
|
|
freeItems();
|
|
freeSmartSprites();
|
|
freePathSprites();
|
|
}
|
|
|
|
// Gestiona el nivel de amenaza
|
|
void Game::updateMenace() {
|
|
if (state_ == GameState::PLAYING) {
|
|
const auto STAGE = Stage::get(Stage::number);
|
|
const float PERCENT = Stage::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
|
|
balloon_manager_->deployBalloonFormation(Stage::number);
|
|
|
|
// Recalcula el nivel de amenaza con el nuevo globo
|
|
evaluateAndSetMenace();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calcula y establece el valor de amenaza en funcion de los globos activos
|
|
void Game::evaluateAndSetMenace() {
|
|
menace_current_ = balloon_manager_->getMenace();
|
|
}
|
|
|
|
// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
|
|
void Game::checkAndUpdateBalloonSpeed() {
|
|
if (difficulty_ != Difficulty::Code::NORMAL) {
|
|
return;
|
|
}
|
|
|
|
const float PERCENT = static_cast<float>(Stage::power) / Stage::get(Stage::number).power_to_complete;
|
|
constexpr std::array<float, 4> THRESHOLDS = {0.2F, 0.4F, 0.6F, 0.8F};
|
|
|
|
for (size_t i = 0; i < std::size(THRESHOLDS); ++i) {
|
|
if (balloon_manager_->getBalloonSpeed() == BALLOON_SPEED[i] && PERCENT > THRESHOLDS[i]) {
|
|
balloon_manager_->setBalloonSpeed(BALLOON_SPEED[i + 1]);
|
|
break; // Salir del bucle una vez actualizada la velocidad y aplicada
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cambia el estado del juego
|
|
void Game::setState(GameState state) {
|
|
state_ = state;
|
|
counter_ = 0;
|
|
}
|
|
|
|
void Game::playSound(const std::string &name) const {
|
|
if (demo_.enabled) {
|
|
return;
|
|
}
|
|
|
|
static auto *audio_ = Audio::get();
|
|
audio_->playSound(name);
|
|
}
|
|
|
|
// Organiza los jugadores para que los vivos se pinten sobre los muertos
|
|
void Game::movePlayersToFront() {
|
|
if (players_to_reorder_.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (auto &player : players_to_reorder_) {
|
|
auto it = std::find(players_.begin(), players_.end(), player);
|
|
if (it != players_.end() && it != players_.begin()) {
|
|
std::shared_ptr<Player> dying_player = *it;
|
|
players_.erase(it);
|
|
players_.insert(players_.begin(), dying_player);
|
|
}
|
|
}
|
|
players_to_reorder_.clear();
|
|
}
|
|
|
|
// Comprueba si está activo el menu de servicio para poner el juego en pausa
|
|
void Game::checkServiceMenu() {
|
|
if (demo_.enabled) {
|
|
return;
|
|
}
|
|
|
|
static bool was_paused_before_service_menu_ = false;
|
|
static bool service_menu_was_active_ = false;
|
|
|
|
bool service_menu_is_active = ServiceMenu::get()->isEnabled();
|
|
|
|
if (service_menu_is_active && !service_menu_was_active_) {
|
|
// El menú acaba de abrirse
|
|
was_paused_before_service_menu_ = paused_;
|
|
pause(true);
|
|
} else if (!service_menu_is_active && service_menu_was_active_) {
|
|
// El menú acaba de cerrarse
|
|
pause(was_paused_before_service_menu_);
|
|
}
|
|
|
|
service_menu_was_active_ = service_menu_is_active;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
// Comprueba los eventos en el modo DEBUG
|
|
void Game::checkDebugEvents(const SDL_Event &event) {
|
|
if (event.type == SDL_EVENT_KEY_DOWN && static_cast<int>(event.key.repeat) == 0) {
|
|
switch (event.key.key) {
|
|
case SDLK_1: // Crea una powerball
|
|
{
|
|
// balloon_manager_->createPowerBall();
|
|
throwCoffee(players_.at(0)->getPosX() + (players_.at(0)->getWidth() / 2), players_.at(0)->getPosY() + (players_.at(0)->getHeight() / 2));
|
|
break;
|
|
}
|
|
case SDLK_2: // Activa o desactiva la aparición de globos
|
|
{
|
|
static bool deploy_balloons_ = true;
|
|
deploy_balloons_ = !deploy_balloons_;
|
|
balloon_manager_->enableBalloonDeployment(deploy_balloons_);
|
|
break;
|
|
}
|
|
case SDLK_3: // Activa el modo para pasar el juego automaticamente
|
|
{
|
|
auto_pop_balloons_ = !auto_pop_balloons_;
|
|
Notifier::get()->show({"auto advance: " + boolToString(auto_pop_balloons_)});
|
|
if (auto_pop_balloons_) {
|
|
balloon_manager_->destroyAllBalloons();
|
|
playSound("power_ball_explosion.wav");
|
|
}
|
|
balloon_manager_->enableBalloonDeployment(!auto_pop_balloons_);
|
|
break;
|
|
}
|
|
case SDLK_4: // Suelta un item
|
|
{
|
|
createItem(ItemType::CLOCK, players_.at(0)->getPosX(), players_.at(0)->getPosY() - 40);
|
|
break;
|
|
}
|
|
case SDLK_5: // 5.000
|
|
{
|
|
const int X = players_.at(0)->getPosX() + (players_.at(0)->getWidth() - game_text_textures_[3]->getWidth()) / 2;
|
|
createItemText(X, game_text_textures_.at(2));
|
|
break;
|
|
}
|
|
case SDLK_6: // Crea un mensaje
|
|
{
|
|
createMessage({paths_.at(0), paths_.at(1)}, Resource::get()->getTexture("game_text_get_ready"));
|
|
break;
|
|
}
|
|
case SDLK_7: // 100.000
|
|
{
|
|
const int X = players_.at(0)->getPosX() + (players_.at(0)->getWidth() - game_text_textures_[3]->getWidth()) / 2;
|
|
createItemText(X, game_text_textures_.at(6));
|
|
break;
|
|
}
|
|
case SDLK_8: {
|
|
for (const auto &player : players_) {
|
|
if (player->isPlaying()) {
|
|
createItem(ItemType::COFFEE_MACHINE, player->getPosX(), param.game.game_area.rect.y - Item::COFFEE_MACHINE_HEIGHT);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SDLK_9: {
|
|
tabe_->enable();
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif |