No canviava al marcador, ni el nom del que tenia la maxima puntuació en calent ni al posar nom retallat el nom de 8 a 6 caracters, i tots en majuscula pa que capia en el marcador ja actualitza be la cadena amb el nom al posar nom per segona vegada en la mateixa partida
1827 lines
50 KiB
C++
1827 lines
50 KiB
C++
#include "game.h"
|
|
#include <SDL2/SDL_blendmode.h> // Para SDL_BLENDMODE_BLEND
|
|
#include <SDL2/SDL_events.h> // Para SDL_PollEvent, SDL_Event, SDL_KEYDOWN
|
|
#include <SDL2/SDL_keycode.h> // Para SDLK_1, SDLK_2, SDLK_3, SDLK_4
|
|
#include <SDL2/SDL_pixels.h> // Para SDL_PIXELFORMAT_RGBA8888
|
|
#include <SDL2/SDL_timer.h> // Para SDL_GetTicks
|
|
#include <SDL2/SDL_video.h> // Para SDL_WINDOWEVENT_FOCUS_GAINED, SDL_...
|
|
#include <stdlib.h> // Para rand, size_t
|
|
#include <algorithm> // Para find_if, clamp, min
|
|
#include <functional> // Para function
|
|
#include <iterator> // Para distance, size
|
|
#include "asset.h" // Para Asset
|
|
#include "background.h" // Para Background
|
|
#include "balloon.h" // Para Balloon, BALLOON_SPEED
|
|
#include "balloon_manager.h" // Para BalloonManager
|
|
#include "bullet.h" // Para Bullet, BulletType, BulletMoveStatus
|
|
#include "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, PlayerState
|
|
#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 "stage.h" // Para get, number, Stage, power
|
|
#include "text.h" // Para Text
|
|
#include "dbgtxt.h" // Para dbg_print
|
|
#include "texture.h" // Para Texture
|
|
struct JA_Sound_t; // lines 37-37
|
|
|
|
// 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<Background>()),
|
|
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<Fade>()),
|
|
balloon_manager_(std::make_unique<BalloonManager>())
|
|
{
|
|
// Pasa variables
|
|
demo_.enabled = demo;
|
|
|
|
// Otras variables
|
|
section::name = section::Name::GAME;
|
|
section::options = section::Options::GAME_PLAY_1P;
|
|
Stage::init();
|
|
Stage::number = current_stage;
|
|
|
|
// 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);
|
|
|
|
SDL_SetTextureBlendMode(canvas_, SDL_BLENDMODE_BLEND);
|
|
|
|
// Inicializa el resto de variables
|
|
initPlayers(player_id);
|
|
initScoreboard();
|
|
initDifficultyVars();
|
|
initDemo(player_id);
|
|
initPaths();
|
|
setTotalPower();
|
|
|
|
#ifdef DEBUG
|
|
// Si se empieza en una fase que no es la primera
|
|
if (!demo_.enabled)
|
|
for (int i = 0; i < Stage::number; ++i)
|
|
{
|
|
Stage::total_power += Stage::get(i).power_to_complete;
|
|
}
|
|
#endif
|
|
|
|
// Crea los primeros globos y el mensaje de inicio
|
|
if (!demo_.enabled)
|
|
{
|
|
balloon_manager_->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<ManageHiScoreTable>(options.game.hi_score_table);
|
|
manager->saveToFile(asset_->get("score.bin"));
|
|
}
|
|
#ifdef RECORDING
|
|
saveDemoFile(Asset::get()->get("demo1.bin"), demo_.data.at(0));
|
|
#endif
|
|
|
|
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 - Items
|
|
{
|
|
item_textures_.emplace_back(Resource::get()->getTexture("item_points1_disk.png"));
|
|
item_textures_.emplace_back(Resource::get()->getTexture("item_points2_gavina.png"));
|
|
item_textures_.emplace_back(Resource::get()->getTexture("item_points3_pacmar.png"));
|
|
item_textures_.emplace_back(Resource::get()->getTexture("item_clock.png"));
|
|
item_textures_.emplace_back(Resource::get()->getTexture("item_coffee.png"));
|
|
item_textures_.emplace_back(Resource::get()->getTexture("item_coffee_machine.png"));
|
|
}
|
|
|
|
// Texturas - Player1
|
|
{
|
|
std::vector<std::shared_ptr<Texture>> player_texture;
|
|
player_texture.emplace_back(Resource::get()->getTexture("player1.gif"));
|
|
player_texture.emplace_back(Resource::get()->getTexture("player1_power.png"));
|
|
player_textures_.push_back(player_texture);
|
|
}
|
|
|
|
// Texturas - Player2
|
|
{
|
|
std::vector<std::shared_ptr<Texture>> player_texture;
|
|
player_texture.emplace_back(Resource::get()->getTexture("player2.gif"));
|
|
player_texture.emplace_back(Resource::get()->getTexture("player2_power.png"));
|
|
player_textures_.push_back(player_texture);
|
|
}
|
|
|
|
// Animaciones -- Jugador
|
|
{
|
|
player_animations_.emplace_back(Resource::get()->getAnimation("player.ani"));
|
|
player_animations_.emplace_back(Resource::get()->getAnimation("player_power.ani"));
|
|
}
|
|
|
|
// Animaciones -- 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"));
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
hi_score_.name.clear();
|
|
;
|
|
|
|
// 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 (Stage::power >= Stage::get(Stage::number).power_to_complete)
|
|
{
|
|
// Cambio de fase
|
|
Stage::power = Stage::get(Stage::number).power_to_complete - Stage::power;
|
|
++Stage::number;
|
|
JA_PlaySound(Resource::get()->getSound("stage_change.wav"));
|
|
balloon_manager_->resetBalloonSpeed();
|
|
screen_->flash(flash_color, 100);
|
|
screen_->shake();
|
|
|
|
// Escribe el texto por pantalla
|
|
if (Stage::number < 10)
|
|
{
|
|
std::vector<Path> paths = {paths_.at(2), paths_.at(3)};
|
|
if (Stage::number == 9)
|
|
{
|
|
createMessage(paths, Resource::get()->getTexture("last_stage"));
|
|
}
|
|
else
|
|
{
|
|
auto text = Resource::get()->getText("04b_25_2x");
|
|
const std::string caption = std::to_string(10 - Stage::number) + 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();
|
|
Stage::number = 9; // Deja el valor dentro de los limites
|
|
balloon_manager_->destroyAllBalloons(); // Destruye a todos los globos
|
|
destroyAllItems(); // Destruye todos los items
|
|
Stage::power = 0; // Vuelve a dejar el poder a cero, por lo que hubiera podido subir al destruir todos los globos
|
|
}
|
|
|
|
// 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 && Stage::number == 10)
|
|
{
|
|
state_ = GameState::COMPLETED;
|
|
}
|
|
|
|
if (state_ != GameState::GAME_OVER && allPlayersAreGameOver())
|
|
{
|
|
state_ = GameState::GAME_OVER;
|
|
}
|
|
}
|
|
|
|
// Destruye todos los items
|
|
void Game::destroyAllItems()
|
|
{
|
|
for (auto &item : items_)
|
|
item->disable();
|
|
}
|
|
|
|
// Comprueba la colisión entre el jugador y los globos activos
|
|
bool Game::checkPlayerBalloonCollision(std::shared_ptr<Player> &player)
|
|
{
|
|
for (auto &balloon : balloon_manager_->getBalloons())
|
|
{
|
|
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> &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 : balloon_manager_->getBalloons())
|
|
{
|
|
if (balloon->isEnabled() && (!balloon->isInvulnerable()) && bullet->isEnabled())
|
|
{
|
|
if (checkCollision(balloon->getCollider(), bullet->getCollider()))
|
|
{
|
|
// Obtiene al jugador que disparó la bala
|
|
auto player = getPlayer(bullet->getOwner());
|
|
|
|
// 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
|
|
const auto score = balloon_manager_->popBalloon(balloon);
|
|
evaluateAndSetMenace();
|
|
|
|
// Otorga los puntos al jugador que disparó la bala
|
|
if (player->isPlaying())
|
|
{
|
|
player->addScore(score * player->getScoreMultiplier() * difficulty_score_multiplier_);
|
|
player->incScoreMultiplier();
|
|
}
|
|
updateHiScore();
|
|
|
|
// 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<Bullet>(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<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, 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 = 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<Path> &paths, std::shared_ptr<Texture> texture)
|
|
{
|
|
path_sprites_.emplace_back(std::make_unique<PathSprite>(texture));
|
|
|
|
// Inicializa
|
|
for (const auto &path : paths)
|
|
path_sprites_.back()->addPath(path, true);
|
|
path_sprites_.back()->enable();
|
|
}
|
|
|
|
// Vacia el vector de smartsprites
|
|
void Game::freeSmartSprites()
|
|
{
|
|
if (!smart_sprites_.empty())
|
|
for (int i = smart_sprites_.size() - 1; i >= 0; --i)
|
|
if (smart_sprites_[i]->hasFinished())
|
|
smart_sprites_.erase(smart_sprites_.begin() + i);
|
|
}
|
|
|
|
// Vacia el vector de pathsprites
|
|
void Game::freePathSprites()
|
|
{
|
|
if (!path_sprites_.empty())
|
|
for (int i = path_sprites_.size() - 1; i >= 0; --i)
|
|
if (path_sprites_[i]->hasFinished())
|
|
path_sprites_.erase(path_sprites_.begin() + i);
|
|
}
|
|
|
|
// Crea un SpriteSmart para arrojar el item café al recibir un impacto
|
|
void Game::throwCoffee(int x, int y)
|
|
{
|
|
smart_sprites_.emplace_back(std::make_unique<SmartSprite>(item_textures_[4]));
|
|
|
|
smart_sprites_.back()->setPosX(x - 8);
|
|
smart_sprites_.back()->setPosY(y - 8);
|
|
smart_sprites_.back()->setWidth(param.game.item_size);
|
|
smart_sprites_.back()->setHeight(param.game.item_size);
|
|
smart_sprites_.back()->setVelX(-1.0f + ((rand() % 5) * 0.5f));
|
|
smart_sprites_.back()->setVelY(-4.0f);
|
|
smart_sprites_.back()->setAccelX(0.0f);
|
|
smart_sprites_.back()->setAccelY(0.2f);
|
|
smart_sprites_.back()->setDestX(x + (smart_sprites_.back()->getVelX() * 50));
|
|
smart_sprites_.back()->setDestY(param.game.height + 1);
|
|
smart_sprites_.back()->setEnabled(true);
|
|
smart_sprites_.back()->setFinishedCounter(1);
|
|
smart_sprites_.back()->setSpriteClip(0, param.game.item_size, param.game.item_size, param.game.item_size);
|
|
smart_sprites_.back()->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> &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();
|
|
balloon_manager_->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();
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
balloon_manager_->normalColorsToAllBalloons();
|
|
if (time_stopped_counter_ % 30 == 15)
|
|
balloon_manager_->reverseColorsToAllBalloons();
|
|
}
|
|
}
|
|
else
|
|
disableTimeStopItem();
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
Stage::total_power = (Stage::total_power > 400) ? (Stage::total_power - 25) : 200;
|
|
}
|
|
|
|
// Calcula la velocidad en función de los globos explotados y el total de globos a explotar para acabar el juego
|
|
constexpr float clouds_initial_speed = 0.05f;
|
|
constexpr float clouds_final_speed = 2.00f - clouds_initial_speed;
|
|
const float cloudsSpeed = (-clouds_initial_speed) + (-clouds_final_speed * (static_cast<float>(Stage::total_power) / total_power_to_complete_game_));
|
|
background_->setCloudsSpeed(cloudsSpeed);
|
|
|
|
// Calcula la transición de los diferentes fondos
|
|
constexpr float num = 1525.0f; // total_power_to_complete div 4
|
|
const float gradient_number = std::min(Stage::total_power / num, 3.0f);
|
|
const float percent = gradient_number - static_cast<int>(gradient_number);
|
|
background_->setGradientNumber(static_cast<int>(gradient_number));
|
|
background_->setTransition(percent);
|
|
|
|
// 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();
|
|
balloon_manager_->render();
|
|
renderBullets();
|
|
renderPathSprites();
|
|
renderPlayers();
|
|
// dbg_print(0, 40, std::to_string(menace_current_).c_str(), 255, 0, 0);
|
|
// dbg_print(0, 50, std::to_string(menace_threshold_).c_str(), 255, 0, 0);
|
|
|
|
// 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();
|
|
}
|
|
|
|
// Habilita el efecto del item de detener el tiempo
|
|
void Game::enableTimeStopItem()
|
|
{
|
|
balloon_manager_->stopAllBalloons();
|
|
balloon_manager_->reverseColorsToAllBalloons();
|
|
time_stopped_counter_ = TIME_STOPPED_COUNTER_;
|
|
}
|
|
|
|
// Deshabilita el efecto del item de detener el tiempo
|
|
void Game::disableTimeStopItem()
|
|
{
|
|
time_stopped_counter_ = 0;
|
|
balloon_manager_->startAllBalloons();
|
|
balloon_manager_->normalColorsToAllBalloons();
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// 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
|
|
{
|
|
balloon_manager_->createPowerBall();
|
|
break;
|
|
}
|
|
case SDLK_2: // Crea dos globos gordos
|
|
{
|
|
balloon_manager_->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 &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(Stage::number + 1);
|
|
scoreboard_->setPower((float)Stage::power / (float)Stage::get(Stage::number).power_to_complete);
|
|
scoreboard_->setHiScore(hi_score_.score);
|
|
scoreboard_->setHiScoreName(hi_score_.name);
|
|
|
|
// Lógica del marcador
|
|
scoreboard_->update();
|
|
}
|
|
|
|
// Pausa el juego
|
|
void Game::pause(bool value)
|
|
{
|
|
paused_ = value;
|
|
screen_->attenuate(paused_);
|
|
}
|
|
|
|
// Añade una puntuación a la tabla de records
|
|
void Game::addScoreToScoreBoard(const std::string &name, int score)
|
|
{
|
|
const auto entry = HiScoreEntry(trim(name), score);
|
|
auto manager = std::make_unique<ManageHiScoreTable>(options.game.hi_score_table);
|
|
manager->add(entry);
|
|
manager->saveToFile(asset_->get("score.bin"));
|
|
hi_score_.name = options.game.hi_score_table.front().name;
|
|
}
|
|
|
|
// 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<Player> 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> &player, int index)
|
|
{
|
|
const auto &demoData = demo_.data[index][demo_.counter];
|
|
|
|
if (demoData.left == 1)
|
|
{
|
|
player->setInput(InputType::LEFT);
|
|
}
|
|
else if (demoData.right == 1)
|
|
{
|
|
player->setInput(InputType::RIGHT);
|
|
}
|
|
else if (demoData.no_input == 1)
|
|
{
|
|
player->setInput(InputType::NONE);
|
|
}
|
|
|
|
if (demoData.fire == 1)
|
|
{
|
|
handleFireInput(player, BulletType::UP);
|
|
}
|
|
else if (demoData.fire_left == 1)
|
|
{
|
|
handleFireInput(player, BulletType::LEFT);
|
|
}
|
|
else if (demoData.fire_right == 1)
|
|
{
|
|
handleFireInput(player, BulletType::RIGHT);
|
|
}
|
|
}
|
|
|
|
// Maneja el disparo de un jugador, incluyendo la creación de balas y la gestión del tiempo de espera entre disparos.
|
|
void Game::handleFireInput(const std::shared_ptr<Player> &player, BulletType bulletType)
|
|
{
|
|
if (player->canFire())
|
|
{
|
|
player->setInput(bulletType == BulletType::UP ? InputType::FIRE_CENTER : bulletType == BulletType::LEFT ? InputType::FIRE_LEFT
|
|
: InputType::FIRE_RIGHT);
|
|
createBullet(player->getPosX() + (player->getWidth() / 2) - 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> &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> &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> &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> &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() == NAME_LENGHT - 1)
|
|
{
|
|
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::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};
|
|
Stage::number = stages[demo];
|
|
}
|
|
|
|
// Actualiza el numero de globos explotados según la fase del modo demostración
|
|
for (int i = 0; i < Stage::number; ++i)
|
|
{
|
|
Stage::total_power += Stage::get(i).power_to_complete;
|
|
}
|
|
|
|
// Activa o no al otro jugador
|
|
if (rand() % 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 (const auto &stage : Stage::stages)
|
|
{
|
|
total_power_to_complete_game_ += stage.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:
|
|
{
|
|
balloon_manager_->setDefaultBalloonSpeed(BALLOON_SPEED[0]);
|
|
difficulty_score_multiplier_ = 0.5f;
|
|
scoreboard_->setColor(scoreboard_easy_color);
|
|
break;
|
|
}
|
|
|
|
case GameDifficulty::NORMAL:
|
|
{
|
|
balloon_manager_->setDefaultBalloonSpeed(BALLOON_SPEED[0]);
|
|
difficulty_score_multiplier_ = 1.0f;
|
|
scoreboard_->setColor(scoreboard_normal_color);
|
|
break;
|
|
}
|
|
|
|
case GameDifficulty::HARD:
|
|
{
|
|
balloon_manager_->setDefaultBalloonSpeed(BALLOON_SPEED[4]);
|
|
difficulty_score_multiplier_ = 1.5f;
|
|
scoreboard_->setColor(scoreboard_hard_color);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
balloon_manager_->resetBalloonSpeed();
|
|
}
|
|
|
|
// 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<Player>(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<Player>(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);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
Stage::addPower(5);
|
|
}
|
|
#endif
|
|
fade_->update();
|
|
updatePlayers();
|
|
checkPlayersStatusPlaying();
|
|
updateScoreboard();
|
|
updateBackground();
|
|
balloon_manager_->update();
|
|
moveBullets();
|
|
updateItems();
|
|
updateStage();
|
|
updateGameOverState();
|
|
updateCompletedState();
|
|
updateSmartSprites();
|
|
updatePathSprites();
|
|
updateTimeStopped();
|
|
updateHelper();
|
|
checkBulletBalloonCollision();
|
|
updateMenace();
|
|
checkAndUpdateBalloonSpeed();
|
|
checkState();
|
|
cleanVectors();
|
|
}
|
|
}
|
|
|
|
// Vacía los vectores de elementos deshabilitados
|
|
void Game::cleanVectors()
|
|
{
|
|
freeBullets();
|
|
balloon_manager_->freeBalloons();
|
|
freeItems();
|
|
freeSmartSprites();
|
|
freePathSprites();
|
|
}
|
|
|
|
// Gestiona el nivel de amenaza
|
|
void Game::updateMenace()
|
|
{
|
|
const auto stage = Stage::get(Stage::number);
|
|
const float percent = Stage::power / stage.power_to_complete;
|
|
const int difference = stage.max_menace - stage.min_menace;
|
|
|
|
// Aumenta el nivel de amenaza en función de la puntuación
|
|
menace_threshold_ = stage.min_menace + (difference * percent);
|
|
|
|
// Si el nivel de amenza es inferior al umbral
|
|
if (menace_current_ < menace_threshold_)
|
|
{
|
|
// Crea una formación de enemigos
|
|
balloon_manager_->deployBalloonFormation(Stage::number);
|
|
|
|
// Recalcula el nivel de amenaza con el nuevo globo
|
|
evaluateAndSetMenace();
|
|
}
|
|
}
|
|
|
|
// Calcula y establece el valor de amenaza en funcion de los globos activos
|
|
void Game::evaluateAndSetMenace()
|
|
{
|
|
menace_current_ = balloon_manager_->getMenace();
|
|
}
|
|
|
|
// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
|
|
void Game::checkAndUpdateBalloonSpeed()
|
|
{
|
|
if (difficulty_ != GameDifficulty::NORMAL)
|
|
return;
|
|
|
|
const float percent = static_cast<float>(Stage::power) / Stage::get(Stage::number).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_manager_->getBalloonSpeed() == BALLOON_SPEED[i] && percent > thresholds[i])
|
|
{
|
|
balloon_manager_->setBalloonSpeed(BALLOON_SPEED[i + 1]);
|
|
break; // Salir del bucle una vez actualizada la velocidad y aplicada
|
|
}
|
|
}
|
|
} |