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::LEAVING_SCREEN);
|
|
}
|
|
}
|
|
|
|
// 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() && !balloon->isPowerBall())
|
|
{
|
|
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()->setRotate(true);
|
|
smart_sprites_.back()->setRotateSpeed(10);
|
|
smart_sprites_.back()->setRotateAmount(90.0);
|
|
}
|
|
|
|
// Actualiza los SmartSprites
|
|
void Game::updateSmartSprites()
|
|
{
|
|
for (auto &sprite : smart_sprites_)
|
|
sprite->update();
|
|
}
|
|
|
|
// Pinta los SmartSprites activos
|
|
void Game::renderSmartSprites()
|
|
{
|
|
for (auto &sprite : smart_sprites_)
|
|
sprite->render();
|
|
}
|
|
|
|
// Actualiza los PathSprites
|
|
void Game::updatePathSprites()
|
|
{
|
|
for (auto &sprite : path_sprites_)
|
|
sprite->update();
|
|
}
|
|
|
|
// Pinta los PathSprites activos
|
|
void Game::renderPathSprites()
|
|
{
|
|
for (auto &sprite : path_sprites_)
|
|
sprite->render();
|
|
}
|
|
|
|
// Acciones a realizar cuando el jugador 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::LEAVING_SCREEN);
|
|
}
|
|
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::LEAVING_SCREEN);
|
|
}
|
|
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::LEAVING_SCREEN);
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
} |