Files
coffee_crisis_arcade_edition/source/sections/title.cpp

505 lines
13 KiB
C++

#include "title.h"
#include <SDL3/SDL_events.h> // Para SDL_PollEvent, SDL_Event, SDL_KEYDOWN
#include <SDL3/SDL_keycode.h> // Para SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5
#include <SDL3/SDL_timer.h> // Para SDL_GetTicks
#include <stddef.h> // Para size_t
#include <string> // Para char_traits, operator+, basic_string
#include <vector> // Para vector
#include "define_buttons.h" // Para DefineButtons
#include "fade.h" // Para Fade, FadeType
#include "game_logo.h" // Para GameLogo
#include "global_inputs.h" // Para check, update
#include "input.h" // Para Input, InputAction, INPUT_DO_NOT_ALLOW_R...
#include "audio.h" // Para JA_GetMusicState, JA_FadeOutMusic, JA_...
#include "lang.h" // Para getText
#include "global_events.h" // Para handleEvent
#include "notifier.h" // Para Notifier
#include "options.h" // Para OptionsController, Options, options
#include "param.h" // Para Param, param, ParamGame, ParamTitle
#include "player.h" // Para Player, PlayerState
#include "resource.h" // Para Resource
#include "screen.h" // Para Screen
#include "section.h" // Para Options, Name, name, AttractMode, options
#include "sprite.h" // Para Sprite
#include "text.h" // Para TEXT_CENTER, TEXT_SHADOW, Text
#include "texture.h" // Para Texture
#include "tiled_bg.h" // Para TiledBG, TiledBGMode
#include "utils.h" // Para Color, Zone, fade_color, no_color, BLOCK
#include "ui/service_menu.h"
// Constructor
Title::Title()
: text_(Resource::get()->getText("smb2_grad")),
fade_(std::make_unique<Fade>()),
tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::RANDOM)),
game_logo_(std::make_unique<GameLogo>(param.game.game_area.center_x, param.title.title_c_c_position)),
mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))),
define_buttons_(std::make_unique<DefineButtons>()),
num_controllers_(Input::get()->getNumControllers()),
state_(TitleState::LOGO_ANIMATING)
{
// Configura objetos
game_logo_->enable();
mini_logo_sprite_->setX(param.game.game_area.center_x - mini_logo_sprite_->getWidth() / 2);
fade_->setColor(param.fade.color);
fade_->setType(FadeType::RANDOM_SQUARE);
fade_->setPostDuration(param.fade.post_duration);
initPlayers();
// Asigna valores a otras variables
Section::options = Section::Options::TITLE_1;
const bool IS_TITLE_TO_DEMO = (Section::attract_mode == Section::AttractMode::TITLE_TO_DEMO);
next_section_ = IS_TITLE_TO_DEMO ? Section::Name::GAME_DEMO : Section::Name::LOGO;
Section::attract_mode = IS_TITLE_TO_DEMO ? Section::AttractMode::TITLE_TO_LOGO : Section::AttractMode::TITLE_TO_DEMO;
// Define los anclajes de los elementos
anchor_.mini_logo = (param.game.height / 5 * 4) + BLOCK;
mini_logo_sprite_->setY(anchor_.mini_logo);
anchor_.copyright_text = anchor_.mini_logo + mini_logo_sprite_->getHeight() + 3;
}
// Destructor
Title::~Title()
{
Audio::get()->stopAllSounds();
if (Section::name == Section::Name::LOGO)
{
Audio::get()->fadeOutMusic(300);
}
}
// Actualiza las variables del objeto
void Title::update()
{
if (SDL_GetTicks() - ticks_ > param.game.speed)
{
ticks_ = SDL_GetTicks();
updateFade();
updateState();
updateStartPrompt();
updatePlayers();
Screen::get()->update();
}
}
// Dibuja el objeto en pantalla
void Title::render()
{
Screen::get()->start();
Screen::get()->clean();
tiled_bg_->render();
game_logo_->render();
renderPlayers();
renderStartPrompt();
renderCopyright();
define_buttons_->render();
fade_->render();
Screen::get()->render();
}
// Comprueba los eventos
void Title::checkEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_EVENT_KEY_DOWN && event.key.repeat == 0)
{
switch (event.key.key)
{
case SDLK_1: // Redefine los botones del mando #0
define_buttons_->enable(0);
resetCounter();
break;
case SDLK_2: // Redefine los botones del mando #1
define_buttons_->enable(1);
resetCounter();
break;
case SDLK_3: // Intercambia los mandos entre los dos jugadores
swapControllers();
resetCounter();
break;
case SDLK_4: // Intercambia la asignación del teclado
swapKeyboard();
resetCounter();
break;
case SDLK_5: // Muestra la asignación de mandos y teclado
showControllers();
resetCounter();
break;
default:
break;
}
}
GlobalEvents::check(event);
define_buttons_->checkEvents(event);
}
}
// Comprueba las entradas
void Title::checkInput()
{
// Comprueba las entradas solo si no se estan definiendo los botones
if (define_buttons_->isEnabled())
return;
Input::get()->update();
if (!ServiceMenu::get()->isEnabled())
{
// Comprueba todos los métodos de control
for (const auto &CONTROLLER : Options::controllers)
{
// Boton START
if (Input::get()->checkInput(InputAction::START, INPUT_DO_NOT_ALLOW_REPEAT, CONTROLLER.type, CONTROLLER.index))
{
if ((state_ != TitleState::LOGO_ANIMATING || ALLOW_TITLE_ANIMATION_SKIP))
{
if (CONTROLLER.player_id == 1)
{
if (!player1_start_pressed_)
{
player1_start_pressed_ = true;
getPlayer(1)->setPlayingState(PlayerState::TITLE_ANIMATION);
setState(TitleState::START_HAS_BEEN_PRESSED);
counter_ = 0;
}
}
if (CONTROLLER.player_id == 2)
{
if (!player2_start_pressed_)
{
player2_start_pressed_ = true;
getPlayer(2)->setPlayingState(PlayerState::TITLE_ANIMATION);
setState(TitleState::START_HAS_BEEN_PRESSED);
counter_ = 0;
}
}
}
}
}
}
// Comprueba los inputs que se pueden introducir en cualquier sección del juego
GlobalInputs::check();
}
// Bucle para el titulo del juego
void Title::run()
{
while (Section::name == Section::Name::TITLE)
{
checkInput();
update();
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Reinicia el contador interno
void Title::resetCounter() { counter_ = 0; }
// Intercambia la asignación de mandos a los jugadores
void Title::swapControllers()
{
if (Input::get()->getNumControllers() == 0)
return;
Options::swapControllers();
showControllers();
}
// Intercambia el teclado de jugador
void Title::swapKeyboard()
{
Options::swapKeyboard();
std::string text = Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(Options::getPlayerWhoUsesKeyboard()) + ": " + Lang::getText("[DEFINE_BUTTONS] KEYBOARD");
Notifier::get()->show({text});
}
// Muestra información sobre los controles y los jugadores
void Title::showControllers()
{
// Crea vectores de texto vacíos para un número máximo de mandos
constexpr size_t NUM_CONTROLLERS = 2;
std::vector<std::string> text(NUM_CONTROLLERS);
std::vector<int> player_controller_index(NUM_CONTROLLERS, -1);
// Obtiene de cada jugador el índice del mando que tiene asignado
for (size_t i = 0; i < NUM_CONTROLLERS; ++i)
{
// Ejemplo: el jugador 1 tiene el mando 2
player_controller_index.at(Options::controllers.at(i).player_id - 1) = i;
}
// Genera el texto correspondiente
for (size_t i = 0; i < NUM_CONTROLLERS; ++i)
{
const size_t index = player_controller_index.at(i);
if (Options::controllers.at(index).plugged)
{
text.at(i) = Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(i + 1) + ": " + Options::controllers.at(index).name;
}
}
// Muestra la notificación
Notifier::get()->show({text.at(0), text.at(1)});
}
// Actualiza el fade
void Title::updateFade()
{
fade_->update();
if (fade_->hasEnded())
{
const int COMBO = (player1_start_pressed_ ? 1 : 0) | (player2_start_pressed_ ? 2 : 0);
switch (COMBO)
{
case 0: // Ningún jugador ha pulsado Start
Section::name = next_section_;
break;
case 1: // Solo el jugador 1 ha pulsado Start
Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_1P;
Audio::get()->stopMusic();
break;
case 2: // Solo el jugador 2 ha pulsado Start
Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_2P;
Audio::get()->stopMusic();
break;
case 3: // Ambos jugadores han pulsado Start
Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_BOTH;
Audio::get()->stopMusic();
break;
}
}
}
// Actualiza el estado
void Title::updateState()
{
// Establece la lógica según el estado
switch (state_)
{
case TitleState::LOGO_ANIMATING:
{
game_logo_->update();
if (game_logo_->hasFinished())
{
setState(TitleState::LOGO_FINISHED);
}
break;
}
case TitleState::LOGO_FINISHED:
{
// El contador solo sube si no estamos definiendo botones
counter_ = define_buttons_->isEnabled() ? 0 : counter_ + 1;
// Actualiza el logo con el título del juego
game_logo_->update();
// Actualiza el mosaico de fondo
tiled_bg_->update();
if (counter_ == param.title.title_duration)
{
// El menu ha hecho time out
fade_->setPostDuration(0);
fade_->activate();
selection_ = Section::Options::TITLE_TIME_OUT;
}
break;
}
case TitleState::START_HAS_BEEN_PRESSED:
{
// Actualiza el logo con el título del juego
game_logo_->update();
// Actualiza el mosaico de fondo
tiled_bg_->update();
if (counter_ == 100)
{
fade_->activate();
}
++counter_;
break;
}
default:
break;
}
}
void Title::updateStartPrompt()
{
constexpr Uint32 LOGO_BLINK_PERIOD = 833; // milisegundos
constexpr Uint32 LOGO_BLINK_ON_TIME = 583; // 833 - 250
constexpr Uint32 START_BLINK_PERIOD = 167;
constexpr Uint32 START_BLINK_ON_TIME = 83; // 167 - 83
Uint32 time_ms = SDL_GetTicks();
bool condition_met = false;
if (!define_buttons_->isEnabled())
{
switch (state_)
{
case TitleState::LOGO_FINISHED:
condition_met = (time_ms % LOGO_BLINK_PERIOD) >= (LOGO_BLINK_PERIOD - LOGO_BLINK_ON_TIME);
break;
case TitleState::START_HAS_BEEN_PRESSED:
condition_met = (time_ms % START_BLINK_PERIOD) >= (START_BLINK_PERIOD - START_BLINK_ON_TIME);
break;
default:
break;
}
}
should_render_start_prompt = condition_met;
}
void Title::renderStartPrompt()
{
if (should_render_start_prompt)
{
text_->writeDX(TEXT_CENTER | TEXT_SHADOW,
param.game.game_area.center_x,
param.title.press_start_position,
Lang::getText("[TITLE] PRESS_BUTTON_TO_PLAY"),
1,
NO_TEXT_COLOR,
1,
TITLE_SHADOW_TEXT_COLOR);
}
}
void Title::renderCopyright()
{
if (state_ != TitleState::LOGO_ANIMATING)
{
// Mini logo
mini_logo_sprite_->render();
// Texto con el copyright
text_->writeDX(TEXT_CENTER | TEXT_SHADOW,
param.game.game_area.center_x,
anchor_.copyright_text,
TEXT_COPYRIGHT,
1,
NO_TEXT_COLOR,
1,
TITLE_SHADOW_TEXT_COLOR);
}
}
// Cambia el estado
void Title::setState(TitleState state)
{
if (state_ == state)
return;
state_ = state;
switch (state_)
{
case TitleState::LOGO_ANIMATING:
break;
case TitleState::LOGO_FINISHED:
Audio::get()->playMusic("title.ogg");
break;
case TitleState::START_HAS_BEEN_PRESSED:
Audio::get()->fadeOutMusic(1500);
break;
}
}
// Inicializa los jugadores
void Title::initPlayers()
{
std::vector<std::vector<std::shared_ptr<Texture>>> player_textures; // Vector con todas las texturas de los jugadores;
std::vector<std::vector<std::string>> player_animations; // Vector con las animaciones del jugador
// 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"));
}
// Crea los dos jugadores
constexpr int PLAYER_WIDTH = 32;
constexpr int PLAYER_HEIGHT = 32;
const int Y = param.title.press_start_position - (PLAYER_HEIGHT / 2);
constexpr bool DEMO = false;
players_.emplace_back(std::make_unique<Player>(1, param.game.game_area.center_x - (PLAYER_WIDTH / 2), Y, DEMO, param.game.play_area.rect, player_textures.at(0), player_animations));
players_.back()->setPlayingState(PlayerState::TITLE_HIDDEN);
players_.emplace_back(std::make_unique<Player>(2, param.game.game_area.center_x - (PLAYER_WIDTH / 2), Y, DEMO, param.game.play_area.rect, player_textures.at(1), player_animations));
players_.back()->setPlayingState(PlayerState::TITLE_HIDDEN);
}
// Actualza los jugadores
void Title::updatePlayers()
{
for (auto &player : players_)
{
player->update();
}
}
// Renderiza los jugadores
void Title::renderPlayers()
{
for (auto const &player : players_)
{
player->render();
}
}
// Obtiene un jugador a partir de su "id"
std::shared_ptr<Player> Title::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;
}