Files
coffee_crisis_arcade_edition/source/sections/game.cpp
2025-09-30 20:41:35 +02:00

2088 lines
78 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "game.h"
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_SetRenderTarget, SDL_CreateTexture, SDL_Delay, SDL_DestroyTexture, SDL_EventType, SDL_GetRenderTarget, SDL_PollEvent, SDL_RenderTexture, SDL_SetTextureBlendMode, SDL_BLENDMODE_BLEND, SDL_Event, SDL_PixelFormat, SDL_Point, SDL_TextureAccess
#include <algorithm> // Para find, clamp, find_if, min, std::shuffle, std::iota
#include <array> // Para array
#include <cstdlib> // Para rand, size_t
#include <functional> // Para function
#include <iterator> // Para size
#include <memory> // Para shared_ptr, unique_ptr, __shared_ptr_access, allocator, make_unique, operator==, make_shared
#include <random> // std::random_device, std::default_random_engine
#include <utility> // Para move
#include "asset.h" // Para Asset
#include "audio.h" // Para Audio
#include "background.h" // Para Background
#include "balloon.h" // Para Balloon
#include "balloon_manager.h" // Para BalloonManager
#include "bullet.h" // Para Bullet, Bullet::Type, BulletMoveStatus
#include "bullet_manager.h" // Para BulletManager
#include "color.h" // Para Color, Colors::FLASH
#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 "hit.h" // Para Hit
#include "input.h" // Para Input
#include "input_types.h" // Para InputAction
#include "item.h" // Para Item, ItemType
#include "lang.h" // Para getText
#include "manage_hiscore_table.h" // Para HiScoreEntry, ManageHiScoreTable
#include "param.h" // Para Param, param, ParamGame, ParamScoreboard, ParamFade, ParamBalloon
#include "path_sprite.h" // Para Path, PathSprite, createPath, PathType
#include "pause_manager.h" // Para PauseManager
#include "player.h" // Para Player
#include "resource.h" // Para Resource
#include "scoreboard.h" // Para Scoreboard
#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, init, power_can_be_added, stages
#include "tabe.h" // Para Tabe
#include "text.h" // Para Text
#include "texture.h" // Para Texture
#include "ui/service_menu.h" // Para ServiceMenu
#ifdef _DEBUG
#include <iostream> // Para std::cout
#include "ui/notifier.h" // Para Notifier
#endif
// Constructor
Game::Game(Player::Id player_id, int current_stage, bool demo_enabled)
: renderer_(Screen::get()->getRenderer()),
screen_(Screen::get()),
input_(Input::get()),
canvas_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.play_area.rect.w, param.game.play_area.rect.h)),
pause_manager_(std::make_unique<PauseManager>([this](bool is_paused) { onPauseStateChanged(is_paused); })),
stage_manager_(std::make_unique<StageManager>()),
balloon_manager_(std::make_unique<BalloonManager>(stage_manager_.get())),
bullet_manager_(std::make_unique<BulletManager>()),
background_(std::make_unique<Background>(stage_manager_->getPowerNeededToReachStage(stage_manager_->getTotalStages() - 1))),
fade_in_(std::make_unique<Fade>()),
fade_out_(std::make_unique<Fade>()),
tabe_(std::make_unique<Tabe>()),
hit_(Hit(Resource::get()->getTexture("hit.png"))) {
// Pasa variables
demo_.enabled = demo_enabled;
// Otras variables
Section::name = Section::Name::GAME;
Section::options = Section::Options::NONE;
stage_manager_->initialize(Asset::get()->get("stages.txt"));
stage_manager_->setPowerChangeCallback([this](int amount) { background_->incrementProgress(amount); });
stage_manager_->jumpToStage(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 ? static_cast<int>(DEMO_FADE_PRE_DURATION_S * 1000) : 0);
fade_in_->setPostDuration(0);
fade_in_->setType(Fade::Type::RANDOM_SQUARE2);
fade_in_->setMode(Fade::Mode::IN);
#ifndef RECORDING
fade_in_->activate();
#endif
fade_out_->setColor(param.fade.color);
fade_out_->setPostDuration(param.fade.post_duration_ms);
fade_out_->setType(Fade::Type::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();
// Registra callbacks
ServiceMenu::get()->setStateChangeCallback([this](bool is_active) {
// Solo aplicar pausa si NO estamos en modo demo
if (!demo_.enabled) {
pause_manager_->setServiceMenuPause(is_active);
}
});
// Configura callbacks del BulletManager
bullet_manager_->setTabeCollisionCallback([this](const std::shared_ptr<Bullet>& bullet) {
return checkBulletTabeCollision(bullet);
});
bullet_manager_->setBalloonCollisionCallback([this](const std::shared_ptr<Bullet>& bullet) {
return checkBulletBalloonCollision(bullet);
});
bullet_manager_->setOutOfBoundsCallback([this](const std::shared_ptr<Bullet>& bullet) {
getPlayer(static_cast<Player::Id>(bullet->getOwner()))->decScoreMultiplier();
});
#ifdef RECORDING
setState(State::PLAYING);
#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;
if (Options::audio.enabled) {
// Musica
Audio::get()->stopMusic();
Audio::get()->setMusicVolume(Options::audio.music.volume);
// Sonidos
Audio::get()->stopAllSounds();
Audio::get()->setSoundVolume(Options::audio.sound.volume, Audio::Group::GAME);
}
}
ServiceMenu::get()->setStateChangeCallback(nullptr);
#ifdef RECORDING
saveDemoFile(Asset::get()->get("demo1.bin"), demo_.data.at(0));
#endif
Scoreboard::destroy();
SDL_DestroyTexture(canvas_);
// Desregistra los jugadores de Options
Options::keyboard.clearPlayers();
Options::gamepad_manager.clearPlayers();
}
// Asigna texturas y animaciones
void Game::setResources() {
// Texturas - Game_text
game_text_textures_.clear();
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_.clear();
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"));
player_textures_.clear();
// Texturas - Player1
std::vector<std::shared_ptr<Texture>> player1_textures;
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal0"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal1"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal2"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal3"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_power.png"));
player_textures_.push_back(player1_textures);
// Texturas - Player2
std::vector<std::shared_ptr<Texture>> player2_textures;
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal0"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal1"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal2"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal3"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_power.png"));
player_textures_.push_back(player2_textures);
// Animaciones -- Jugador
player_animations_.clear();
player_animations_.emplace_back(Resource::get()->getAnimation("player.ani"));
player_animations_.emplace_back(Resource::get()->getAnimation("player_power.ani"));
// Animaciones -- Items
item_animations_.clear();
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
if (!hi_score_achieved_) {
hi_score_achieved_ = true;
playSound("hi_score_achieved.wav"); // Emite un sonido
createMessage({paths_.at(8), paths_.at(9)}, Resource::get()->getTexture("game_text_new_record")); // CRea un mensaje
}
}
}
}
// Actualiza las variables del jugador
void Game::updatePlayers(float deltaTime) {
for (auto& player : players_) {
player->update(deltaTime);
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_timer_ > 0) {
balloon_manager_->popBalloon(balloon);
}
// En caso contrario, el jugador ha sido golpeado por un globo activo
else {
handlePlayerCollision(player, balloon);
if (demo_.enabled && allPlayersAreNotPlaying()) {
fade_out_->setType(Fade::Type::RANDOM_SQUARE2);
fade_out_->setPostDuration(500);
fade_out_->activate();
}
}
}
// Comprueba las colisiones entre el jugador y los items
checkPlayerItemCollision(player);
}
}
// Organiza la lista de jugadores
sortPlayersByZOrder();
}
// Dibuja a los jugadores
void Game::renderPlayers() {
for (auto& player : players_) {
player->render();
}
}
// Comprueba si hay cambio de fase y actualiza las variables
void Game::updateStage() {
if (!stage_manager_->isCurrentStageCompleted()) {
return; // No hay cambio de fase
}
// Calcular poder sobrante antes de avanzar
int power_overflow = 0;
auto current_stage = stage_manager_->getCurrentStage();
if (current_stage.has_value()) {
int current_power = stage_manager_->getCurrentPower();
int power_needed = current_stage->getPowerToComplete();
power_overflow = current_power - power_needed; // Poder que sobra
}
// Intentar avanzar a la siguiente fase
if (!stage_manager_->advanceToNextStage()) {
// No se pudo avanzar (probablemente juego completado)
return;
}
// Aplicar el poder sobrante a la nueva fase
if (power_overflow > 0) {
stage_manager_->addPower(power_overflow);
}
// Efectos de cambio de fase
playSound("stage_change.wav");
balloon_manager_->resetBalloonSpeed();
screen_->flash(Colors::FLASH, 0.05F);
screen_->shake();
// Obtener datos de la nueva fase
size_t current_stage_index = stage_manager_->getCurrentStageIndex();
size_t total_stages = stage_manager_->getTotalStages();
// Escribir texto por pantalla
if (current_stage_index < total_stages) { // No es la última fase
std::vector<Path> paths = {paths_.at(2), paths_.at(3)};
if (current_stage_index == total_stages - 1) { // Penúltima fase (será la última)
createMessage(paths, Resource::get()->getTexture("game_text_last_stage"));
} else {
auto text = Resource::get()->getText("04b_25_2x_enhanced");
const std::string CAPTION = Lang::getText("[GAME_TEXT] 2") +
std::to_string(total_stages - current_stage_index) +
Lang::getText("[GAME_TEXT] 2A");
createMessage(paths, text->writeDXToTexture(Text::STROKE, CAPTION, -4, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
}
}
// Modificar color de fondo en la última fase
if (current_stage_index == total_stages - 1) { // Última fase
background_->setColor(Color(0xdd, 0x19, 0x1d).DARKEN());
background_->setAlpha(96);
}
}
// Actualiza las variables y sistemas durante el estado de fin de partida
void Game::updateGameStateGameOver(float deltaTime) {
fade_out_->update();
updatePlayers(deltaTime);
updateScoreboard();
updateBackground(deltaTime);
balloon_manager_->update(deltaTime);
tabe_->update(deltaTime);
bullet_manager_->update(deltaTime);
updateItems(deltaTime);
updateSmartSprites(deltaTime);
updatePathSprites(deltaTime);
updateTimeStopped(deltaTime);
bullet_manager_->checkCollisions();
cleanVectors();
if (game_over_timer_ < GAME_OVER_DURATION_S) {
game_over_timer_ += deltaTime; // Incremento time-based primero
handleGameOverEvents(); // Maneja eventos después del incremento
}
if (Options::audio.enabled) {
const float progress = std::min(game_over_timer_ / GAME_OVER_DURATION_S, 1.0f);
const float VOL = 64.0f * (1.0f - progress);
Audio::get()->setSoundVolume(static_cast<int>(VOL), Audio::Group::GAME);
}
if (fade_out_->hasEnded()) {
if (game_completed_timer_ > 0) {
Section::name = Section::Name::CREDITS; // Los jugadores han completado el juego
} else {
Section::name = Section::Name::HI_SCORE_TABLE; // La partida ha terminado con la derrota de los jugadores
}
Section::options = Section::Options::HI_SCORE_AFTER_PLAYING;
}
}
// Gestiona eventos para el estado del final del juego
void Game::updateGameStateCompleted(float deltaTime) {
updatePlayers(deltaTime);
updateScoreboard();
updateBackground(deltaTime);
balloon_manager_->update(deltaTime);
tabe_->update(deltaTime);
bullet_manager_->update(deltaTime);
updateItems(deltaTime);
updateSmartSprites(deltaTime);
updatePathSprites(deltaTime);
cleanVectors();
// Maneja eventos del juego completado
handleGameCompletedEvents();
// Si los jugadores ya no estan y no quedan mensajes en pantalla
if (allPlayersAreGameOver() && path_sprites_.empty()) {
setState(State::GAME_OVER);
}
// Incrementa el acumulador al final
game_completed_timer_ += deltaTime;
}
// Comprueba el estado del juego
void Game::checkState() {
if (state_ != State::COMPLETED && stage_manager_->isGameCompleted()) {
setState(State::COMPLETED);
background_->setState(Background::State::COMPLETED);
}
if (state_ != State::GAME_OVER && allPlayersAreGameOver()) {
setState(State::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> {
#ifndef RECORDING
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
}
}
}
#endif
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, Options::settings.hi_score_table.back().score);
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, Options::settings.hi_score_table.back().score);
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, Options::settings.hi_score_table.back().score);
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, Options::settings.hi_score_table.back().score);
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, Options::settings.hi_score_table.back().score);
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();
}
}
}
}
// Maneja la colisión entre bala y Tabe
auto Game::checkBulletTabeCollision(const std::shared_ptr<Bullet>& bullet) -> bool {
if (!tabe_->isEnabled()) {
return false;
}
if (!checkCollision(bullet->getCollider(), tabe_->getCollider())) {
return false;
}
tabe_->setState(Tabe::State::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(const 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(const std::shared_ptr<Bullet>& bullet, const std::shared_ptr<Balloon>& balloon) {
auto player = getPlayer(static_cast<Player::Id>(bullet->getOwner()));
handleItemDrop(balloon, player);
handleBalloonDestruction(balloon, player);
bullet->disable();
}
// Maneja la caída de items cuando se destruye un globo
void Game::handleItemDrop(const std::shared_ptr<Balloon>& balloon, const 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());
playSound("item_drop.wav");
} 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, const std::shared_ptr<Player>& player) {
if (player->isPlaying()) {
auto const SCORE = balloon_manager_->popBalloon(std::move(balloon)) * player->getScoreMultiplier() * difficulty_score_multiplier_;
player->addScore(SCORE, Options::settings.hi_score_table.back().score);
player->incScoreMultiplier();
}
setMenace();
updateHiScore();
}
// Actualiza los items
void Game::updateItems(float deltaTime) {
for (auto& item : items_) {
if (item->isEnabled()) {
item->update(deltaTime);
if (item->isOnFloor()) {
playSound("title.wav");
screen_->shake(1, 0.033f, 0.067f); // desp=1, delay=0.033s, length=0.067s
}
}
}
}
// 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]));
}
// 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, const 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, 1.667f, easeOutQuint, 0); // 100 frames → 1.667s
path_sprites_.back()->addPath(Y1, Y2, PathType::VERTICAL, x, 1.333f, easeInQuint, 0); // 80 frames → 1.333s
path_sprites_.back()->enable();
}
// Crea un objeto PathSprite
void Game::createMessage(const std::vector<Path>& paths, const 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(Item::WIDTH);
smart_sprites_.back()->setHeight(Item::HEIGHT);
smart_sprites_.back()->setVelX((-1.0F + ((rand() % 5) * 0.5F)) * 60.0f); // Convertir a pixels/segundo
smart_sprites_.back()->setVelY(-4.0F * 60.0f); // Convertir a pixels/segundo
smart_sprites_.back()->setAccelX(0.0F);
smart_sprites_.back()->setAccelY(0.2F * 60.0f * 60.0f); // Convertir a pixels/segundo² (0.2 × 60²)
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()->setFinishedDelay(0.0F);
smart_sprites_.back()->setSpriteClip(0, Item::HEIGHT, Item::WIDTH, Item::HEIGHT);
smart_sprites_.back()->setRotatingCenter({Item::WIDTH / 2, Item::HEIGHT / 2});
smart_sprites_.back()->setRotate(true);
smart_sprites_.back()->setRotateAmount(90.0);
}
// Actualiza los SmartSprites
void Game::updateSmartSprites(float deltaTime) {
for (auto& sprite : smart_sprites_) {
sprite->update(deltaTime);
}
}
// Pinta los SmartSprites activos
void Game::renderSmartSprites() {
for (auto& sprite : smart_sprites_) {
sprite->render();
}
}
// Actualiza los PathSprites
void Game::updatePathSprites(float deltaTime) {
for (auto& sprite : path_sprites_) {
sprite->update(deltaTime);
}
}
// 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, std::shared_ptr<Balloon>& balloon) {
if (!player->isPlaying() || player->isInvulnerable()) {
return; // Si no está jugando o tiene inmunidad, no hace nada
}
// Si tiene cafes
if (player->hasExtraHit()) {
// Lo pierde
player->removeExtraHit();
throwCoffee(player->getPosX() + (Player::WIDTH / 2), player->getPosY() + (Player::HEIGHT / 2));
playSound("coffee_out.wav");
screen_->shake();
} else {
// Si no tiene cafes, muere
playSound("player_collision.wav");
if (param.game.hit_stop) {
pauseMusic();
SDL_Delay(param.game.hit_stop_ms);
resumeMusic();
}
screen_->shake();
playSound("voice_no.wav");
player->setPlayingState(Player::State::ROLLING);
sendPlayerToTheBack(player);
if (allPlayersAreNotPlaying()) {
stage_manager_->disablePowerCollection();
}
}
}
// Actualiza el estado del tiempo detenido
void Game::updateTimeStopped(float deltaTime) {
static constexpr float WARNING_THRESHOLD_S = 2.0f; // 120 frames a 60fps → segundos
static constexpr float CLOCK_SOUND_INTERVAL_S = 0.5f; // 30 frames a 60fps → segundos
static constexpr float COLOR_FLASH_INTERVAL_S = 0.25f; // 15 frames a 60fps → segundos
if (time_stopped_timer_ > 0) {
time_stopped_timer_ -= deltaTime;
// Fase de advertencia (últimos 2 segundos)
if (time_stopped_timer_ <= WARNING_THRESHOLD_S) {
static float last_sound_time = 0.0f;
// CLAC al entrar en fase de advertencia
if (!time_stopped_flags_.warning_phase_started) {
playSound("clock.wav");
time_stopped_flags_.warning_phase_started = true;
last_sound_time = 0.0f; // Reset para empezar el ciclo rápido
}
last_sound_time += deltaTime;
if (last_sound_time >= CLOCK_SOUND_INTERVAL_S) {
balloon_manager_->normalColorsToAllBalloons();
playSound("clock.wav");
last_sound_time = 0.0f;
time_stopped_flags_.color_flash_sound_played = false; // Reset flag para el próximo intervalo
} else if (last_sound_time >= COLOR_FLASH_INTERVAL_S && !time_stopped_flags_.color_flash_sound_played) {
balloon_manager_->reverseColorsToAllBalloons();
playSound("clock.wav");
time_stopped_flags_.color_flash_sound_played = true; // Evita que suene múltiples veces
}
} else {
// Fase normal - solo sonido ocasional
static float sound_timer = 0.0f;
sound_timer += deltaTime;
if (sound_timer >= CLOCK_SOUND_INTERVAL_S) {
playSound("clock.wav");
sound_timer = 0.0f;
}
}
// Si el timer llega a 0 o menos, desactivar
if (time_stopped_timer_ <= 0) {
playSound("clock.wav"); // CLAC final
disableTimeStopItem();
}
}
}
// Actualiza toda la lógica del juego
void Game::update(float deltaTime) {
screen_->update(deltaTime); // Actualiza el objeto screen
Audio::update(); // Actualiza el objeto audio
updateDemo(deltaTime);
#ifdef RECORDING
updateRecording(deltaTime);
#endif
updateGameStates(deltaTime);
fillCanvas();
}
// Dibuja el juego
void Game::render() {
screen_->start(); // Prepara para empezar a dibujar en la textura de juego
SDL_RenderTexture(renderer_, canvas_, nullptr, &param.game.play_area.rect); // Copia la textura con la zona de juego a la pantalla
scoreboard_->render(); // Dibuja el marcador
fade_in_->render(); // Dibuja el fade de entrada
fade_out_->render(); // Dibuja el fade de salida
screen_->render(); // Vuelca el contenido del renderizador en pantalla
}
// Actualiza los estados del juego
void Game::updateGameStates(float deltaTime) {
if (!pause_manager_->isPaused()) {
switch (state_) {
case State::FADE_IN:
updateGameStateFadeIn(deltaTime);
break;
case State::ENTERING_PLAYER:
updateGameStateEnteringPlayer(deltaTime);
break;
case State::SHOWING_GET_READY_MESSAGE:
updateGameStateShowingGetReadyMessage(deltaTime);
break;
case State::PLAYING:
updateGameStatePlaying(deltaTime);
break;
case State::COMPLETED:
updateGameStateCompleted(deltaTime);
break;
case State::GAME_OVER:
updateGameStateGameOver(deltaTime);
break;
default:
break;
}
}
}
// Actualiza el fondo
void Game::updateBackground(float deltaTime) {
background_->update(deltaTime);
}
// Dibuja los elementos de la zona de juego en su textura
void Game::fillCanvas() {
// Dibuja el contenido de la zona de juego en su textura
auto* temp = SDL_GetRenderTarget(renderer_);
SDL_SetRenderTarget(renderer_, canvas_);
// Dibuja los objetos
background_->render();
balloon_manager_->render();
renderSmartSprites(); // El cafe que sale cuando te golpean
renderItems();
tabe_->render();
bullet_manager_->render();
renderPlayers();
renderPathSprites();
// Deja el renderizador apuntando donde estaba
SDL_SetRenderTarget(renderer_, temp);
}
// Habilita el efecto del item de detener el tiempo
void Game::enableTimeStopItem() {
balloon_manager_->stopAllBalloons();
balloon_manager_->reverseColorsToAllBalloons();
time_stopped_timer_ = TIME_STOPPED_DURATION_S;
time_stopped_flags_.reset(); // Resetea flags al activar el item
}
// Deshabilita el efecto del item de detener el tiempo
void Game::disableTimeStopItem() {
time_stopped_timer_ = 0;
balloon_manager_->startAllBalloons();
balloon_manager_->normalColorsToAllBalloons();
}
// Calcula el deltatime en segundos
auto Game::calculateDeltaTime() -> float {
const Uint64 current_time = SDL_GetTicks();
const float delta_time_ms = static_cast<float>(current_time - last_time_);
last_time_ = current_time;
return delta_time_ms / 1000.0f; // Convertir de milisegundos a segundos
}
// Bucle para el juego
void Game::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::GAME) {
const float delta_time = calculateDeltaTime();
checkInput();
update(delta_time);
handleEvents(); // 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), 0.5f);
paths_.emplace_back(createPath(X1, X2, PathType::HORIZONTAL, Y, 80, easeInQuint), 0);
}
// Recorrido para el texto de "Last Stage!" o de "X stages left" (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), 0.5f);
paths_.emplace_back(createPath(Y1, Y2, PathType::VERTICAL, X, 80, easeInQuint), 0);
}
// Recorrido para el texto de "Congratulations!!" (4,5)
{
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);
paths_.emplace_back(createPath(X0, X1, PathType::HORIZONTAL, Y, 80, easeOutQuint), 7.0F);
paths_.emplace_back(createPath(X1, X2, PathType::HORIZONTAL, Y, 80, easeInQuint), 0);
}
// Recorrido para el texto de "1.000.000 points!" (6,7)
{
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);
paths_.emplace_back(createPath(X0, X1, PathType::HORIZONTAL, Y, 80, easeOutQuint), 7.0F);
paths_.emplace_back(createPath(X1, X2, PathType::HORIZONTAL, Y, 80, easeInQuint), 0);
}
// Recorrido para el texto de "New Record!" (8,9)
{
const auto& texture = Resource::get()->getTexture("game_text_new_record");
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) - (H * 2);
paths_.emplace_back(createPath(X0, X1, PathType::HORIZONTAL, Y, 80, easeOutQuint), 1.0f);
paths_.emplace_back(createPath(X1, X2, PathType::HORIZONTAL, Y, 80, easeInQuint), 0);
}
// Recorrido para el texto de "Game Over" (10,11)
{
const auto& texture = Resource::get()->getTexture("game_text_game_over");
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), 2.0f);
paths_.emplace_back(createPath(Y1, Y2, PathType::VERTICAL, X, 80, easeInQuint), 0);
}
}
// Actualiza las variables de ayuda
void Game::updateHelper() {
// Solo ofrece ayuda cuando la amenaza es elevada
if (menace_ > 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::handleEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_WINDOW_FOCUS_LOST: {
pause_manager_->setFocusLossPause(!demo_.enabled);
break;
}
case SDL_EVENT_WINDOW_FOCUS_GAINED: {
pause_manager_->setFocusLossPause(false);
break;
}
default:
break;
}
#ifdef _DEBUG
handleDebugEvents(event);
#endif
GlobalEvents::handle(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_manager_->getCurrentStageIndex() + 1);
scoreboard_->setPower(stage_manager_->getCurrentStageProgressFraction());
scoreboard_->setHiScore(hi_score_.score);
scoreboard_->setHiScoreName(hi_score_.name);
scoreboard_->update();
}
// Pone en el marcador el nombre del primer jugador de la tabla
void Game::updateHiScoreName() {
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(Player::State::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(Player::State::GAME_OVER);
}
}
// Comprobar estado de ambos jugadores
checkAndUpdatePlayerStatus(0, 1);
checkAndUpdatePlayerStatus(1, 0);
}
// Obtiene un jugador a partir de su "id"
auto Game::getPlayer(Player::Id 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(Player::Id player_id) -> int {
switch (player_id) {
case Player::Id::PLAYER1:
return 0;
case Player::Id::PLAYER2:
return 1;
default:
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 (const auto& gamepad : input_->getGamepads()) {
if (input_->checkAction(Input::Action::PAUSE, Input::DO_NOT_ALLOW_REPEAT, Input::DO_NOT_CHECK_KEYBOARD, gamepad)) {
pause_manager_->togglePlayerPause();
return;
}
}
// Comprueba el teclado
if (input_->checkAction(Input::Action::PAUSE, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
pause_manager_->togglePlayerPause();
return;
}
}
// Gestiona las entradas de los jugadores en el modo demo para saltarse la demo.
void Game::demoHandlePassInput() {
if (input_->checkAnyButton()) {
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() {
for (const auto& player : players_) {
if (player->isPlaying()) {
demoHandlePlayerInput(player, player->getDemoFile()); // Maneja el input específico del jugador en modo demo.
}
}
}
// 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.at(index).at(demo_.index);
if (demo_data.left == 1) {
player->setInput(Input::Action::LEFT);
} else if (demo_data.right == 1) {
player->setInput(Input::Action::RIGHT);
} else if (demo_data.no_input == 1) {
player->setInput(Input::Action::NONE);
}
if (demo_data.fire == 1) {
handleFireInput(player, Bullet::Type::UP);
} else if (demo_data.fire_left == 1) {
handleFireInput(player, Bullet::Type::LEFT);
} else if (demo_data.fire_right == 1) {
handleFireInput(player, Bullet::Type::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, Bullet::Type type) {
if (player->canFire()) {
SDL_Point bullet = {0, 0};
switch (type) {
case Bullet::Type::UP:
player->setInput(Input::Action::FIRE_CENTER);
bullet.x = 2 + player->getPosX() + (Player::WIDTH - Bullet::WIDTH) / 2;
bullet.y = player->getPosY() - (Bullet::HEIGHT / 2);
break;
case Bullet::Type::LEFT:
player->setInput(Input::Action::FIRE_LEFT);
bullet.x = player->getPosX() - (Bullet::WIDTH / 2);
bullet.y = player->getPosY();
break;
case Bullet::Type::RIGHT:
player->setInput(Input::Action::FIRE_RIGHT);
bullet.x = player->getPosX() + Player::WIDTH - (Bullet::WIDTH / 2);
bullet.y = player->getPosY();
break;
default:
break;
}
bullet_manager_->createBullet(bullet.x, bullet.y, type, player->getNextBulletColor(), static_cast<int>(player->getId()));
playSound(player->getBulletSoundFile());
// 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->startFiringSystem(cant_fire_counter); // Sistema de disparo de dos líneas
}
}
// Gestiona las entradas de todos los jugadores en el modo normal (fuera del modo demo)
void Game::handlePlayersInput() {
for (const auto& player : players_) {
if (player->isPlaying()) {
handleNormalPlayerInput(player); // Maneja el input de los jugadores en modo normal
} else if (player->isContinue()) {
handlePlayerContinueInput(player); // Gestiona la continuación del jugador
} else if (player->isWaiting()) {
handlePlayerWaitingInput(player); // Gestiona la (re)entrada del jugador
} else if (player->isEnteringName() || player->isEnteringNameGameCompleted() || player->isShowingName()) {
handleNameInput(player); // Gestiona la introducción del nombre del jugador
}
}
}
// Maneja las entradas de movimiento y disparo para un jugador en modo normal.
void Game::handleNormalPlayerInput(const std::shared_ptr<Player>& player) {
if (input_->checkAction(Input::Action::LEFT, Input::ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
player->setInput(Input::Action::LEFT);
#ifdef RECORDING
demo_.keys.left = 1;
#endif
} else if (input_->checkAction(Input::Action::RIGHT, Input::ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
player->setInput(Input::Action::RIGHT);
#ifdef RECORDING
demo_.keys.right = 1;
#endif
} else {
player->setInput(Input::Action::NONE);
#ifdef RECORDING
demo_.keys.no_input = 1;
#endif
}
const bool AUTOFIRE = player->isPowerUp() || Options::settings.autofire;
handleFireInputs(player, AUTOFIRE); // 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) {
if (!player) {
return;
}
if (input_->checkAction(Input::Action::FIRE_CENTER, autofire, player->getUsesKeyboard(), player->getGamepad())) {
handleFireInput(player, Bullet::Type::UP);
#ifdef RECORDING
demo_.keys.fire = 1;
#endif
return;
}
if (input_->checkAction(Input::Action::FIRE_LEFT, autofire, player->getUsesKeyboard(), player->getGamepad())) {
handleFireInput(player, Bullet::Type::LEFT);
#ifdef RECORDING
demo_.keys.fire_left = 1;
#endif
return;
}
if (input_->checkAction(Input::Action::FIRE_RIGHT, autofire, player->getUsesKeyboard(), player->getGamepad())) {
handleFireInput(player, Bullet::Type::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::handlePlayerContinueInput(const std::shared_ptr<Player>& player) {
if (input_->checkAction(Input::Action::START, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
player->setPlayingState(Player::State::RECOVER);
player->addCredit();
sendPlayerToTheFront(player);
return;
}
// Disminuye el contador de continuación si se presiona cualquier botón de disparo.
if (input_->checkAction(Input::Action::FIRE_LEFT, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad()) ||
input_->checkAction(Input::Action::FIRE_CENTER, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad()) ||
input_->checkAction(Input::Action::FIRE_RIGHT, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
if (player->getContinueCounter() < param.scoreboard.skip_countdown_value) {
player->decContinueCounter();
}
}
}
// Maneja la continuación del jugador cuando no está jugando, permitiendo que continúe si se pulsa el botón de inicio.
void Game::handlePlayerWaitingInput(const std::shared_ptr<Player>& player) {
if (input_->checkAction(Input::Action::START, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
player->setPlayingState(Player::State::ENTERING_SCREEN);
player->addCredit();
sendPlayerToTheFront(player);
}
}
// Procesa las entradas para la introducción del nombre del jugador.
void Game::handleNameInput(const std::shared_ptr<Player>& player) {
if (input_->checkAction(Input::Action::FIRE_LEFT, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
if (player->isShowingName()) {
player->passShowingName();
return;
}
if (player->getEnterNamePositionOverflow()) {
player->setInput(Input::Action::START);
player->setPlayingState(Player::State::SHOWING_NAME);
playSound("service_menu_select.wav");
updateHiScoreName();
return;
}
player->setInput(Input::Action::RIGHT);
playSound("service_menu_select.wav");
return;
}
if (input_->checkAction(Input::Action::FIRE_CENTER, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad()) ||
input_->checkAction(Input::Action::FIRE_RIGHT, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
if (player->isShowingName()) {
player->passShowingName();
return;
}
player->setInput(Input::Action::LEFT);
playSound("service_menu_back.wav");
return;
}
if (input_->checkAction(Input::Action::UP, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
player->setInput(Input::Action::UP);
playSound("service_menu_move.wav");
return;
}
if (input_->checkAction(Input::Action::DOWN, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
player->setInput(Input::Action::DOWN);
playSound("service_menu_move.wav");
return;
}
if (input_->checkAction(Input::Action::START, Input::DO_NOT_ALLOW_REPEAT, player->getUsesKeyboard(), player->getGamepad())) {
if (player->isShowingName()) {
player->passShowingName();
return;
}
player->setInput(Input::Action::START);
player->setPlayingState(Player::State::SHOWING_NAME);
playSound("name_input_accept.wav");
updateHiScoreName();
}
}
// Inicializa las variables para el modo DEMO
void Game::initDemo(Player::Id player_id) {
#ifdef RECORDING
// En modo grabación, inicializar vector vacío para almacenar teclas
demo_.data.emplace_back(); // Vector vacío para grabación
demo_.data.at(0).reserve(TOTAL_DEMO_DATA); // Reservar espacio para 2000 elementos
#endif
if (demo_.enabled) {
// Cambia el estado del juego
setState(State::PLAYING);
#ifndef RECORDING
// En modo juego: cargar todas las demos y asignar una diferente a cada jugador
auto const NUM_DEMOS = Asset::get()->getListByType(Asset::Type::DEMODATA).size();
for (size_t num_demo = 0; num_demo < NUM_DEMOS; ++num_demo) {
demo_.data.emplace_back(Resource::get()->getDemoData(num_demo));
}
// Crear índices mezclados para asignación aleatoria
std::vector<size_t> demo_indices(NUM_DEMOS);
std::iota(demo_indices.begin(), demo_indices.end(), 0);
std::random_device rd;
std::default_random_engine rng(rd());
std::shuffle(demo_indices.begin(), demo_indices.end(), rng);
// Asignar demos a jugadores (round-robin si hay más jugadores que demos)
for (size_t i = 0; i < players_.size(); ++i) {
size_t demo_index = demo_indices[i % NUM_DEMOS];
players_.at(i)->setDemoFile(demo_index);
}
#endif
// Selecciona una pantalla al azar
constexpr auto NUM_STAGES = 3;
const auto STAGE = rand() % NUM_STAGES;
constexpr std::array<float, NUM_STAGES> STAGES = {0.005F, 0.32F, 0.53F};
stage_manager_->setTotalPower(stage_manager_->getTotalPowerNeededToCompleteGame() * STAGES.at(STAGE));
background_->setProgress(stage_manager_->getTotalPower());
// Activa o no al otro jugador
if (rand() % 3 != 0) {
const auto OTHER_PLAYER_ID = player_id == Player::Id::PLAYER1 ? Player::Id::PLAYER2 : Player::Id::PLAYER1;
auto other_player = getPlayer(OTHER_PLAYER_ID);
other_player->setPlayingState(Player::State::PLAYING);
}
// Asigna cafes a los jugadores
for (auto& player : players_) {
if (player->isPlaying()) {
for (int i = 0; i < rand() % 3; ++i) {
player->giveExtraHit();
}
}
player->setInvulnerable(true);
}
// Configura los marcadores
scoreboard_->setMode(Scoreboard::Id::LEFT, Scoreboard::Mode::DEMO);
scoreboard_->setMode(Scoreboard::Id::RIGHT, Scoreboard::Mode::DEMO);
// Silencia los globos
balloon_manager_->setSounds(false);
}
// Modo grabar demo
#ifdef RECORDING
demo_.recording = true;
#else
demo_.recording = false;
#endif
demo_.index = 0;
}
// Inicializa el marcador
void Game::initScoreboard() {
scoreboard_->setPos(param.scoreboard.rect);
scoreboard_->setMode(Scoreboard::Id::CENTER, Scoreboard::Mode::STAGE_INFO);
for (const auto& player : players_) {
scoreboard_->setName(player->getScoreBoardPanel(), player->getName());
if (player->isWaiting()) {
scoreboard_->setMode(player->getScoreBoardPanel(), Scoreboard::Mode::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::GAME_TEMPO.at(0));
difficulty_score_multiplier_ = 0.5F;
scoreboard_->setColor(param.scoreboard.easy_color);
break;
}
case Difficulty::Code::NORMAL: {
balloon_manager_->setDefaultBalloonSpeed(Balloon::GAME_TEMPO.at(0));
difficulty_score_multiplier_ = 1.0F;
scoreboard_->setColor(param.scoreboard.normal_color);
break;
}
case Difficulty::Code::HARD: {
balloon_manager_->setDefaultBalloonSpeed(Balloon::GAME_TEMPO.at(4));
difficulty_score_multiplier_ = 1.5F;
scoreboard_->setColor(param.scoreboard.hard_color);
break;
}
default:
break;
}
balloon_manager_->resetBalloonSpeed();
}
// Inicializa los jugadores
void Game::initPlayers(Player::Id player_id) {
const int Y = param.game.play_area.rect.h - Player::HEIGHT + 1; // Se hunde un pixel para esconder el outline de los pies
const Player::State STATE = demo_.enabled ? Player::State::PLAYING : Player::State::ENTERING_SCREEN;
// Crea al jugador uno y lo pone en modo espera
Player::Config config_player1{
.id = Player::Id::PLAYER1,
#ifdef RECORDING
.x = param.game.play_area.center_x - (Player::WIDTH / 2),
#else
.x = param.game.play_area.first_quarter_x - (Player::WIDTH / 2),
#endif
.y = Y,
.demo = demo_.enabled,
.play_area = &param.game.play_area.rect,
.texture = player_textures_.at(0),
.animations = player_animations_,
.hi_score_table = &Options::settings.hi_score_table,
.glowing_entry = &Options::settings.glowing_entries.at(static_cast<int>(Player::Id::PLAYER1) - 1),
.stage_info = stage_manager_.get()};
auto player1 = std::make_unique<Player>(config_player1);
player1->setBulletColors(Bullet::Color::YELLOW, Bullet::Color::GREEN);
player1->setBulletSoundFile("bullet1p.wav");
player1->setScoreBoardPanel(Scoreboard::Id::LEFT);
player1->setName(Lang::getText("[SCOREBOARD] 1"));
player1->setGamepad(Options::gamepad_manager.getGamepad(Player::Id::PLAYER1).instance);
player1->setUsesKeyboard(Player::Id::PLAYER1 == Options::keyboard.player_id);
#ifdef RECORDING
player1->setPlayingState(Player::State::PLAYING);
#else
player1->setPlayingState((player_id == Player::Id::BOTH_PLAYERS || player_id == Player::Id::PLAYER1) ? STATE : Player::State::WAITING);
#endif
// Crea al jugador dos y lo pone en modo espera
Player::Config config_player2{
.id = Player::Id::PLAYER2,
.x = param.game.play_area.third_quarter_x - (Player::WIDTH / 2),
.y = Y,
.demo = demo_.enabled,
.play_area = &param.game.play_area.rect,
.texture = player_textures_.at(1),
.animations = player_animations_,
.hi_score_table = &Options::settings.hi_score_table,
.glowing_entry = &Options::settings.glowing_entries.at(static_cast<int>(Player::Id::PLAYER2) - 1),
.stage_info = stage_manager_.get()};
auto player2 = std::make_unique<Player>(config_player2);
player2->setBulletColors(Bullet::Color::RED, Bullet::Color::PURPLE);
player2->setBulletSoundFile("bullet2p.wav");
player2->setScoreBoardPanel(Scoreboard::Id::RIGHT);
player2->setName(Lang::getText("[SCOREBOARD] 2"));
player2->setGamepad(Options::gamepad_manager.getGamepad(Player::Id::PLAYER2).instance);
player2->setUsesKeyboard(Player::Id::PLAYER2 == Options::keyboard.player_id);
player2->setPlayingState((player_id == Player::Id::BOTH_PLAYERS || player_id == Player::Id::PLAYER2) ? STATE : Player::State::WAITING);
// Añade los jugadores al vector de forma que el jugador 1 se pinte por delante del jugador 2
players_.push_back(std::move(player2));
players_.push_back(std::move(player1));
// Registra los jugadores en Options
for (const auto& player : players_) {
Options::keyboard.addPlayer(player);
Options::gamepad_manager.addPlayer(player);
}
}
// Hace sonar la música
void Game::playMusic(const std::string& music_file, int loop) {
Audio::get()->playMusic(music_file, loop);
}
// Pausa la música
void Game::pauseMusic() {
Audio::get()->pauseMusic();
}
// Retoma la música que eestaba pausada
void Game::resumeMusic() {
Audio::get()->resumeMusic();
}
// Detiene la música
void Game::stopMusic() const {
if (!demo_.enabled) {
Audio::get()->stopMusic();
}
}
// Actualiza las variables durante el modo demo
void Game::updateDemo(float deltaTime) {
if (demo_.enabled) {
balloon_manager_->setCreationTimeEnabled(balloon_manager_->getNumBalloons() != 0);
// Actualiza ambos fades
fade_in_->update();
fade_out_->update();
// Actualiza el contador de tiempo y el índice
demo_.elapsed_s += deltaTime;
demo_.index = static_cast<int>(demo_.elapsed_s * 60.0F);
// Activa el fundido antes de acabar con los datos de la demo
if (demo_.index == TOTAL_DEMO_DATA - 200) {
fade_out_->setType(Fade::Type::RANDOM_SQUARE2);
fade_out_->setPostDuration(param.fade.post_duration_ms);
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(float deltaTime) {
// Actualiza el contador de tiempo y el índice
demo_.elapsed_s += deltaTime;
demo_.index = static_cast<int>(demo_.elapsed_s * 60.0F);
if (demo_.index >= TOTAL_DEMO_DATA) {
Section::name = Section::Name::QUIT;
return;
}
// Almacenar las teclas del frame actual en el vector de grabación
if (demo_.index < TOTAL_DEMO_DATA && demo_.data.size() > 0) {
// Asegurar que el vector tenga el tamaño suficiente
if (demo_.data.at(0).size() <= static_cast<size_t>(demo_.index)) {
demo_.data.at(0).resize(demo_.index + 1);
}
// Almacenar las teclas del frame actual
demo_.data.at(0).at(demo_.index) = demo_.keys;
// Resetear las teclas para el siguiente frame
demo_.keys = DemoKeys();
}
}
#endif
// Actualiza las variables durante dicho estado
void Game::updateGameStateFadeIn(float deltaTime) {
fade_in_->update();
updateScoreboard();
updateBackground(deltaTime);
if (fade_in_->hasEnded()) {
setState(State::ENTERING_PLAYER);
balloon_manager_->createTwoBigBalloons();
setMenace();
}
}
// Actualiza las variables durante dicho estado
void Game::updateGameStateEnteringPlayer(float deltaTime) {
balloon_manager_->update(deltaTime);
updatePlayers(deltaTime);
updateScoreboard();
updateBackground(deltaTime);
for (const auto& player : players_) {
if (player->isPlaying()) {
setState(State::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(float deltaTime) {
updateGameStatePlaying(deltaTime);
constexpr float MUSIC_START_S = 1.67F;
static float music_timer = 0.0f;
music_timer += deltaTime;
if (music_timer >= MUSIC_START_S) {
playMusic("playing.ogg");
music_timer = 0.0F;
setState(State::PLAYING);
}
}
// Actualiza las variables durante el transcurso normal del juego
void Game::updateGameStatePlaying(float deltaTime) {
#ifdef _DEBUG
if (auto_pop_balloons_) {
stage_manager_->addPower(2);
}
#endif
updatePlayers(deltaTime);
checkPlayersStatusPlaying();
updateScoreboard();
updateBackground(deltaTime);
balloon_manager_->update(deltaTime);
tabe_->update(deltaTime);
bullet_manager_->update(deltaTime);
updateItems(deltaTime);
updateStage();
updateSmartSprites(deltaTime);
updatePathSprites(deltaTime);
updateTimeStopped(deltaTime);
updateHelper();
bullet_manager_->checkCollisions();
updateMenace();
checkAndUpdateBalloonSpeed();
checkState();
cleanVectors();
}
// Vacía los vectores de elementos deshabilitados
void Game::cleanVectors() {
bullet_manager_->freeBullets();
balloon_manager_->freeBalloons();
freeItems();
freeSmartSprites();
freePathSprites();
}
// Gestiona el nivel de amenaza
void Game::updateMenace() {
if (state_ != State::PLAYING) {
return;
}
auto current_stage = stage_manager_->getCurrentStage();
if (!current_stage.has_value()) {
return;
}
const auto& stage = current_stage.value();
const double FRACTION = stage_manager_->getCurrentStageProgressFraction();
const int DIFFERENCE = stage.getMaxMenace() - stage.getMinMenace();
// Aumenta el nivel de amenaza en función del progreso de la fase
menace_threshold_ = stage.getMinMenace() + (DIFFERENCE * FRACTION);
if (menace_ < menace_threshold_) {
balloon_manager_->deployRandomFormation(stage_manager_->getCurrentStageIndex());
setMenace();
}
}
// Calcula y establece el valor de amenaza en funcion de los globos activos
void Game::setMenace() {
menace_ = 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 = stage_manager_->getCurrentStageProgressFraction();
constexpr std::array<float, 4> THRESHOLDS = {0.2F, 0.4F, 0.6F, 0.8F};
for (size_t i = 0; i < std::size(THRESHOLDS); ++i) {
// Si la velocidad actual del globo es la correspondiente al umbral "i" y el porcentaje de progreso ha superado ese umbral
if (balloon_manager_->getBalloonSpeed() == Balloon::GAME_TEMPO.at(i) && PERCENT > THRESHOLDS.at(i)) {
// Sube la velocidad al siguiente nivel (i + 1)
balloon_manager_->setBalloonSpeed(Balloon::GAME_TEMPO.at(i + 1));
return;
}
}
}
// Cambia el estado del juego
void Game::setState(State state) {
state_ = state;
counter_ = 0;
switch (state) {
case State::COMPLETED: // Para la música y elimina todos los globos e items
stopMusic(); // Detiene la música
balloon_manager_->destroyAllBalloons(); // Destruye a todos los globos
playSound("power_ball_explosion.wav"); // Sonido de destruir todos los globos
destroyAllItems(); // Destruye todos los items
background_->setAlpha(0); // Elimina el tono rojo de las últimas pantallas
tabe_->disableSpawning(); // Deshabilita la creacion de Tabes
game_completed_flags_.reset(); // Resetea flags de juego completado
break;
case State::GAME_OVER:
game_over_flags_.reset(); // Resetea flags de game over
break;
default:
break;
}
}
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::sortPlayersByZOrder() {
// Procesar jugadores que van al fondo (se dibujan primero)
if (!players_to_put_at_back_.empty()) {
for (auto& player : players_to_put_at_back_) {
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_put_at_back_.clear();
}
// Procesar jugadores que van al frente (se dibujan últimos)
if (!players_to_put_at_front_.empty()) {
for (auto& player : players_to_put_at_front_) {
auto it = std::find(players_.begin(), players_.end(), player);
if (it != players_.end() && it != players_.end() - 1) {
std::shared_ptr<Player> front_player = *it;
players_.erase(it);
players_.push_back(front_player);
}
}
players_to_put_at_front_.clear();
}
}
// Mueve el jugador para pintarlo al fondo de la lista de jugadores
void Game::sendPlayerToTheBack(const std::shared_ptr<Player>& player) {
players_to_put_at_back_.push_back(player);
}
// Mueve el jugador para pintarlo el primero de la lista de jugadores
void Game::sendPlayerToTheFront(const std::shared_ptr<Player>& player) {
players_to_put_at_front_.push_back(player);
}
void Game::onPauseStateChanged(bool is_paused) {
screen_->attenuate(is_paused);
tabe_->pauseTimer(is_paused);
}
// Maneja eventos del juego completado usando flags para triggers únicos
void Game::handleGameCompletedEvents() {
constexpr float START_CELEBRATIONS_S = 6.0f;
constexpr float END_CELEBRATIONS_S = 14.0f;
// Inicio de celebraciones
if (!game_completed_flags_.start_celebrations_triggered && game_completed_timer_ >= START_CELEBRATIONS_S) {
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, Options::settings.hi_score_table.back().score);
player->setPlayingState(Player::State::CELEBRATING);
} else {
player->setPlayingState(Player::State::GAME_OVER);
}
}
updateHiScore();
playMusic("congratulations.ogg", 1);
game_completed_flags_.start_celebrations_triggered = true;
}
// Fin de celebraciones
if (!game_completed_flags_.end_celebrations_triggered && game_completed_timer_ >= END_CELEBRATIONS_S) {
for (auto& player : players_) {
if (player->isCelebrating()) {
player->setPlayingState(player->qualifiesForHighScore() ? Player::State::ENTERING_NAME_GAME_COMPLETED : Player::State::LEAVING_SCREEN);
}
}
game_completed_flags_.end_celebrations_triggered = true;
}
}
// Maneja eventos discretos basados en tiempo durante el estado game over
void Game::handleGameOverEvents() {
constexpr float MESSAGE_TRIGGER_S = 1.5f;
constexpr float FADE_TRIGGER_S = GAME_OVER_DURATION_S - 2.5f;
// Trigger inicial: fade out de música y sonidos de globos
if (!game_over_flags_.music_fade_triggered) {
Audio::get()->fadeOutMusic(1000);
balloon_manager_->setBouncingSounds(true);
game_over_flags_.music_fade_triggered = true;
}
// Trigger del mensaje "Game Over"
if (!game_over_flags_.message_triggered && game_over_timer_ >= MESSAGE_TRIGGER_S) {
createMessage({paths_.at(10), paths_.at(11)}, Resource::get()->getTexture("game_text_game_over"));
playSound("voice_game_over.wav");
game_over_flags_.message_triggered = true;
}
// Trigger del fade out
if (!game_over_flags_.fade_out_triggered && game_over_timer_ >= FADE_TRIGGER_S && !fade_out_->isEnabled()) {
fade_out_->activate();
game_over_flags_.fade_out_triggered = true;
}
}
#ifdef _DEBUG
// Comprueba los eventos en el modo DEBUG
void Game::handleDebugEvents(const SDL_Event& event) {
static int formation_id_ = 0;
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();
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() + ((Player::WIDTH - 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() + ((Player::WIDTH - 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;
}
case SDLK_KP_PLUS: {
++formation_id_;
balloon_manager_->destroyAllBalloons();
balloon_manager_->deployFormation(formation_id_);
std::cout << formation_id_ << '\n';
break;
}
case SDLK_KP_MINUS: {
--formation_id_;
balloon_manager_->destroyAllBalloons();
balloon_manager_->deployFormation(formation_id_);
std::cout << formation_id_ << '\n';
break;
}
default:
break;
}
}
}
#endif