#include "game.h" #include "asset.h" // Para Asset #include "background.h" // Para Background #include "balloon.h" // Para Balloon, BALLOON_SCORE, BALLOON_VE... #include "balloon_formations.h" // Para BalloonFormations, BalloonFormatio... #include "bullet.h" // Para Bullet, BulletType, BulletMoveStatus #include "explosions.h" // Para Explosions #include "fade.h" // Para Fade, FadeType #include "global_inputs.h" // Para check #include "input.h" // Para InputType, Input, INPUT_DO_NOT_ALL... #include "item.h" // Para Item, ItemType #include "jail_audio.h" // Para JA_PlaySound, JA_GetMusicState #include "lang.h" // Para getText #include "manage_hiscore_table.h" // Para ManageHiScoreTable, HiScoreEntry #include "notifier.h" // Para Notifier #include "param.h" // Para Param, param, ParamGame, ParamFade #include "path_sprite.h" // Para Path, PathSprite, createPath, Path... #include "player.h" // Para Player, PlayerStatus #include "resource.h" // Para Resource #include "scoreboard.h" // Para Scoreboard, ScoreboardMode, SCOREB... #include "screen.h" // Para Screen #include "section.h" // Para Name, name, Options, options #include "smart_sprite.h" // Para SmartSprite #include "text.h" // Para Text #include "texture.h" // Para Texture #include // Para SDL_BLENDMODE_BLEND #include // Para SDL_PollEvent, SDL_Event, SDL_KEYDOWN #include // Para SDLK_1, SDLK_2, SDLK_3, SDLK_4 #include // Para SDL_PIXELFORMAT_RGBA8888 #include // Para SDL_GetTicks #include // Para SDL_WINDOWEVENT_FOCUS_GAINED, SDL_... #include // Para find_if, clamp, min, remove_if #include // Para function #include // Para distance, size #include // Para accumulate #include // Para rand, size_t struct JA_Sound_t; // lines 36-36 // Constructor Game::Game(int player_id, int current_stage, bool demo) : renderer_(Screen::get()->getRenderer()), screen_(Screen::get()), asset_(Asset::get()), input_(Input::get()), background_(std::make_unique()), explosions_(std::make_unique()), balloon_formations_(std::make_unique()), canvas_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.play_area.rect.w, param.game.play_area.rect.h)), fade_(std::make_unique()), current_stage_(current_stage) { // Pasa variables demo_.enabled = demo; // Otras variables section::name = section::Name::GAME; section::options = section::Options::GAME_PLAY_1P; // Asigna texturas y animaciones setResources(); // Crea y configura los objetos Scoreboard::init(); scoreboard_ = Scoreboard::get(); fade_->setColor(fade_color.r, fade_color.g, fade_color.b); fade_->setPost(param.fade.post_duration); fade_->setType(FadeType::VENETIAN); background_->setPos(param.game.play_area.rect); explosions_->addTexture(1, explosions_textures_[0], explosions_animations_[0]); explosions_->addTexture(2, explosions_textures_[1], explosions_animations_[1]); explosions_->addTexture(3, explosions_textures_[2], explosions_animations_[2]); explosions_->addTexture(4, explosions_textures_[3], explosions_animations_[3]); SDL_SetTextureBlendMode(canvas_, SDL_BLENDMODE_BLEND); // Inicializa el resto de variables initPlayers(player_id); initScoreboard(); initDifficultyVars(); initDemo(player_id); initPaths(); setTotalPower(); #ifdef DEBUG // Si se empieza en una fase que no es la primera if (!demo_.enabled) for (int i = 0; i < current_stage_; ++i) { balloons_popped_ += balloon_formations_->getStage(i).power_to_complete; } #endif // Crea los primeros globos y el mensaje de inicio if (!demo_.enabled) { createTwoBigBalloons(); evaluateAndSetMenace(); createMessage({paths_.at(0), paths_.at(1)}, Resource::get()->getTexture("get_ready")); } } Game::~Game() { // Guarda las puntuaciones en un fichero if (!demo_.enabled) { auto manager = std::make_unique(options.game.hi_score_table); manager->saveToFile(asset_->get("score.bin")); } #ifdef RECORDING saveDemoFile(Asset::get()->get("demo1.bin"), demo_.data.at(0)); #endif Scoreboard::destroy(); SDL_DestroyTexture(canvas_); } // Asigna texturas y animaciones void Game::setResources() { // Texturas { bullet_texture_ = Resource::get()->getTexture("bullet.png"); } // Texturas - Game_text { game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_1000_points")); 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")); } // 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> 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> 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")); } } // Crea una formación de enemigos void Game::deployBalloonFormation() { // Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última if (balloon_deploy_counter_ == 0) { // En este punto se decide entre crear una powerball o una formación enemiga if ((rand() % 100 < 15) && (canPowerBallBeCreated())) { // Crea una powerball createPowerBall(); // Da un poco de margen para que se creen mas enemigos balloon_deploy_counter_ = 300; } else { // Decrementa el contador de despliegues enemigos de la PowerBall power_ball_counter_ = (power_ball_counter_ > 0) ? (power_ball_counter_ - 1) : 0; // Elige una formación enemiga la azar auto formation = rand() % 10; // Evita repetir la ultima formación enemiga desplegada if (formation == last_balloon_deploy_) { ++formation %= 10; } last_balloon_deploy_ = formation; const auto set = balloon_formations_->getStage(current_stage_).balloon_pool.set[formation]; const auto numEnemies = set.number_of_balloons; for (int i = 0; i < numEnemies; ++i) { auto p = set.init[i]; createBalloon(p.x, p.y, p.type, p.size, p.vel_x, balloon_speed_, p.creation_counter); } balloon_deploy_counter_ = 300; } } } // Aumenta el poder de la fase void Game::increaseStageCurrentPower(int power) { current_power_ += power; } // Actualiza el valor de hiScore en caso necesario void Game::updateHiScore() { // Si la puntuación actual es mayor que la máxima puntuación for (const auto &player : players_) { if (player->getScore() > hi_score_.score) { // Actualiza la máxima puntuación hi_score_.score = player->getScore(); // Si se supera la máxima puntuación emite sonido if (hi_score_achieved_ == false) { hi_score_achieved_ = true; JA_PlaySound(Resource::get()->getSound("hiscore.wav")); } } } } // Actualiza las variables del jugador void Game::updatePlayers() { for (auto &player : players_) { player->update(); if (player->isPlaying()) { // Comprueba la colisión entre el jugador y los globos if (checkPlayerBalloonCollision(player)) { killPlayer(player); if (demo_.enabled && allPlayersAreNotPlaying()) { fade_->setType(FadeType::RANDOM_SQUARE); fade_->activate(); } } // Comprueba las colisiones entre el jugador y los items checkPlayerItemCollision(player); } } } // Dibuja a los jugadores void Game::renderPlayers() { for (auto &player : players_) { if (!player->isWaiting()) { player->render(); #ifdef DEBUG // SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); // const Circle c = player->getCollider(); // DrawCircle(renderer, c.x, c.y, c.r); #endif } } } // Comprueba si hay cambio de fase y actualiza las variables void Game::updateStage() { if (state_ == GameState::PLAYING) { if (current_power_ >= balloon_formations_->getStage(current_stage_).power_to_complete) { // Cambio de fase ++current_stage_; current_power_ = 0; JA_PlaySound(Resource::get()->getSound("stage_change.wav")); balloon_speed_ = default_balloon_speed_; setBalloonSpeed(balloon_speed_); screen_->flash(flash_color, 100); screen_->shake(); // Escribe el texto por pantalla if (current_stage_ < 10) { const auto stage_number = balloon_formations_->getStage(current_stage_).number; std::vector paths = {paths_.at(2), paths_.at(3)}; if (stage_number == 10) createMessage(paths, Resource::get()->getTexture("last_stage")); else { auto text = Resource::get()->getText("04b_25_2x"); const std::string caption = std::to_string(10 - current_stage_) + lang::getText(38); createMessage(paths, text->writeToTexture(caption, 1, -4)); } } } } } // Actualiza el estado de fin de la partida void Game::updateGameOverState() { if (state_ == GameState::GAME_OVER) { if (game_over_counter_ > 0) { if (game_over_counter_ == GAME_OVER_COUNTER_) createMessage({paths_.at(2), paths_.at(3)}, Resource::get()->getTexture("game_over")); 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; } } } // Gestiona eventos para el estado del final del juego void Game::updateCompletedState() { if (state_ == GameState::COMPLETED) { // Para la música y elimina todos los globos e items if (game_completed_counter_ == 0) { stopMusic(); current_stage_ = 9; // Deja el valor dentro de los limites destroyAllBalloons(); // Destruye a todos los globos destroyAllItems(); // Destruye todos los items current_power_ = 0; // Vuelve a dejar el poder a cero, por lo que hubiera podido subir al destruir todos los globos } // Comienza las celebraciones // Muestra el mensaje de felicitación y da los puntos a los jugadores if (game_completed_counter_ == 200) { createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("congratulations")); createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("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_ == 500) { for (auto &player : players_) if (player->isCelebrating()) { player->setPlayingState(player->IsEligibleForHighScore() ? PlayerState::ENTERING_NAME_GAME_COMPLETED : PlayerState::GAME_COMPLETED); } } // Incrementa el contador al final ++game_completed_counter_; } } // Comprueba el estado del juego void Game::checkState() { if (state_ != GameState::COMPLETED && current_stage_ == 10) { state_ = GameState::COMPLETED; } if (state_ != GameState::GAME_OVER && allPlayersAreGameOver()) { state_ = GameState::GAME_OVER; } } // 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 Game::createBalloon(float x, int y, BalloonType type, BalloonSize size, float velx, float speed, int creation_timer) { const int index = static_cast(size); balloons_.emplace_back(std::make_shared(x, y, type, size, velx, speed, creation_timer, balloon_textures_.at(index), balloon_animations_.at(index))); return balloons_.back(); } // Crea un globo a partir de otro globo void Game::createChildBalloon(const std::shared_ptr &balloon, const std::string &direction) { const float vx = direction == "LEFT" ? BALLOON_VELX_NEGATIVE : BALLOON_VELX_POSITIVE; const auto lower_size = static_cast(static_cast(balloon->getSize()) - 1); auto b = createBalloon(0, balloon->getPosY(), balloon->getType(), lower_size, vx, balloon_speed_, 0); b->alignTo(balloon->getPosX() + (balloon->getWidth() / 2)); b->setVelY(b->getType() == BalloonType::BALLOON ? -2.50f : BALLOON_VELX_NEGATIVE * 2.0f); if (balloon->isStopped()) b->stop(); if (balloon->isUsingReversedColor()) b->useReverseColor(); } // Crea una PowerBall void Game::createPowerBall() { constexpr auto values = 6; constexpr auto pos_y = -BLOCK; constexpr int creation_time = 300; const auto left = param.game.play_area.rect.x; const auto center = param.game.play_area.center_x - (BALLOON_SIZE[3] / 2); const auto right = param.game.play_area.rect.w - BALLOON_SIZE[3]; const auto luck = rand() % values; const int x[values] = {left, left, center, center, right, right}; const float vx[values] = {BALLOON_VELX_POSITIVE, BALLOON_VELX_POSITIVE, BALLOON_VELX_POSITIVE, BALLOON_VELX_NEGATIVE, BALLOON_VELX_NEGATIVE, BALLOON_VELX_NEGATIVE}; balloons_.emplace_back(std::make_unique(x[luck], pos_y, BalloonType::POWERBALL, BalloonSize::SIZE4, vx[luck], balloon_speed_, creation_time, balloon_textures_[4], balloon_animations_[4])); power_ball_enabled_ = true; power_ball_counter_ = POWERBALL_COUNTER; } // Establece la velocidad de los globos void Game::setBalloonSpeed(float speed) { for (auto &balloon : balloons_) balloon->setSpeed(speed); } // Actualiza la velocidad de los globos en funcion del poder acumulado de la fase void Game::checkAndUpdateBalloonSpeed() { if (difficulty_ != GameDifficulty::NORMAL) return; const float percent = static_cast(current_power_) / balloon_formations_->getStage(current_stage_).power_to_complete; const float thresholds[] = {0.2f, 0.4f, 0.6f, 0.8f}; for (size_t i = 0; i < std::size(thresholds); ++i) if (balloon_speed_ == BALLOON_SPEED[i] && percent > thresholds[i]) { balloon_speed_ = BALLOON_SPEED[i + 1]; setBalloonSpeed(balloon_speed_); break; // Salir del bucle una vez actualizada la velocidad y aplicada } } // Explosiona un globo. Lo destruye y crea otros dos si es el caso void Game::popBalloon(std::shared_ptr balloon) { increaseStageCurrentPower(1); balloons_popped_++; if (balloon->getType() == BalloonType::POWERBALL) { destroyAllBalloons(); power_ball_enabled_ = false; balloon_deploy_counter_ = 20; } else { if (balloon->getSize() != BalloonSize::SIZE1) { createChildBalloon(balloon, "LEFT"); createChildBalloon(balloon, "RIGHT"); } // Agrega la explosión y elimina el globo explosions_->add(balloon->getPosX(), balloon->getPosY(), static_cast(balloon->getSize())); balloon->pop(); } // Recalcula el nivel de amenaza evaluateAndSetMenace(); } // Explosiona un globo. Lo destruye = no crea otros globos void Game::destroyBalloon(std::shared_ptr &balloon) { int score = 0; // Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos switch (balloon->getSize()) { case BalloonSize::SIZE4: score = BALLOON_SCORE[3] + (2 * BALLOON_SCORE[2]) + (4 * BALLOON_SCORE[1]) + (8 * BALLOON_SCORE[0]); break; case BalloonSize::SIZE3: score = BALLOON_SCORE[2] + (2 * BALLOON_SCORE[1]) + (4 * BALLOON_SCORE[0]); break; case BalloonSize::SIZE2: score = BALLOON_SCORE[1] + (2 * BALLOON_SCORE[0]); break; case BalloonSize::SIZE1: score = BALLOON_SCORE[0]; break; default: score = 0; break; } // Otorga los puntos correspondientes al globo for (auto &player : players_) player->addScore(score * player->getScoreMultiplier() * difficulty_score_multiplier_); updateHiScore(); // Aumenta el poder de la fase const auto power = balloon->getPower(); increaseStageCurrentPower(power); balloons_popped_ += power; // Destruye el globo explosions_->add(balloon->getPosX(), balloon->getPosY(), static_cast(balloon->getSize())); balloon->pop(); } // Destruye todos los globos void Game::destroyAllBalloons() { for (auto &balloon : balloons_) destroyBalloon(balloon); balloon_deploy_counter_ = 300; JA_PlaySound(Resource::get()->getSound("powerball.wav")); screen_->flash(flash_color, 100); screen_->shake(); } // Destruye todos los items void Game::destroyAllItems() { for (auto &item : items_) item->disable(); } // Detiene todos los globos void Game::stopAllBalloons() { for (auto &balloon : balloons_) balloon->stop(); } // Pone en marcha todos los globos void Game::startAllBalloons() { for (auto &balloon : balloons_) if (!balloon->isBeingCreated()) balloon->start(); } // Cambia el color de todos los globos void Game::reverseColorsToAllBalloons() { for (auto &balloon : balloons_) if (balloon->isStopped()) balloon->useReverseColor(); } // Cambia el color de todos los globos void Game::normalColorsToAllBalloons() { for (auto &balloon : balloons_) balloon->useNormalColor(); } // Vacia del vector de globos los globos que ya no sirven void Game::freeBalloons() { auto it = std::remove_if(balloons_.begin(), balloons_.end(), [](const auto &balloon) { return !balloon->isEnabled(); }); balloons_.erase(it, balloons_.end()); } // Comprueba la colisión entre el jugador y los globos activos bool Game::checkPlayerBalloonCollision(std::shared_ptr &player) { for (auto &balloon : balloons_) if (!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) { 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_[0]->getWidth()) / 2; createItemText(x, game_text_textures_[0]); break; } case ItemType::GAVINA: { player->addScore(2500); const auto x = item->getPosX() + (item->getWidth() - game_text_textures_[1]->getWidth()) / 2; createItemText(x, game_text_textures_[1]); break; } case ItemType::PACMAR: { player->addScore(5000); const auto x = item->getPosX() + (item->getWidth() - game_text_textures_[2]->getWidth()) / 2; createItemText(x, game_text_textures_[2]); break; } case ItemType::CLOCK: { enableTimeStopItem(); const auto x = item->getPosX() + (item->getWidth() - game_text_textures_[5]->getWidth()) / 2; createItemText(x, game_text_textures_[5]); break; } case ItemType::COFFEE: { if (player->getCoffees() == 2) { player->addScore(5000); const auto x = item->getPosX() + (item->getWidth() - game_text_textures_[2]->getWidth()) / 2; createItemText(x, game_text_textures_[2]); } else { player->giveExtraHit(); const auto x = item->getPosX() + (item->getWidth() - game_text_textures_[4]->getWidth()) / 2; createItemText(x, game_text_textures_[4]); } break; } case ItemType::COFFEE_MACHINE: { player->setPowerUp(); coffee_machine_enabled_ = false; const auto x = item->getPosX() + (item->getWidth() - game_text_textures_[3]->getWidth()) / 2; createItemText(x, 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 al jugador que disparó la bala auto player = getPlayer(bullet->getOwner()); if (!player) { return; } player->addScore(balloon->getScore() * player->getScoreMultiplier() * difficulty_score_multiplier_); player->incScoreMultiplier(); updateHiScore(); // Suelta el item si se da el caso const auto droppeditem = dropItem(); if (droppeditem != ItemType::NONE && !demo_.recording) { if (droppeditem != ItemType::COFFEE_MACHINE) { createItem(droppeditem, balloon->getPosX(), balloon->getPosY()); JA_PlaySound(Resource::get()->getSound("itemdrop.wav")); } else { createItem(droppeditem, player->getPosX(), 0); coffee_machine_enabled_ = true; } } // Explota el globo popBalloon(balloon); // Sonido de explosión JA_PlaySound(Resource::get()->getSound("balloon.wav")); // Deshabilita la bala bullet->disable(); break; } } } } } // Mueve las balas activas void Game::moveBullets() { for (auto &bullet : bullets_) if (bullet->move() == BulletMoveStatus::OUT) getPlayer(bullet->getOwner())->decScoreMultiplier(); } // Pinta las balas activas void Game::renderBullets() { for (auto &bullet : bullets_) bullet->render(); } // Crea un objeto bala void Game::createBullet(int x, int y, BulletType kind, bool powered_up, int owner) { bullets_.emplace_back( std::make_unique(x, y, kind, powered_up, owner, bullet_texture_)); } // Vacia el vector de balas void Game::freeBullets() { if (!bullets_.empty()) for (int i = bullets_.size() - 1; i >= 0; --i) if (!bullets_[i]->isEnabled()) bullets_.erase(bullets_.begin() + i); } // Actualiza los items void Game::updateItems() { for (auto &item : items_) if (item->isEnabled()) { item->update(); if (item->isOnFloor()) { JA_PlaySound(Resource::get()->getSound("title.wav")); screen_->shake(); } } } // Pinta los items activos void Game::renderItems() { for (auto &item : items_) item->render(); } // Devuelve un item al azar y luego segun sus probabilidades ItemType Game::dropItem() { const auto lucky_number = rand() % 100; const auto item = rand() % 6; switch (item) { case 0: if (lucky_number < helper_.item_disk_odds) { return ItemType::DISK; } break; case 1: if (lucky_number < helper_.item_gavina_odds) { return ItemType::GAVINA; } break; case 2: if (lucky_number < helper_.item_pacmar_odds) { return ItemType::GAVINA; } break; case 3: if (lucky_number < helper_.item_clock_odds) { return ItemType::CLOCK; } break; case 4: if (lucky_number < helper_.item_coffee_odds) { helper_.item_coffee_odds = ITEM_COFFEE_ODDS_; return ItemType::COFFEE; } else { if (helper_.need_coffee) helper_.item_coffee_odds++; } break; case 5: if (lucky_number < helper_.item_coffee_machine_odds) { helper_.item_coffee_machine_odds = ITEM_COFFEE_MACHINE_ODDS_; if (!coffee_machine_enabled_ && helper_.need_coffee_machine) return ItemType::COFFEE_MACHINE; } else { if (helper_.need_coffee_machine) helper_.item_coffee_machine_odds++; } break; default: break; } return ItemType::NONE; } // Crea un objeto item void Game::createItem(ItemType type, float x, float y) { items_.emplace_back(std::make_unique(type, x, y, param.game.play_area.rect, item_textures_[static_cast(type) - 1], item_animations_[static_cast(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 PathSprite void Game::createItemText(int x, std::shared_ptr texture) { path_sprites_.emplace_back(std::make_unique(texture)); const auto w = texture->getWidth(); const auto h = texture->getHeight(); const int y0 = param.game.play_area.rect.h - h; const int y1 = 155; const int y2 = -h; // Ajusta para que no se dibuje fuera de pantalla x = std::clamp(x, 2, 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, w, 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 &paths, std::shared_ptr texture) { path_sprites_.emplace_back(std::make_unique(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(item_textures_[4])); smart_sprites_.back()->setPosX(x - 8); smart_sprites_.back()->setPosY(y - 8); smart_sprites_.back()->setWidth(param.game.item_size); smart_sprites_.back()->setHeight(param.game.item_size); smart_sprites_.back()->setVelX(-1.0f + ((rand() % 5) * 0.5f)); smart_sprites_.back()->setVelY(-4.0f); smart_sprites_.back()->setAccelX(0.0f); smart_sprites_.back()->setAccelY(0.2f); smart_sprites_.back()->setDestX(x + (smart_sprites_.back()->getVelX() * 50)); smart_sprites_.back()->setDestY(param.game.height + 1); smart_sprites_.back()->setEnabled(true); smart_sprites_.back()->setFinishedCounter(1); smart_sprites_.back()->setSpriteClip(0, param.game.item_size, param.game.item_size, param.game.item_size); smart_sprites_.back()->enableRotate(); smart_sprites_.back()->setRotateSpeed(10); smart_sprites_.back()->setRotateAmount(90.0); } // Actualiza los SmartSprites void Game::updateSmartSprites() { for (auto &sprite : smart_sprites_) sprite->update(); } // Pinta los SmartSprites activos void Game::renderSmartSprites() { for (auto &sprite : smart_sprites_) sprite->render(); } // Actualiza los PathSprites void Game::updatePathSprites() { for (auto &sprite : path_sprites_) sprite->update(); } // Pinta los PathSprites activos void Game::renderPathSprites() { for (auto &sprite : path_sprites_) sprite->render(); } // Acciones a realizar cuando el jugador muere void Game::killPlayer(std::shared_ptr &player) { if (!player->isPlaying() || player->isInvulnerable()) { // Si no está jugando o tiene inmunidad, no hace nada return; } // Si tiene cafes if (player->hasExtraHit()) { // Lo pierde player->removeExtraHit(); throwCoffee(player->getPosX() + (player->getWidth() / 2), player->getPosY() + (player->getHeight() / 2)); JA_PlaySound(Resource::get()->getSound("coffeeout.wav")); screen_->shake(); } else { // Si no tiene cafes, muere pauseMusic(); stopAllBalloons(); JA_PlaySound(Resource::get()->getSound("player_collision.wav")); screen_->shake(); JA_PlaySound(Resource::get()->getSound("coffeeout.wav")); player->setPlayingState(PlayerState::DYING); allPlayersAreNotPlaying() ? stopMusic() : resumeMusic(); } } // Calcula y establece el valor de amenaza en funcion de los globos activos void Game::evaluateAndSetMenace() { menace_current_ = std::accumulate( balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon) { return sum + (balloon->isEnabled() ? balloon->getMenace() : 0); }); } // Actualiza y comprueba el valor de la variable void Game::updateTimeStopped() { if (time_stopped_counter_ > 0) { time_stopped_counter_--; if (time_stopped_counter_ > 120) { if (time_stopped_counter_ % 30 == 0) JA_PlaySound(Resource::get()->getSound("clock.wav")); } else { if (time_stopped_counter_ % 15 == 0) JA_PlaySound(Resource::get()->getSound("clock.wav")); if (time_stopped_counter_ % 30 == 0) normalColorsToAllBalloons(); if (time_stopped_counter_ % 30 == 15) reverseColorsToAllBalloons(); } } else disableTimeStopItem(); } // Actualiza la variable enemyDeployCounter void Game::updateBalloonDeployCounter() { if (balloon_deploy_counter_ > 0) --balloon_deploy_counter_; } // Actualiza el juego void Game::update() { constexpr int TICKS_SPEED = 15; if (SDL_GetTicks() - ticks_ > TICKS_SPEED) { ticks_ = SDL_GetTicks(); counter_++; updateDemo(); #ifdef RECORDING updateRecording(); #endif updateGame(); checkMusicStatus(); 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) 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 float clouds_initial_speed = 0.05f; constexpr float clouds_final_speed = 2.00f - clouds_initial_speed; const float cloudsSpeed = (-clouds_initial_speed) + (-clouds_final_speed * (static_cast(balloons_popped_) / total_power_to_complete_game_)); background_->setCloudsSpeed(cloudsSpeed); // Calcula la transición de los diferentes fondos const float gradient_number = std::min(balloons_popped_ / 1250.0f, 3.0f); const float percent = gradient_number - static_cast(gradient_number); background_->setGradientNumber(static_cast(gradient_number)); background_->setTransition(percent); // Actualiza el objeto background_->update(); } // Dibuja los elementos de la zona de juego en su textura void Game::fillCanvas() { // Dibujamos el contenido de la zona de juego en su textura auto temp = SDL_GetRenderTarget(renderer_); SDL_SetRenderTarget(renderer_, canvas_); // Dibuja los objetos background_->render(); renderItems(); renderSmartSprites(); explosions_->render(); renderBalloons(); renderBullets(); renderPathSprites(); 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() { 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(); } } // Habilita el efecto del item de detener el tiempo void Game::enableTimeStopItem() { stopAllBalloons(); reverseColorsToAllBalloons(); time_stopped_counter_ = TIME_STOPPED_COUNTER_; } // Deshabilita el efecto del item de detener el tiempo void Game::disableTimeStopItem() { time_stopped_counter_ = 0; startAllBalloons(); normalColorsToAllBalloons(); } // Comprueba si la música ha de estar sonando void Game::checkMusicStatus() { // Si la música no está sonando if (JA_GetMusicState() == JA_MUSIC_INVALID || JA_GetMusicState() == JA_MUSIC_STOPPED) // Si se ha completado el juego o los jugadores han terminado, detiene la música state_ == GameState::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() { // Recorrido para el texto de "Get Ready!" (0,1) { const auto &texture = Resource::get()->getTexture("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(Path(createPath(x0, x1, PathType::HORIZONTAL, y, 80, easeOutQuint), 20)); paths_.emplace_back(Path(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("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(Path(createPath(y0, y1, PathType::VERTICAL, x, 80, easeOutQuint), 20)); paths_.emplace_back(Path(createPath(y1, y2, PathType::VERTICAL, x, 80, easeInQuint), 0)); } // Recorrido para el texto de "Congratulations!!" (3,4) { const auto &texture = Resource::get()->getTexture("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(Path(createPath(x0, x1, PathType::HORIZONTAL, y, 80, easeOutQuint), 400)); paths_.emplace_back(Path(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("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(Path(createPath(x0, x1, PathType::HORIZONTAL, y, 80, easeOutQuint), 400)); paths_.emplace_back(Path(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 bool Game::allPlayersAreWaitingOrGameOver() { auto success = true; for (const auto &player : players_) success &= player->isWaiting() || player->isGameOver(); return success; } // Comprueba si todos los jugadores han terminado de jugar bool Game::allPlayersAreGameOver() { auto success = true; for (const auto &player : players_) success &= player->isGameOver(); return success; } // Comprueba si todos los jugadores han terminado de jugar bool Game::allPlayersAreNotPlaying() { auto success = true; for (const auto &player : players_) success &= !player->isPlaying(); return success; } // Comprueba los eventos que hay en cola void Game::checkEvents() { SDL_Event event; while (SDL_PollEvent(&event)) { // Evento de salida de la aplicación if (event.type == SDL_QUIT) { section::name = section::Name::QUIT; section::options = section::Options::QUIT_FROM_EVENT; break; } else if (event.type == SDL_WINDOWEVENT) { switch (event.window.event) { case SDL_WINDOWEVENT_FOCUS_LOST: { pause(!demo_.enabled); break; } case SDL_WINDOWEVENT_FOCUS_GAINED: { pause(false); break; } case SDL_WINDOWEVENT_SIZE_CHANGED: { reloadTextures(); break; } default: break; } } #ifdef DEBUG else if (event.type == SDL_KEYDOWN && event.key.repeat == 0) { switch (event.key.keysym.sym) { case SDLK_1: // Crea una powerball { createPowerBall(); break; } case SDLK_2: // Crea dos globos gordos { createTwoBigBalloons(); } break; case SDLK_3: // Activa el modo para pasar el juego automaticamente { auto_pop_balloons_ = !auto_pop_balloons_; Notifier::get()->showText({"auto_pop_balloons_ " + boolToString(auto_pop_balloons_)}); break; } case SDLK_4: // Suelta un item { createItem(ItemType::CLOCK, players_.at(0)->getPosX(), players_.at(0)->getPosY() - 40); break; } case SDLK_5: // Crea un PathSprite { const int x = players_.at(0)->getPosX() + (players_.at(0)->getWidth() - game_text_textures_[3]->getWidth()) / 2; createItemText(x, game_text_textures_.at(3)); break; } case SDLK_6: // Crea un mensaje { createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("congratulations")); createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("1000000_points")); break; } case SDLK_7: // Flash { screen_->flash(flash_color, 100); break; } case SDLK_8: { players_.at(0)->setPlayingState(PlayerState::GAME_COMPLETED); } default: break; } } #endif } } // Recarga las texturas void Game::reloadTextures() { for (auto &texture : item_textures_) texture->reLoad(); for (auto &texture : balloon_textures_) texture->reLoad(); for (auto &textures : player_textures_) for (auto &texture : textures) texture->reLoad(); for (auto &texture : game_text_textures_) texture->reLoad(); bullet_texture_->reLoad(); background_->reloadTextures(); } // Actualiza el marcador void Game::updateScoreboard() { for (const auto &player : players_) { scoreboard_->setScore(player->getScoreBoardPanel(), player->getScore()); scoreboard_->setMult(player->getScoreBoardPanel(), player->getScoreMultiplier()); } // Resto de marcador scoreboard_->setStage(balloon_formations_->getStage(current_stage_).number); scoreboard_->setPower((float)current_power_ / (float)balloon_formations_->getStage(current_stage_).power_to_complete); scoreboard_->setHiScore(hi_score_.score); scoreboard_->setHiScoreName(hi_score_.name); // Lógica del marcador scoreboard_->update(); } // Pausa el juego void Game::pause(bool value) { paused_ = value; 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(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]->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" std::shared_ptr Game::getPlayer(int id) { auto it = std::find_if(players_.begin(), players_.end(), [id](const auto &player) { return player->getId() == id; }); if (it != players_.end()) { return *it; } return nullptr; } // Obtiene un controlador a partir del "id" del jugador int Game::getController(int player_id) { auto it = std::find_if(options.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() { // Verifica si se debe pausar el juego. checkPauseInput(); demo_.enabled ? handleDemoMode() : handlePlayersInput(); // 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(InputType::SERVICE, INPUT_ALLOW_REPEAT, InputDeviceToUse::CONTROLLER, i) && input_->checkInput(InputType::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT, InputDeviceToUse::CONTROLLER, i)) { pause(!paused_); return; } // Comprueba el teclado if (input_->checkInput(InputType::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT, InputDeviceToUse::KEYBOARD)) { pause(!paused_); return; } } // Gestiona las entradas de los jugadores en el modo demo, incluyendo movimientos y disparos automáticos. void Game::handleDemoMode() { int index = 0; for (const auto &player : players_) { if (player->isPlaying()) { // Maneja el input específico del jugador en modo demo. handleDemoPlayerInput(player, index); } if (input_->checkAnyButtonPressed()) { section::name = section::Name::TITLE; // Salir del modo demo y regresar al menú principal. return; } ++index; } } // Procesa las entradas para un jugador específico durante el modo demo. void Game::handleDemoPlayerInput(const std::shared_ptr &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, BulletType bulletType) { if (player->canFire()) { player->setInput(bulletType == BulletType::UP ? InputType::FIRE_CENTER : bulletType == BulletType::LEFT ? InputType::FIRE_LEFT : InputType::FIRE_RIGHT); createBullet(player->getPosX() + (player->getWidth() / 2) - 6, player->getPosY() + (player->getHeight() / 2), bulletType, player->isPowerUp(), player->getId()); JA_PlaySound(Resource::get()->getSound("bullet.wav")); // Establece un tiempo de espera para el próximo disparo. player->setFireCooldown(10); } } // 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()) { // 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) { const auto &controller = options.controllers.at(player->getController()); const bool autofire = player->isPowerUp() || options.game.autofire; if (input_->checkInput(InputType::LEFT, INPUT_ALLOW_REPEAT, controller.type, controller.index)) { player->setInput(InputType::LEFT); #ifdef RECORDING demo_.keys.left = 1; #endif } else if (input_->checkInput(InputType::RIGHT, INPUT_ALLOW_REPEAT, controller.type, controller.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, 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, bool autofire, int controllerIndex) { if (input_->checkInput(InputType::FIRE_CENTER, autofire, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index)) { handleFireInput(player, BulletType::UP); #ifdef RECORDING demo_.keys.fire = 1; #endif } else if (input_->checkInput(InputType::FIRE_LEFT, autofire, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index)) { handleFireInput(player, BulletType::LEFT); #ifdef RECORDING demo_.keys.fire_left = 1; #endif } else if (input_->checkInput(InputType::FIRE_RIGHT, autofire, options.controllers[controllerIndex].type, options.controllers[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) { const auto controllerIndex = player->getController(); if (input_->checkInput(InputType::START, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index)) { player->setPlayingState(PlayerState::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.controllers[controllerIndex].type, options.controllers[controllerIndex].index) || input_->checkInput(InputType::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index) || input_->checkInput(InputType::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index)) { player->decContinueCounter(); } } // Procesa las entradas para la introducción del nombre del jugador. void Game::handleNameInput(const std::shared_ptr &player) { const auto controllerIndex = player->getController(); if (input_->checkInput(InputType::FIRE_LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index) || input_->checkInput(InputType::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index) || input_->checkInput(InputType::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index)) { if (player->getRecordNamePos() == 7) { player->setInput(InputType::START); addScoreToScoreBoard(player->getRecordName(), player->getScore()); const auto status = player->getPlayingState(); player->setPlayingState(status == PlayerState::ENTERING_NAME ? PlayerState::CONTINUE : PlayerState::GAME_COMPLETED); } else { player->setInput(InputType::RIGHT); } } else if (input_->checkInput(InputType::UP, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index)) { player->setInput(InputType::UP); } else if (input_->checkInput(InputType::DOWN, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index)) { player->setInput(InputType::DOWN); } else if (input_->checkInput(InputType::LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[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.controllers[controllerIndex].type, options.controllers[controllerIndex].index)) { player->setInput(InputType::START); addScoreToScoreBoard(player->getRecordName(), player->getScore()); const auto status = player->getPlayingState(); player->setPlayingState(status == PlayerState::ENTERING_NAME ? PlayerState::CONTINUE : PlayerState::GAME_COMPLETED); } } // Inicializa las variables para el modo DEMO void Game::initDemo(int player_id) { if (demo_.enabled) { // Aleatoriza la asignación del fichero con los datos del modo demostracion const auto demo1 = rand() % 2; const auto demo2 = (demo1 == 0) ? 1 : 0; demo_.data.emplace_back(Resource::get()->getDemoData(demo1)); demo_.data.emplace_back(Resource::get()->getDemoData(demo2)); // Selecciona una pantalla al azar { constexpr auto demos = 3; const auto demo = rand() % demos; const int stages[demos] = {0, 3, 5}; current_stage_ = stages[demo]; } // Actualiza el numero de globos explotados según la fase del modo demostración for (int i = 0; i < current_stage_; ++i) { balloons_popped_ += balloon_formations_->getStage(i).power_to_complete; } // Activa o no al otro jugador if (rand() % 2 == 0) { const auto other_player_id = player_id == 1 ? 2 : 1; auto other_player = getPlayer(other_player_id); other_player->setPlayingState(PlayerState::PLAYING); } // Asigna cafes a los jugadores for (auto &player : players_) { for (int i = 0; i < rand() % 3; ++i) player->giveExtraHit(); player->setInvulnerable(false); } // Deshabilita los sonidos JA_EnableSound(false); // Configura los marcadores scoreboard_->setMode(SCOREBOARD_LEFT_PANEL, ScoreboardMode::DEMO); scoreboard_->setMode(SCOREBOARD_RIGHT_PANEL, ScoreboardMode::DEMO); } // Modo grabar demo #ifdef RECORDING demo_.recording = true; #else demo_.recording = false; #endif demo_.counter = 0; } // Calcula el poder total necesario para completar el juego void Game::setTotalPower() { total_power_to_complete_game_ = 0; for (int i = 0; i < 10; ++i) { total_power_to_complete_game_ += balloon_formations_->getStage(i).power_to_complete; } } // Inicializa el marcador void Game::initScoreboard() { scoreboard_->setPos({param.scoreboard.x, param.scoreboard.y, param.scoreboard.w, param.scoreboard.h}); scoreboard_->setMode(SCOREBOARD_CENTER_PANEL, ScoreboardMode::STAGE_INFO); for (const auto &player : players_) { scoreboard_->setName(player->getScoreBoardPanel(), player->getName()); if (player->isWaiting()) scoreboard_->setMode(player->getScoreBoardPanel(), ScoreboardMode::WAITING); } } // Inicializa las opciones relacionadas con la dificultad void Game::initDifficultyVars() { // Variables relacionadas con la dificultad switch (difficulty_) { case GameDifficulty::EASY: { default_balloon_speed_ = BALLOON_SPEED[0]; difficulty_score_multiplier_ = 0.5f; scoreboard_->setColor(scoreboard_easy_color); break; } case GameDifficulty::NORMAL: { default_balloon_speed_ = BALLOON_SPEED[0]; difficulty_score_multiplier_ = 1.0f; scoreboard_->setColor(scoreboard_normal_color); break; } case GameDifficulty::HARD: { default_balloon_speed_ = BALLOON_SPEED[4]; difficulty_score_multiplier_ = 1.5f; scoreboard_->setColor(scoreboard_hard_color); break; } default: break; } balloon_speed_ = default_balloon_speed_; } // Inicializa los jugadores void Game::initPlayers(int player_id) { // Crea los dos jugadores const int y = param.game.play_area.rect.h - 30; players_.emplace_back(std::make_unique(1, param.game.play_area.first_quarter_x - 15, y, demo_.enabled, param.game.play_area.rect, player_textures_[0], player_animations_)); players_.back()->setScoreBoardPanel(SCOREBOARD_LEFT_PANEL); players_.back()->setName(lang::getText(53)); players_.back()->setController(getController(players_.back()->getId())); players_.emplace_back(std::make_unique(2, param.game.play_area.third_quarter_x - 15, y, demo_.enabled, param.game.play_area.rect, player_textures_[1], player_animations_)); players_.back()->setScoreBoardPanel(SCOREBOARD_RIGHT_PANEL); players_.back()->setName(lang::getText(54)); players_.back()->setController(getController(players_.back()->getId())); // Activa el jugador que coincide con el "player_id" auto player = getPlayer(player_id); player->setPlayingState(PlayerState::PLAYING); player->setInvulnerable(false); } // Crea dos globos gordos void Game::createTwoBigBalloons() { const auto set = balloon_formations_->getStage(0).balloon_pool.set[1]; const auto numEnemies = set.number_of_balloons; for (int i = 0; i < numEnemies; ++i) { auto p = set.init[i]; createBalloon(p.x, p.y, p.type, p.size, p.vel_x, balloon_speed_, p.creation_counter); } } // Pausa la música void Game::pauseMusic() { if (JA_GetMusicState() == JA_MUSIC_PLAYING && !demo_.enabled) JA_PauseMusic(); } // Reanuda la música void Game::resumeMusic() { if (JA_GetMusicState() == JA_MUSIC_PAUSED && !demo_.enabled) JA_ResumeMusic(); } // Detiene la música void Game::stopMusic() { if (!demo_.enabled) JA_StopMusic(); } // Actualiza las variables durante el modo demo void Game::updateDemo() { 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 // 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 el transcurso normal del juego void Game::updateGame() { if (!paused_) { #ifdef DEBUG if (auto_pop_balloons_ && state_ == GameState::PLAYING) { balloons_popped_ += 5; increaseStageCurrentPower(5); } #endif fade_->update(); updatePlayers(); checkPlayersStatusPlaying(); updateScoreboard(); updateBackground(); updateBalloons(); explosions_->update(); moveBullets(); updateItems(); updateStage(); updateGameOverState(); updateCompletedState(); updateSmartSprites(); updatePathSprites(); updateTimeStopped(); updateBalloonDeployCounter(); updateHelper(); checkBulletBalloonCollision(); updateMenace(); checkAndUpdateBalloonSpeed(); checkState(); cleanVectors(); } } // Vacía los vectores de elementos deshabilitados void Game::cleanVectors() { freeBullets(); freeBalloons(); freeItems(); freeSmartSprites(); freePathSprites(); }