Files
coffee_crisis_arcade_edition/source/sections/title.cpp
Sergio Valor 3964503f1c game fix: la velocitat dels globos dins de la fase actual muntava al primer globo explotat
game fix: al trencar una powerball ja no eixien mes globos
style: renombrades variables i funcions
2025-08-14 20:41:44 +02:00

569 lines
18 KiB
C++

#include "title.h"
#include <SDL3/SDL.h> // Para SDL_GetTicks, Uint32, SDL_Event, SDL_PollEvent, SDL_EventType
#include <algorithm> // Para max, find_if
#include <string> // Para operator+, char_traits, to_string, string, basic_string
#include <vector> // Para vector
#include "audio.h" // Para Audio
#include "color.h" // Para NO_TEXT_COLOR, TITLE_SHADOW_TEXT_COLOR
#include "fade.h" // Para Fade, FadeType
#include "game_logo.h" // Para GameLogo
#include "global_events.h" // Para check
#include "global_inputs.h" // Para check
#include "input.h" // Para Input
#include "input_types.h" // Para InputAction
#include "lang.h" // Para getText
#include "options.h" // Para Gamepad, GamepadManager, gamepad_manager, Settings, settings, Keyboard, keyboard, getPlayerWhoUsesKeyboard, swapControllers, swapKeyboard
#include "param.h" // Para Param, param, ParamGame, ParamTitle, ParamFade
#include "player.h" // Para Player
#include "resource.h" // Para Resource
#include "screen.h" // Para Screen
#include "section.hpp" // Para Name, name, Options, options, AttractMode, attract_mode
#include "sprite.h" // Para Sprite
#include "text.h" // Para Text
#include "tiled_bg.h" // Para TiledBG, TiledBGMode
#include "ui/notifier.h" // Para Notifier
#include "ui/service_menu.h" // Para ServiceMenu
#include "utils.h" // Para Zone, BLOCK
class Texture;
#ifdef _DEBUG
#include <iomanip> // Para operator<<, setfill, setw
#include <iostream>
#endif
// 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"))),
num_controllers_(Input::get()->getNumGamepads()),
state_(TitleState::LOGO_ANIMATING) {
// Configura objetos
tiled_bg_->setColor(param.title.bg_color);
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);
}
// Desregistra los jugadores de Options
Options::keyboard.clearPlayers();
Options::gamepad_manager.clearPlayers();
}
// Actualiza las variables del objeto
void Title::update() {
if (SDL_GetTicks() - ticks_ > param.game.speed) {
ticks_ = SDL_GetTicks();
Screen::get()->update();
updateFade();
updateState();
updateStartPrompt();
updatePlayers();
}
Audio::update();
}
// Dibuja el objeto en pantalla
void Title::render() {
static auto* const SCREEN = Screen::get();
SCREEN->start();
SCREEN->clean();
tiled_bg_->render();
game_logo_->render();
renderPlayers();
renderStartPrompt();
renderCopyright();
fade_->render();
SCREEN->render();
}
// Comprueba los eventos
void Title::checkEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_KEY_DOWN) {
handleKeyDownEvent(event);
}
GlobalEvents::handle(event);
}
}
void Title::handleKeyDownEvent(const SDL_Event& event) {
#ifdef _DEBUG
bool is_repeat = static_cast<int>(event.key.repeat) == 1;
if (is_repeat) {
handleDebugColorKeys(event.key.key);
}
#endif
}
#ifdef _DEBUG
void Title::handleDebugColorKeys(SDL_Keycode key) {
static Color color_ = param.title.bg_color;
adjustColorComponent(key, color_);
counter_ = 0;
tiled_bg_->setColor(color_);
printColorValue(color_);
}
void Title::adjustColorComponent(SDL_Keycode key, Color& color) {
switch (key) {
case SDLK_A:
incrementColorComponent(color.r);
break;
case SDLK_Z:
decrementColorComponent(color.r);
break;
case SDLK_S:
incrementColorComponent(color.g);
break;
case SDLK_X:
decrementColorComponent(color.g);
break;
case SDLK_D:
incrementColorComponent(color.b);
break;
case SDLK_C:
decrementColorComponent(color.b);
break;
case SDLK_F:
incrementAllComponents(color);
break;
case SDLK_V:
decrementAllComponents(color);
break;
default:
break;
}
}
void Title::incrementColorComponent(uint8_t& component) {
if (component < 255) {
++component;
}
}
void Title::decrementColorComponent(uint8_t& component) {
if (component > 0) {
--component;
}
}
void Title::incrementAllComponents(Color& color) {
incrementColorComponent(color.r);
incrementColorComponent(color.g);
incrementColorComponent(color.b);
}
void Title::decrementAllComponents(Color& color) {
decrementColorComponent(color.r);
decrementColorComponent(color.g);
decrementColorComponent(color.b);
}
void Title::printColorValue(const Color& color) {
std::cout << "#"
<< std::hex << std::setw(2) << std::setfill('0') << (int)color.r
<< std::setw(2) << std::setfill('0') << (int)color.g
<< std::setw(2) << std::setfill('0') << (int)color.b
<< std::endl;
}
#endif
// Comprueba las entradas
void Title::checkInput() {
Input::get()->update();
if (!ServiceMenu::get()->isEnabled()) {
processControllerInputs();
processKeyboardStart();
}
GlobalInputs::check();
}
void Title::processKeyboardStart() {
if (!canProcessStartButton()) {
return;
}
const bool START_PRESSED = Input::get()->checkAction(
Input::Action::START,
Input::DO_NOT_ALLOW_REPEAT,
Input::CHECK_KEYBOARD);
if (START_PRESSED) {
switch (Options::keyboard.player_id) {
case Player::Id::PLAYER1:
processPlayer1Start();
break;
case Player::Id::PLAYER2:
processPlayer2Start();
break;
default:
break;
}
}
}
void Title::processControllerInputs() {
for (const auto& controller : Options::gamepad_manager) {
if (isStartButtonPressed(&controller)) {
handleStartButtonPress(&controller);
}
}
}
auto Title::isStartButtonPressed(const Options::Gamepad* controller) -> bool {
return Input::get()->checkAction(
Input::Action::START,
Input::DO_NOT_ALLOW_REPEAT,
Input::DO_NOT_CHECK_KEYBOARD,
controller->instance);
}
void Title::handleStartButtonPress(const Options::Gamepad* controller) {
if (!canProcessStartButton()) {
return;
}
if (controller->player_id == Player::Id::PLAYER1) {
processPlayer1Start();
} else if (controller->player_id == Player::Id::PLAYER2) {
processPlayer2Start();
}
}
auto Title::canProcessStartButton() const -> bool {
return (state_ != TitleState::LOGO_ANIMATING || ALLOW_TITLE_ANIMATION_SKIP);
}
void Title::processPlayer1Start() {
if (!player1_start_pressed_) {
player1_start_pressed_ = true;
activatePlayerAndSetState(Player::Id::PLAYER1);
}
}
void Title::processPlayer2Start() {
if (!player2_start_pressed_) {
player2_start_pressed_ = true;
activatePlayerAndSetState(Player::Id::PLAYER2);
}
}
void Title::activatePlayerAndSetState(Player::Id player_id) {
getPlayer(player_id)->setPlayingState(Player::State::TITLE_ANIMATION);
setState(TitleState::START_HAS_BEEN_PRESSED);
counter_ = 0;
}
// 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()->getNumGamepads() == 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(static_cast<int>(Options::getPlayerWhoUsesKeyboard())) + ": " + Lang::getText("[DEFINE_BUTTONS] KEYBOARD");
Notifier::get()->show({text});
}
// Muestra información sobre los controles y los jugadores
void Title::showControllers() {
// Crea los textos
std::string text1 = Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(static_cast<int>(Player::Id::PLAYER1)) + ": " + Options::gamepad_manager.getGamepad(Player::Id::PLAYER1).name;
std::string text2 = Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(static_cast<int>(Player::Id::PLAYER2)) + ": " + Options::gamepad_manager.getGamepad(Player::Id::PLAYER2).name;
// Muestra la notificación
Notifier::get()->show({text1, text2});
}
// 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: {
++counter_; // Incrementa el contador
game_logo_->update(); // Actualiza el logo con el título del juego
tiled_bg_->update(); // Actualiza el mosaico de fondo
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: {
++counter_; // Incrementa el contador
game_logo_->update(); // Actualiza el logo con el título del juego
tiled_bg_->update(); // Actualiza el mosaico de fondo
if (counter_ == 100) {
fade_->activate();
}
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;
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,
std::string(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>> player1_textures;
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal0"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal1"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal2"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal3"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_power.png"));
player_textures.push_back(player1_textures);
// Texturas - Player2
std::vector<std::shared_ptr<Texture>> player2_textures;
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal0"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal1"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal2"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal3"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_power.png"));
player_textures.push_back(player2_textures);
// Animaciones -- Jugador
player_animations.emplace_back(Resource::get()->getAnimation("player.ani"));
player_animations.emplace_back(Resource::get()->getAnimation("player_power.ani"));
// Crea los dos jugadores
const int Y = param.title.press_start_position - (Player::HEIGHT / 2);
constexpr bool DEMO = false;
Player::Config config_player1;
config_player1.id = Player::Id::PLAYER1;
config_player1.x = param.game.game_area.center_x - (Player::WIDTH / 2);
config_player1.y = Y;
config_player1.demo = DEMO;
config_player1.play_area = &param.game.play_area.rect;
config_player1.texture = player_textures.at(0);
config_player1.animations = player_animations;
config_player1.hi_score_table = &Options::settings.hi_score_table;
config_player1.glowing_entry = &Options::settings.glowing_entries.at(static_cast<int>(Player::Id::PLAYER1) - 1);
players_.emplace_back(std::make_unique<Player>(config_player1));
players_.back()->setPlayingState(Player::State::TITLE_HIDDEN);
Player::Config config_player2;
config_player2.id = Player::Id::PLAYER2;
config_player2.x = param.game.game_area.center_x - (Player::WIDTH / 2);
config_player2.y = Y;
config_player2.demo = DEMO;
config_player2.play_area = &param.game.play_area.rect;
config_player2.texture = player_textures.at(1);
config_player2.animations = player_animations;
config_player2.hi_score_table = &Options::settings.hi_score_table;
config_player2.glowing_entry = &Options::settings.glowing_entries.at(static_cast<int>(Player::Id::PLAYER2) - 1);
players_.emplace_back(std::make_unique<Player>(config_player2));
players_.back()->setPlayingState(Player::State::TITLE_HIDDEN);
// Registra los jugadores en Options
for (auto player : players_) {
Options::keyboard.addPlayer(player);
Options::gamepad_manager.addPlayer(player);
}
}
// 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"
auto Title::getPlayer(Player::Id id) -> std::shared_ptr<Player> {
auto it = std::find_if(players_.begin(), players_.end(), [id](const auto& player) { return player->getId() == id; });
if (it != players_.end()) {
return *it;
}
return nullptr;
}