Files
jaildoctors_dilemma/source/game/scenes/title.cpp

853 lines
32 KiB
C++

#include "game/scenes/title.hpp"
#include <SDL3/SDL.h>
#include <algorithm> // Para clamp
#include "core/audio/audio.hpp" // Para Audio
#include "core/input/global_inputs.hpp" // Para check
#include "core/input/input.hpp" // Para Input, InputAction, Input::DO_NOT_ALLOW_REPEAT, REP...
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/surface.hpp" // Para Surface
#include "core/rendering/surface_sprite.hpp" // Para SSprite
#include "core/rendering/text.hpp" // Para Text, TEXT_CENTER, TEXT_COLOR
#include "core/resources/asset.hpp" // Para Asset
#include "core/resources/resource.hpp" // Para Resource
#include "core/system/global_events.hpp" // Para check
#include "game/gameplay/cheevos.hpp" // Para Cheevos, Achievement
#include "game/options.hpp" // Para Options, options, SectionState, Section
#include "game/scene_manager.hpp" // Para SceneManager
#include "utils/defines.hpp" // Para PLAY_AREA_CENTER_X, GAMECANVAS_WIDTH
#include "utils/utils.hpp" // Para stringToColor, PaletteColor, playMusic
// Constructor
Title::Title()
: game_logo_surface_(Resource::get()->getSurface("title_logo.gif")),
game_logo_sprite_(std::make_unique<SurfaceSprite>(game_logo_surface_, 29, 9, game_logo_surface_->getWidth(), game_logo_surface_->getHeight())),
loading_screen_surface_(Resource::get()->getSurface("loading_screen_color.gif")),
loading_screen_sprite_(std::make_unique<SurfaceSprite>(loading_screen_surface_, 0, 0, loading_screen_surface_->getWidth(), loading_screen_surface_->getHeight())),
title_surface_(std::make_shared<Surface>(Options::game.width, Options::game.height)),
delta_timer_(std::make_unique<DeltaTimer>()),
marquee_text_(Resource::get()->getText("gauntlet")),
menu_text_(Resource::get()->getText("gauntlet")),
first_active_letter_(0),
last_active_letter_(0),
state_time_(0.0F),
fade_accumulator_(0.0F),
exit_scene_(SceneManager::Scene::GAME),
controls_menu_state_(ControlsMenuState::MAIN),
remap_step_(0),
axis_cooldown_(0.0F) {
// Inicializa variables
state_ = SceneManager::options == SceneManager::Options::TITLE_WITH_LOADING_SCREEN ? State::SHOW_LOADING_SCREEN : State::MAIN_MENU;
SceneManager::current = SceneManager::Scene::TITLE;
SceneManager::options = SceneManager::Options::NONE;
initMarquee();
// Crea y rellena la textura para mostrar los logros
createCheevosTexture();
// Cambia el color del borde
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
// Inicia la musica
playMusic("title.ogg");
}
// Inicializa la marquesina
void Title::initMarquee() {
letters_.clear();
long_text_ = "HEY JAILERS!! IT'S 2022 AND WE'RE STILL ROCKING LIKE IT'S 1998!!! HAVE YOU HEARD IT? JAILGAMES ARE BACK!! YEEESSS BACK!! MORE THAN 10 TITLES ON JAILDOC'S KITCHEN!! THATS A LOOOOOOT OF JAILGAMES, BUT WHICH ONE WILL STRIKE FIRST? THERE IS ALSO A NEW DEVICE TO COME THAT WILL BLOW YOUR MIND WITH JAILGAMES ON THE GO: P.A.C.O. BUT WAIT! WHAT'S THAT BEAUTY I'M SEEING RIGHT OVER THERE?? OOOH THAT TINY MINIASCII IS PURE LOVE!! I WANT TO LICK EVERY BYTE OF IT!! OH SHIT! AND DON'T FORGET TO BRING BACK THOSE OLD AND FAT MS-DOS JAILGAMES TO GITHUB TO KEEP THEM ALIVE!! WHAT WILL BE THE NEXT JAILDOC RELEASE? WHAT WILL BE THE NEXT PROJECT TO COME ALIVE?? OH BABY WE DON'T KNOW BUT HERE YOU CAN FIND THE ANSWER, YOU JUST HAVE TO COMPLETE JAILDOCTOR'S DILEMMA ... COULD YOU?";
// Pre-calcular anchos de caracteres para eficiencia
for (size_t i = 0; i < long_text_.length(); ++i) {
Glyph l;
l.letter = long_text_[i]; // char directo, no substring
l.x = MARQUEE_START_X; // Usar constante
l.width = marquee_text_->lenght(std::string(1, long_text_[i])); // Pre-calcular ancho
l.enabled = false;
letters_.push_back(l);
}
letters_[0].enabled = true;
first_active_letter_ = 0;
last_active_letter_ = 0;
}
// Comprueba el manejador de eventos
void Title::handleEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
GlobalEvents::handle(event);
// Manejo especial para captura de botones de gamepad
if (state_ == State::CONTROLS_MENU &&
controls_menu_state_ == ControlsMenuState::JOYSTICK_REMAP &&
(event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN || event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION)) {
handleControlsMenuJoystickRemap(event);
continue; // No procesar más este evento
}
if (event.type == SDL_EVENT_KEY_DOWN) {
switch (state_) {
case State::MAIN_MENU:
switch (event.key.key) {
case SDLK_1:
exit_scene_ = SceneManager::Scene::GAME;
transitionToState(State::FADE_MENU);
Audio::get()->fadeOutMusic(1000);
break;
case SDLK_2:
transitionToState(State::CONTROLS_MENU);
controls_menu_state_ = ControlsMenuState::MAIN;
break;
case SDLK_3:
transitionToState(State::CHEEVOS_MENU);
break;
default:
break;
}
break;
case State::CONTROLS_MENU:
if (controls_menu_state_ == ControlsMenuState::MAIN) {
// Menu principal de controles
switch (event.key.key) {
case SDLK_1:
// Iniciar redefinicion de teclado
controls_menu_state_ = ControlsMenuState::KEYBOARD_REMAP;
remap_step_ = 0;
remap_error_message_.clear();
break;
case SDLK_2:
// Redefinir joystick - solo si hay gamepads conectados
if (Input::get()->gameControllerFound()) {
controls_menu_state_ = ControlsMenuState::JOYSTICK_REMAP;
remap_step_ = 0;
remap_error_message_.clear();
axis_cooldown_ = 0.0F; // Resetear cooldown
}
break;
default:
break;
}
} else if (controls_menu_state_ == ControlsMenuState::KEYBOARD_REMAP) {
// Captura de teclas para redefinir
handleControlsMenuKeyboardRemap(event);
}
break;
default:
break;
}
}
}
}
// Comprueba las entradas
void Title::handleInput(float delta_time) {
Input::get()->update();
switch (state_) {
case State::SHOW_LOADING_SCREEN:
if (Input::get()->checkAction(InputAction::ACCEPT, Input::DO_NOT_ALLOW_REPEAT)) {
transitionToState(State::FADE_LOADING_SCREEN);
}
break;
case State::CHEEVOS_MENU:
if (Input::get()->checkAction(InputAction::RIGHT, Input::ALLOW_REPEAT)) {
moveCheevosList(1, delta_time);
} else if (Input::get()->checkAction(InputAction::LEFT, Input::ALLOW_REPEAT)) {
moveCheevosList(0, delta_time);
}
if (Input::get()->checkAction(InputAction::ACCEPT, Input::DO_NOT_ALLOW_REPEAT) ||
Input::get()->checkAction(InputAction::CANCEL, Input::DO_NOT_ALLOW_REPEAT)) {
resetCheevosScroll();
transitionToState(State::MAIN_MENU);
}
break;
case State::CONTROLS_MENU:
// Solo permitir salir si estamos en el menu principal de controles
if (controls_menu_state_ == ControlsMenuState::MAIN) {
if (Input::get()->checkAction(InputAction::ACCEPT, Input::DO_NOT_ALLOW_REPEAT) ||
Input::get()->checkAction(InputAction::CANCEL, Input::DO_NOT_ALLOW_REPEAT)) {
transitionToState(State::MAIN_MENU);
}
}
break;
default:
break;
}
GlobalInputs::handle();
}
// Actualiza la marquesina
void Title::updateMarquee(float delta_time) {
const float DISPLACEMENT = MARQUEE_SPEED * delta_time;
// Solo procesar letras en rango activo + 1 para poder activar la siguiente
for (int i = first_active_letter_; i <= last_active_letter_ + 1 && i < (int)letters_.size(); ++i) {
auto& letter = letters_[i];
if (letter.enabled) {
letter.x -= DISPLACEMENT;
// Desactivar si sale de pantalla
if (letter.x < MARQUEE_EXIT_X) {
letter.enabled = false;
if (i == first_active_letter_) {
first_active_letter_++; // Avanzar inicio del rango
}
}
} else if (i > 0 && letters_[i - 1].x < MARQUEE_START_X && letters_[i - 1].enabled) {
// Activar siguiente letra usando ancho pre-calculado
letter.enabled = true;
letter.x = letters_[i - 1].x + letters_[i - 1].width + MARQUEE_LETTER_SPACING;
last_active_letter_ = i; // Expandir fin del rango
}
}
// Comprueba si ha terminado la marquesina y la reinicia
if (letters_[letters_.size() - 1].x < MARQUEE_EXIT_X) {
initMarquee();
}
}
// Dibuja la marquesina
void Title::renderMarquee() {
// Solo renderizar letras activas (optimización: usa cache y rangos)
for (int i = first_active_letter_; i <= last_active_letter_ + 1 && i < (int)letters_.size(); ++i) {
const auto& letter = letters_[i];
if (letter.enabled) {
marquee_text_->writeColored(
static_cast<int>(letter.x), // Conversión explícita float→int
static_cast<int>(MARQUEE_Y), // Usar constante
std::string(1, letter.letter), // Convertir char a string
static_cast<Uint8>(PaletteColor::MAGENTA));
}
}
}
// Actualiza las variables
void Title::update() {
const float DELTA_TIME = delta_timer_->tick();
handleEvents(); // Comprueba los eventos
handleInput(DELTA_TIME); // Comprueba las entradas
updateState(DELTA_TIME); // Actualiza el estado actual
Audio::get()->update(); // Actualiza el objeto Audio
Screen::get()->update(DELTA_TIME); // Actualiza el objeto Screen
}
// Actualiza el estado actual
void Title::updateState(float delta_time) {
switch (state_) {
case State::SHOW_LOADING_SCREEN:
updateShowLoadingScreen(delta_time);
break;
case State::FADE_LOADING_SCREEN:
updateFadeLoadingScreen(delta_time);
break;
case State::MAIN_MENU:
updateMainMenu(delta_time);
break;
case State::CHEEVOS_MENU:
updateCheevosMenu(delta_time);
break;
case State::CONTROLS_MENU:
updateControlsMenu(delta_time);
break;
case State::FADE_MENU:
updateFadeMenu(delta_time);
break;
case State::POST_FADE_MENU:
updatePostFadeMenu(delta_time);
break;
default:
break;
}
}
// Transiciona a un nuevo estado
void Title::transitionToState(State new_state) {
state_ = new_state;
state_time_ = 0.0F;
fade_accumulator_ = 0.0F;
}
// Actualiza el estado SHOW_LOADING_SCREEN
void Title::updateShowLoadingScreen(float delta_time) {
state_time_ += delta_time;
if (state_time_ >= SHOW_LOADING_DURATION) {
transitionToState(State::FADE_LOADING_SCREEN);
}
}
// Actualiza el estado FADE_LOADING_SCREEN
void Title::updateFadeLoadingScreen(float delta_time) {
fade_accumulator_ += delta_time;
if (fade_accumulator_ >= FADE_STEP_INTERVAL) {
fade_accumulator_ = 0.0F;
if (loading_screen_surface_->fadeSubPalette()) {
transitionToState(State::MAIN_MENU);
}
}
}
// Actualiza el estado MAIN_MENU
void Title::updateMainMenu(float delta_time) {
// Actualiza la marquesina
updateMarquee(delta_time);
// Incrementa el temporizador solo en el menú principal
state_time_ += delta_time;
// Si el tiempo alcanza el timeout, va a créditos con fade
if (state_time_ >= MAIN_MENU_IDLE_TIMEOUT) {
exit_scene_ = SceneManager::Scene::CREDITS;
transitionToState(State::FADE_MENU);
}
}
// Actualiza el estado CHEEVOS_MENU
void Title::updateCheevosMenu(float delta_time) {
// Actualiza la marquesina (sigue visible en fondo)
updateMarquee(delta_time);
// No incrementar state_time_ (no timeout en este estado)
}
// Actualiza el estado CONTROLS_MENU
void Title::updateControlsMenu(float delta_time) {
// Actualiza la marquesina (sigue visible en fondo)
updateMarquee(delta_time);
// Decrementar cooldown de ejes si estamos capturando botones
if (controls_menu_state_ == ControlsMenuState::JOYSTICK_REMAP && axis_cooldown_ > 0.0F) {
axis_cooldown_ -= delta_time;
if (axis_cooldown_ < 0.0F) {
axis_cooldown_ = 0.0F;
}
}
// Si estamos mostrando las teclas definidas, esperar antes de guardar
if (controls_menu_state_ == ControlsMenuState::KEYBOARD_REMAP_COMPLETE) {
state_time_ += delta_time;
if (state_time_ >= KEYBOARD_REMAP_DISPLAY_DELAY) {
// Aplicar y guardar las teclas
applyKeyboardRemap();
// Volver al menu principal
transitionToState(State::MAIN_MENU);
}
}
// Si estamos mostrando los botones definidos, esperar antes de guardar
else if (controls_menu_state_ == ControlsMenuState::JOYSTICK_REMAP_COMPLETE) {
state_time_ += delta_time;
if (state_time_ >= KEYBOARD_REMAP_DISPLAY_DELAY) {
// Aplicar y guardar los botones
applyJoystickRemap();
// Volver al menu principal
transitionToState(State::MAIN_MENU);
}
}
}
// Actualiza el estado FADE_MENU
void Title::updateFadeMenu(float delta_time) {
fade_accumulator_ += delta_time;
if (fade_accumulator_ >= FADE_STEP_INTERVAL) {
fade_accumulator_ = 0.0F;
if (title_surface_->fadeSubPalette()) {
transitionToState(State::POST_FADE_MENU);
}
}
// Actualiza la marquesina (sigue visible en fondo)
updateMarquee(delta_time);
}
// Actualiza el estado POST_FADE_MENU
void Title::updatePostFadeMenu(float delta_time) {
state_time_ += delta_time;
if (state_time_ >= POST_FADE_DELAY) {
SceneManager::current = exit_scene_;
SceneManager::options = SceneManager::Options::NONE;
}
}
// Dibuja en pantalla
void Title::render() {
// Rellena la surface
fillTitleSurface();
// Prepara para empezar a dibujar en la textura de juego
Screen::get()->start();
Screen::get()->clearSurface(static_cast<Uint8>(PaletteColor::BLACK));
// Dibuja en pantalla la surface con la composicion
title_surface_->render();
// Vuelca el contenido del renderizador en pantalla
Screen::get()->render();
}
// Bucle para el logo del juego
void Title::run() {
while (SceneManager::current == SceneManager::Scene::TITLE) {
update();
render();
}
}
// Desplaza la lista de logros
void Title::moveCheevosList(int direction, float delta_time) {
// Calcula el desplazamiento basado en tiempo
const float DISPLACEMENT = CHEEVOS_SCROLL_SPEED * delta_time;
// Modifica la posición de la ventana de vista
cheevos_surface_view_.y = direction == 0
? cheevos_surface_view_.y - DISPLACEMENT
: cheevos_surface_view_.y + DISPLACEMENT;
// Ajusta los limites
const float BOTTOM = cheevos_surface_->getHeight() - cheevos_surface_view_.h;
cheevos_surface_view_.y = std::clamp(cheevos_surface_view_.y, 0.0F, BOTTOM);
cheevos_sprite_->setClip(cheevos_surface_view_);
}
// Crea y rellena la textura para mostrar los logros
void Title::createCheevosTexture() {
// Crea la textura con el listado de logros
const auto CHEEVOS_LIST = Cheevos::get()->list();
const auto TEXT = Resource::get()->getText("subatomic");
constexpr int CHEEVOS_TEXTURE_WIDTH = 200;
constexpr int CHEEVOS_TEXTURE_VIEW_HEIGHT = 110 - 8;
constexpr int CHEEVOS_TEXTURE_POS_Y = 73;
constexpr int CHEEVOS_PADDING = 10;
const int CHEEVO_HEIGHT = CHEEVOS_PADDING + (TEXT->getCharacterSize() * 2) + 1;
const int CHEEVOS_TEXTURE_HEIGHT = (CHEEVO_HEIGHT * CHEEVOS_LIST.size()) + 2 + TEXT->getCharacterSize() + 8;
cheevos_surface_ = std::make_shared<Surface>(CHEEVOS_TEXTURE_WIDTH, CHEEVOS_TEXTURE_HEIGHT);
// Prepara para dibujar sobre la textura
auto previuos_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(cheevos_surface_);
// Rellena la textura con color sólido
const auto CHEEVOS_BG_COLOR = static_cast<Uint8>(PaletteColor::BLACK);
cheevos_surface_->clear(CHEEVOS_BG_COLOR);
// Escribe la lista de logros en la textura
const std::string CHEEVOS_OWNER = "PROJECTS";
const std::string CHEEVOS_LIST_CAPTION = CHEEVOS_OWNER + " (" + std::to_string(Cheevos::get()->getTotalUnlockedAchievements()) + " / " + std::to_string(Cheevos::get()->size()) + ")";
int pos = 2;
TEXT->writeDX(TEXT_CENTER | TEXT_COLOR, cheevos_surface_->getWidth() / 2, pos, CHEEVOS_LIST_CAPTION, 1, stringToColor("bright_green"));
pos += TEXT->getCharacterSize();
const Uint8 CHEEVO_LOCKED_COLOR = stringToColor("white");
const Uint8 CHEEVO_UNLOCKED_COLOR = stringToColor("bright_green");
constexpr int LINE_X1 = (CHEEVOS_TEXTURE_WIDTH / 7) * 3;
constexpr int LINE_X2 = LINE_X1 + ((CHEEVOS_TEXTURE_WIDTH / 7) * 1);
for (const auto& cheevo : CHEEVOS_LIST) {
const Uint8 CHEEVO_COLOR = cheevo.completed ? CHEEVO_UNLOCKED_COLOR : CHEEVO_LOCKED_COLOR;
pos += CHEEVOS_PADDING;
constexpr int HALF = CHEEVOS_PADDING / 2;
cheevos_surface_->drawLine(LINE_X1, pos - HALF - 1, LINE_X2, pos - HALF - 1, CHEEVO_COLOR);
TEXT->writeDX(TEXT_CENTER | TEXT_COLOR, CHEEVOS_TEXTURE_WIDTH / 2, pos, cheevo.caption, 1, CHEEVO_COLOR);
pos += TEXT->getCharacterSize() + 1;
TEXT->writeDX(TEXT_CENTER | TEXT_COLOR, CHEEVOS_TEXTURE_WIDTH / 2, pos, cheevo.description, 1, CHEEVO_COLOR);
pos += TEXT->getCharacterSize();
}
// Restablece el RenderSurface
Screen::get()->setRendererSurface(previuos_renderer);
// Crea el sprite para el listado de logros
cheevos_sprite_ = std::make_unique<SurfaceSprite>(cheevos_surface_, (GAMECANVAS_WIDTH - cheevos_surface_->getWidth()) / 2, CHEEVOS_TEXTURE_POS_Y, cheevos_surface_->getWidth(), cheevos_surface_->getHeight());
cheevos_surface_view_ = {.x = 0, .y = 0, .w = cheevos_surface_->getWidth(), .h = CHEEVOS_TEXTURE_VIEW_HEIGHT};
cheevos_sprite_->setClip(cheevos_surface_view_);
}
// Resetea el scroll de la lista de logros
void Title::resetCheevosScroll() {
cheevos_surface_view_.y = 0;
cheevos_sprite_->setClip(cheevos_surface_view_);
}
// Dibuja el logo con el titulo del juego
void Title::renderGameLogo() {
game_logo_sprite_->render();
}
// Dibuja el menu principal
void Title::renderMainMenu() {
const Uint8 COLOR = stringToColor("green");
const int TEXT_SIZE = menu_text_->getCharacterSize();
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 11 * TEXT_SIZE, "1. PLAY", 1, COLOR);
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 13 * TEXT_SIZE, "2. CONTROLS", 1, COLOR);
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 15 * TEXT_SIZE, "3. PROJECTS", 1, COLOR);
}
// Dibuja el menu de logros
void Title::renderCheevosMenu() {
cheevos_sprite_->render();
}
// Dibuja el menu de controles
void Title::renderControlsMenu() {
// Delegar al render apropiado segun el subestado
if (controls_menu_state_ == ControlsMenuState::KEYBOARD_REMAP ||
controls_menu_state_ == ControlsMenuState::KEYBOARD_REMAP_COMPLETE) {
renderKeyboardRemap();
} else if (controls_menu_state_ == ControlsMenuState::JOYSTICK_REMAP ||
controls_menu_state_ == ControlsMenuState::JOYSTICK_REMAP_COMPLETE) {
renderJoystickRemap();
} else {
// Menu principal de controles
const Uint8 COLOR = stringToColor("green");
const Uint8 DISABLED_COLOR = stringToColor("dark grey");
const int TEXT_SIZE = menu_text_->getCharacterSize();
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 11 * TEXT_SIZE, "1. REDEFINE KEYBOARD", 1, COLOR);
// Deshabilitar opcion de joystick si no hay gamepads conectados
const bool gamepad_available = Input::get()->gameControllerFound();
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 13 * TEXT_SIZE,
"2. REDEFINE JOYSTICK", 1, gamepad_available ? COLOR : DISABLED_COLOR);
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 17 * TEXT_SIZE, "ENTER TO GO BACK", 1, COLOR);
}
}
// Dibuja los elementos en la surface
void Title::fillTitleSurface() {
// Renderiza sobre la textura
auto previuos_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(title_surface_);
// Rellena la textura de color
title_surface_->clear(static_cast<Uint8>(PaletteColor::BLACK));
switch (state_) {
case State::MAIN_MENU:
case State::FADE_MENU:
renderGameLogo();
renderMainMenu();
renderMarquee();
break;
case State::CHEEVOS_MENU:
renderGameLogo();
renderCheevosMenu();
renderMarquee();
break;
case State::CONTROLS_MENU:
renderGameLogo();
renderControlsMenu();
renderMarquee();
break;
case State::SHOW_LOADING_SCREEN:
case State::FADE_LOADING_SCREEN:
loading_screen_sprite_->render();
renderGameLogo();
break;
default:
break;
}
// Deja el renderizador como estaba
Screen::get()->setRendererSurface(previuos_renderer);
}
// Maneja la captura de teclas para redefinir el teclado
void Title::handleControlsMenuKeyboardRemap(const SDL_Event& event) {
SDL_Scancode scancode = event.key.scancode;
// Valida la tecla
if (!isKeyValid(scancode)) {
remap_error_message_ = "INVALID KEY! TRY ANOTHER";
return;
}
// Verifica duplicados
if (isKeyDuplicate(scancode, remap_step_)) {
remap_error_message_ = "KEY ALREADY USED! TRY ANOTHER";
return;
}
// Tecla valida, guardar
temp_keys_[remap_step_] = scancode;
remap_error_message_.clear();
remap_step_++;
// Si completamos los 3 pasos, mostrar resultado y esperar
if (remap_step_ >= 3) {
controls_menu_state_ = ControlsMenuState::KEYBOARD_REMAP_COMPLETE;
state_time_ = 0.0F; // Resetear el timer para el delay de 1 segundo
}
}
// Valida si una tecla es permitida
bool Title::isKeyValid(SDL_Scancode scancode) {
// Prohibir ESC (reservado para cancelar)
if (scancode == SDL_SCANCODE_ESCAPE) {
return false;
}
// Prohibir teclas F1-F12 (reservadas para funciones del sistema)
if (scancode >= SDL_SCANCODE_F1 && scancode <= SDL_SCANCODE_F12) {
return false;
}
// Prohibir Enter/Return (reservado para confirmaciones)
if (scancode == SDL_SCANCODE_RETURN || scancode == SDL_SCANCODE_RETURN2) {
return false;
}
return true;
}
// Verifica si una tecla ya fue usada en pasos anteriores
bool Title::isKeyDuplicate(SDL_Scancode scancode, int current_step) {
for (int i = 0; i < current_step; i++) {
if (temp_keys_[i] == scancode) {
return true;
}
}
return false;
}
// Retorna el nombre de la accion para el paso actual
std::string Title::getActionName(int step) {
switch (step) {
case 0:
return "LEFT";
case 1:
return "RIGHT";
case 2:
return "JUMP";
default:
return "UNKNOWN";
}
}
// Aplica y guarda las teclas redefinidas
void Title::applyKeyboardRemap() {
// Guardar las nuevas teclas en Options::controls
Options::controls.key_left = temp_keys_[0];
Options::controls.key_right = temp_keys_[1];
Options::controls.key_jump = temp_keys_[2];
// Aplicar los bindings al sistema de Input
Input::get()->applyKeyboardBindingsFromOptions();
// Guardar a archivo de configuracion
Options::saveToFile(Asset::get()->get("config.txt"));
}
// Dibuja la pantalla de redefinir teclado
void Title::renderKeyboardRemap() {
const Uint8 COLOR = stringToColor("green");
const Uint8 ERROR_COLOR = stringToColor("red");
const int TEXT_SIZE = menu_text_->getCharacterSize();
// Titulo
//menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 9 * TEXT_SIZE, "REDEFINE KEYBOARD", 1, COLOR);
// Mensaje principal: "PRESS KEY FOR [ACTION]" o "KEYS DEFINED" si completado
if (remap_step_ >= 3) {
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 12 * TEXT_SIZE, "KEYS DEFINED", 1, COLOR);
} else {
const std::string ACTION = getActionName(remap_step_);
const std::string MESSAGE = "PRESS KEY FOR " + ACTION;
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 12 * TEXT_SIZE, MESSAGE, 1, COLOR);
}
// Mostrar teclas ya capturadas
if (remap_step_ > 0) {
const std::string LEFT_KEY = SDL_GetScancodeName(temp_keys_[0]);
const std::string LEFT_MSG = "LEFT: " + LEFT_KEY;
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 14 * TEXT_SIZE, LEFT_MSG, 1, COLOR);
}
if (remap_step_ > 1) {
const std::string RIGHT_KEY = SDL_GetScancodeName(temp_keys_[1]);
const std::string RIGHT_MSG = "RIGHT: " + RIGHT_KEY;
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 15 * TEXT_SIZE, RIGHT_MSG, 1, COLOR);
}
if (remap_step_ >= 3) {
const std::string JUMP_KEY = SDL_GetScancodeName(temp_keys_[2]);
const std::string JUMP_MSG = "JUMP: " + JUMP_KEY;
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 16 * TEXT_SIZE, JUMP_MSG, 1, COLOR);
}
// Mensaje de error si existe
if (!remap_error_message_.empty()) {
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 18 * TEXT_SIZE, remap_error_message_, 1, ERROR_COLOR);
}
}
// Dibuja la pantalla de redefinir joystick
void Title::renderJoystickRemap() {
const Uint8 COLOR = stringToColor("green");
const Uint8 ERROR_COLOR = stringToColor("red");
const int TEXT_SIZE = menu_text_->getCharacterSize();
// Mensaje principal: "PRESS BUTTON FOR [ACTION]" o "BUTTONS DEFINED" si completado
if (remap_step_ >= 3) {
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 12 * TEXT_SIZE, "BUTTONS DEFINED", 1, COLOR);
} else {
const std::string ACTION = getActionName(remap_step_);
const std::string MESSAGE = "PRESS BUTTON FOR " + ACTION;
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 12 * TEXT_SIZE, MESSAGE, 1, COLOR);
}
// Mostrar botones ya capturados
if (remap_step_ > 0) {
const std::string LEFT_BTN = getButtonName(temp_buttons_[0]);
const std::string LEFT_MSG = "LEFT: " + LEFT_BTN;
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 14 * TEXT_SIZE, LEFT_MSG, 1, COLOR);
}
if (remap_step_ > 1) {
const std::string RIGHT_BTN = getButtonName(temp_buttons_[1]);
const std::string RIGHT_MSG = "RIGHT: " + RIGHT_BTN;
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 15 * TEXT_SIZE, RIGHT_MSG, 1, COLOR);
}
if (remap_step_ >= 3) {
const std::string JUMP_BTN = getButtonName(temp_buttons_[2]);
const std::string JUMP_MSG = "JUMP: " + JUMP_BTN;
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 16 * TEXT_SIZE, JUMP_MSG, 1, COLOR);
}
// Mensaje de error si existe
if (!remap_error_message_.empty()) {
menu_text_->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 18 * TEXT_SIZE, remap_error_message_, 1, ERROR_COLOR);
}
}
// Maneja la captura de botones del gamepad para redefinir
void Title::handleControlsMenuJoystickRemap(const SDL_Event& event) {
int captured_button = -1;
// Capturar botones del gamepad
if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
captured_button = static_cast<int>(event.gbutton.button);
}
// Capturar triggers y ejes analógicos
else if (event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
// Si el cooldown está activo, ignorar eventos de ejes (evita múltiples capturas)
if (axis_cooldown_ > 0.0F) {
return;
}
constexpr Sint16 TRIGGER_THRESHOLD = 20000;
constexpr Sint16 AXIS_THRESHOLD = 20000;
// Capturar triggers como botones (usando valores especiales 100/101)
if (event.gaxis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER && event.gaxis.value > TRIGGER_THRESHOLD) {
captured_button = Input::TRIGGER_L2_AS_BUTTON; // 100
axis_cooldown_ = 0.5F; // Cooldown de medio segundo
} else if (event.gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER && event.gaxis.value > TRIGGER_THRESHOLD) {
captured_button = Input::TRIGGER_R2_AS_BUTTON; // 101
axis_cooldown_ = 0.5F;
}
// Capturar ejes del stick analógico (usando valores especiales 200+)
else if (event.gaxis.axis == SDL_GAMEPAD_AXIS_LEFTX) {
if (event.gaxis.value < -AXIS_THRESHOLD) {
captured_button = 200; // Left stick izquierda
axis_cooldown_ = 0.5F;
} else if (event.gaxis.value > AXIS_THRESHOLD) {
captured_button = 201; // Left stick derecha
axis_cooldown_ = 0.5F;
}
}
}
// Si no se capturó ningún input válido, salir
if (captured_button == -1) {
return;
}
// Verifica duplicados
if (isButtonDuplicate(captured_button, remap_step_)) {
remap_error_message_ = "BUTTON ALREADY USED! TRY ANOTHER";
return;
}
// Botón válido, guardar
temp_buttons_[remap_step_] = captured_button;
remap_error_message_.clear();
remap_step_++;
// Si completamos los 3 pasos, mostrar resultado y esperar
if (remap_step_ >= 3) {
controls_menu_state_ = ControlsMenuState::JOYSTICK_REMAP_COMPLETE;
state_time_ = 0.0F; // Resetear el timer para el delay
}
}
// Valida si un botón está duplicado
bool Title::isButtonDuplicate(int button, int current_step) {
for (int i = 0; i < current_step; ++i) {
if (temp_buttons_[i] == button) {
return true;
}
}
return false;
}
// Aplica y guarda los botones del gamepad redefinidos
void Title::applyJoystickRemap() {
// Guardar los nuevos botones en Options::gamepad_controls
Options::gamepad_controls.button_left = temp_buttons_[0];
Options::gamepad_controls.button_right = temp_buttons_[1];
Options::gamepad_controls.button_jump = temp_buttons_[2];
// Aplicar los bindings al sistema de Input
Input::get()->applyGamepadBindingsFromOptions();
// Guardar a archivo de configuracion
Options::saveToFile(Asset::get()->get("config.txt"));
}
// Retorna el nombre amigable del botón del gamepad
std::string Title::getButtonName(int button) {
// Triggers especiales
if (button == Input::TRIGGER_L2_AS_BUTTON) {
return "L2";
}
if (button == Input::TRIGGER_R2_AS_BUTTON) {
return "R2";
}
// Ejes del stick analógico
if (button == 200) {
return "LEFT STICK LEFT";
}
if (button == 201) {
return "LEFT STICK RIGHT";
}
// Botones estándar SDL
const auto sdl_button = static_cast<SDL_GamepadButton>(button);
const char* button_name = SDL_GetGamepadStringForButton(sdl_button);
return button_name ? std::string(button_name) : "UNKNOWN";
}