612 lines
22 KiB
C++
612 lines
22 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_FLAG, Text::COLOR_FLAG
|
|
#include "core/resources/resource_cache.hpp" // Para Resource
|
|
#include "core/resources/resource_list.hpp" // Para Asset
|
|
#include "core/system/global_events.hpp" // Para check
|
|
#include "game/defaults.hpp" // Para Defaults::Music
|
|
#include "game/options.hpp" // Para Options, options, SectionState, Section
|
|
#include "game/scene_manager.hpp" // Para SceneManager
|
|
#include "utils/defines.hpp" // Para GameCanvas::CENTER_X, GameCanvas::WIDTH
|
|
#include "utils/color.hpp" // Para Color
|
|
#include "utils/utils.hpp" // Para stringToColor
|
|
|
|
// Constructor
|
|
Title::Title()
|
|
: game_logo_surface_(Resource::Cache::get()->getSurface("title_logo.gif")),
|
|
game_logo_sprite_(std::make_unique<SurfaceSprite>(
|
|
game_logo_surface_,
|
|
(GameCanvas::WIDTH - game_logo_surface_->getWidth()) / 2, // Centrado horizontal dinámico
|
|
static_cast<int>(GameCanvas::HEIGHT * 0.05F), // Posición Y proporcional (~5% desde arriba)
|
|
game_logo_surface_->getWidth(),
|
|
game_logo_surface_->getHeight())),
|
|
title_surface_(std::make_shared<Surface>(Options::game.width, Options::game.height)),
|
|
delta_timer_(std::make_unique<DeltaTimer>()),
|
|
menu_text_(Resource::Cache::get()->getText("gauntlet")) {
|
|
// Inicializa arrays con valores por defecto
|
|
temp_keys_.fill(SDL_SCANCODE_UNKNOWN);
|
|
temp_buttons_.fill(-1);
|
|
|
|
// Determina el estado inicial
|
|
state_ = State::MAIN_MENU;
|
|
|
|
// Establece SceneManager
|
|
SceneManager::current = SceneManager::Scene::TITLE;
|
|
SceneManager::options = SceneManager::Options::NONE;
|
|
|
|
// Acciones iniciales
|
|
Screen::get()->setBorderColor(Color::index(Color::Cpc::BLACK)); // Cambia el color del borde
|
|
Audio::get()->playMusic(Defaults::Music::TITLE_TRACK); // Inicia la musica
|
|
}
|
|
|
|
// 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 (is_remapping_joystick_ && !remap_completed_ &&
|
|
(event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN || event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION)) {
|
|
handleJoystickRemap(event);
|
|
continue; // No procesar más este evento
|
|
}
|
|
|
|
if (event.type == SDL_EVENT_KEY_DOWN) {
|
|
// Si estamos en modo remap de teclado, capturar tecla
|
|
if (is_remapping_keyboard_ && !remap_completed_) {
|
|
handleKeyboardRemap(event);
|
|
}
|
|
// Si estamos en el menú principal normal
|
|
else if (state_ == State::MAIN_MENU && !is_remapping_keyboard_ && !is_remapping_joystick_) {
|
|
handleMainMenuKeyPress(event.key.key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Maneja las teclas del menu principal
|
|
void Title::handleMainMenuKeyPress(SDL_Keycode key) {
|
|
switch (key) {
|
|
case SDLK_1:
|
|
// PLAY
|
|
exit_scene_ = SceneManager::Scene::GAME;
|
|
transitionToState(State::FADE_MENU);
|
|
Audio::get()->fadeOutMusic(Defaults::Music::FADE_DURATION_MS);
|
|
break;
|
|
|
|
case SDLK_2:
|
|
// REDEFINE KEYBOARD
|
|
is_remapping_keyboard_ = true;
|
|
is_remapping_joystick_ = false;
|
|
remap_step_ = 0;
|
|
remap_completed_ = false;
|
|
remap_error_message_.clear();
|
|
state_time_ = 0.0F;
|
|
break;
|
|
|
|
case SDLK_3:
|
|
// REDEFINE JOYSTICK (siempre visible, pero solo funciona si hay gamepad)
|
|
if (Input::get()->gameControllerFound()) {
|
|
is_remapping_keyboard_ = false;
|
|
is_remapping_joystick_ = true;
|
|
remap_step_ = 0;
|
|
remap_completed_ = false;
|
|
remap_error_message_.clear();
|
|
axis_cooldown_ = 0.0F;
|
|
state_time_ = 0.0F;
|
|
}
|
|
// Si no hay gamepad, simplemente no hacer nada
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Comprueba las entradas
|
|
void Title::handleInput(float delta_time) {
|
|
Input::get()->update();
|
|
|
|
// Permitir cancelar remap con ESC/CANCEL
|
|
if ((is_remapping_keyboard_ || is_remapping_joystick_) && !remap_completed_) {
|
|
if (Input::get()->checkAction(InputAction::CANCEL, Input::DO_NOT_ALLOW_REPEAT)) {
|
|
is_remapping_keyboard_ = false;
|
|
is_remapping_joystick_ = false;
|
|
remap_step_ = 0;
|
|
remap_completed_ = false;
|
|
remap_error_message_.clear();
|
|
}
|
|
// Durante el remap, no procesar otras entradas
|
|
GlobalInputs::handle();
|
|
return;
|
|
}
|
|
|
|
GlobalInputs::handle();
|
|
}
|
|
|
|
// 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::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::MAIN_MENU:
|
|
updateMainMenu(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 MAIN_MENU
|
|
void Title::updateMainMenu(float delta_time) {
|
|
// Si estamos en modo remap, manejar la lógica específica
|
|
if (is_remapping_keyboard_ || is_remapping_joystick_) {
|
|
// Decrementar cooldown de ejes si estamos capturando botones de joystick
|
|
if (is_remapping_joystick_ && axis_cooldown_ > 0.0F) {
|
|
axis_cooldown_ -= delta_time;
|
|
axis_cooldown_ = std::max(axis_cooldown_, 0.0F);
|
|
}
|
|
|
|
// Si el remap está completado, esperar antes de guardar
|
|
if (remap_completed_) {
|
|
state_time_ += delta_time;
|
|
if (state_time_ >= KEYBOARD_REMAP_DISPLAY_DELAY) {
|
|
if (is_remapping_keyboard_) {
|
|
applyKeyboardRemap();
|
|
} else if (is_remapping_joystick_) {
|
|
applyJoystickRemap();
|
|
}
|
|
// Resetear estado de remap
|
|
is_remapping_keyboard_ = false;
|
|
is_remapping_joystick_ = false;
|
|
remap_completed_ = false;
|
|
state_time_ = 0.0F;
|
|
}
|
|
}
|
|
} else {
|
|
// Incrementa el temporizador solo en el menú principal normal
|
|
state_time_ += delta_time;
|
|
|
|
// Si el tiempo alcanza el timeout, vuelve al logo
|
|
if (state_time_ >= MAIN_MENU_IDLE_TIMEOUT) {
|
|
exit_scene_ = SceneManager::Scene::LOGO;
|
|
transitionToState(State::FADE_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 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(Color::index(Color::Cpc::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();
|
|
}
|
|
}
|
|
|
|
// Dibuja el logo con el titulo del juego
|
|
void Title::renderGameLogo() {
|
|
game_logo_sprite_->render();
|
|
}
|
|
|
|
// Dibuja el menu principal
|
|
void Title::renderMainMenu() {
|
|
// Si estamos en modo remap, mostrar la pantalla correspondiente
|
|
if (is_remapping_keyboard_) {
|
|
renderKeyboardRemap();
|
|
return;
|
|
}
|
|
if (is_remapping_joystick_) {
|
|
renderJoystickRemap();
|
|
return;
|
|
}
|
|
|
|
// Zona dinámica del menú (proporcional al canvas)
|
|
// El logo ocupa la parte superior, el menú se centra en el espacio restante
|
|
const int LOGO_BOTTOM = static_cast<int>(GameCanvas::HEIGHT * 0.25F); // Espacio reservado para logo
|
|
const int MENU_ZONE_HEIGHT = GameCanvas::HEIGHT - LOGO_BOTTOM; // Espacio disponible para menú
|
|
|
|
// Menú principal normal con 3 opciones centradas verticalmente en la zona
|
|
const Uint8 COLOR = stringToColor("green");
|
|
const int TEXT_SIZE = menu_text_->getCharacterSize();
|
|
const int MENU_CENTER_Y = LOGO_BOTTOM + (MENU_ZONE_HEIGHT / 2);
|
|
const int SPACING = 2 * TEXT_SIZE; // Espaciado entre opciones
|
|
|
|
// Calcula posiciones centradas verticalmente (3 items con espaciado)
|
|
const int TOTAL_HEIGHT = 2 * SPACING; // 2 espacios entre 3 items
|
|
const int START_Y = MENU_CENTER_Y - (TOTAL_HEIGHT / 2);
|
|
|
|
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, START_Y, "1. PLAY", 1, COLOR);
|
|
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, START_Y + SPACING, "2. REDEFINE KEYBOARD", 1, COLOR);
|
|
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, START_Y + (2 * SPACING), "3. REDEFINE JOYSTICK", 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(Color::index(Color::Cpc::BLACK));
|
|
|
|
switch (state_) {
|
|
case State::MAIN_MENU:
|
|
case State::FADE_MENU:
|
|
renderGameLogo();
|
|
renderMainMenu();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Deja el renderizador como estaba
|
|
Screen::get()->setRendererSurface(previuos_renderer);
|
|
}
|
|
|
|
// Maneja la captura de teclas para redefinir el teclado
|
|
void Title::handleKeyboardRemap(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) {
|
|
remap_completed_ = true;
|
|
state_time_ = 0.0F; // Resetear el timer para el delay de 1 segundo
|
|
}
|
|
}
|
|
|
|
// Valida si una tecla es permitida
|
|
auto Title::isKeyValid(SDL_Scancode scancode) -> bool {
|
|
// 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
|
|
auto Title::isKeyDuplicate(SDL_Scancode scancode, int current_step) -> bool {
|
|
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
|
|
auto Title::getActionName(int step) -> std::string {
|
|
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::keyboard_controls.key_left = temp_keys_[0];
|
|
Options::keyboard_controls.key_right = temp_keys_[1];
|
|
Options::keyboard_controls.key_jump = temp_keys_[2];
|
|
|
|
// Aplicar los bindings al sistema de Input
|
|
Input::get()->applyKeyboardBindingsFromOptions();
|
|
|
|
// Guardar a archivo de configuracion
|
|
Options::saveToFile();
|
|
}
|
|
|
|
// Dibuja la pantalla de redefinir teclado
|
|
void Title::renderKeyboardRemap() {
|
|
// Zona dinámica del menú (proporcional al canvas)
|
|
const int LOGO_BOTTOM = static_cast<int>(GameCanvas::HEIGHT * 0.25F);
|
|
const int MENU_ZONE_HEIGHT = GameCanvas::HEIGHT - LOGO_BOTTOM;
|
|
|
|
const Uint8 COLOR = stringToColor("green");
|
|
const Uint8 ERROR_COLOR = stringToColor("red");
|
|
const int TEXT_SIZE = menu_text_->getCharacterSize();
|
|
const int MENU_CENTER_Y = LOGO_BOTTOM + (MENU_ZONE_HEIGHT / 2);
|
|
|
|
// Calcula posiciones centradas verticalmente
|
|
// Layout: Mensaje principal, espacio, 3 teclas (LEFT/RIGHT/JUMP), espacio, mensaje de error
|
|
const int LINE_SPACING = TEXT_SIZE;
|
|
const int START_Y = MENU_CENTER_Y - (2 * TEXT_SIZE); // Centrado aproximado
|
|
|
|
// Mensaje principal: "PRESS KEY FOR [ACTION]" o "KEYS DEFINED" si completado
|
|
if (remap_step_ >= 3) {
|
|
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, START_Y, "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_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, START_Y, MESSAGE, 1, COLOR);
|
|
}
|
|
|
|
// Mostrar teclas ya capturadas (con espaciado de 2 líneas desde el mensaje principal)
|
|
const int KEYS_START_Y = START_Y + (2 * LINE_SPACING);
|
|
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_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, KEYS_START_Y, 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_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, KEYS_START_Y + LINE_SPACING, 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_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, KEYS_START_Y + (2 * LINE_SPACING), JUMP_MSG, 1, COLOR);
|
|
}
|
|
|
|
// Mensaje de error si existe (4 líneas después del inicio de las teclas)
|
|
if (!remap_error_message_.empty()) {
|
|
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, KEYS_START_Y + (4 * LINE_SPACING), remap_error_message_, 1, ERROR_COLOR);
|
|
}
|
|
}
|
|
|
|
// Dibuja la pantalla de redefinir joystick
|
|
void Title::renderJoystickRemap() {
|
|
// Zona dinámica del menú (proporcional al canvas)
|
|
const int LOGO_BOTTOM = static_cast<int>(GameCanvas::HEIGHT * 0.25F);
|
|
const int MENU_ZONE_HEIGHT = GameCanvas::HEIGHT - LOGO_BOTTOM;
|
|
|
|
const Uint8 COLOR = stringToColor("green");
|
|
const Uint8 ERROR_COLOR = stringToColor("red");
|
|
const int TEXT_SIZE = menu_text_->getCharacterSize();
|
|
const int MENU_CENTER_Y = LOGO_BOTTOM + (MENU_ZONE_HEIGHT / 2);
|
|
|
|
// Calcula posiciones centradas verticalmente
|
|
// Layout: Mensaje principal, espacio, 3 botones (LEFT/RIGHT/JUMP), espacio, mensaje de error
|
|
const int LINE_SPACING = TEXT_SIZE;
|
|
const int START_Y = MENU_CENTER_Y - (2 * TEXT_SIZE); // Centrado aproximado
|
|
|
|
// Mensaje principal: "PRESS BUTTON FOR [ACTION]" o "BUTTONS DEFINED" si completado
|
|
if (remap_step_ >= 3) {
|
|
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, START_Y, "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_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, START_Y, MESSAGE, 1, COLOR);
|
|
}
|
|
|
|
// Mostrar botones ya capturados (con espaciado de 2 líneas desde el mensaje principal)
|
|
const int BUTTONS_START_Y = START_Y + (2 * LINE_SPACING);
|
|
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_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, BUTTONS_START_Y, 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_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, BUTTONS_START_Y + LINE_SPACING, 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_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, BUTTONS_START_Y + (2 * LINE_SPACING), JUMP_MSG, 1, COLOR);
|
|
}
|
|
|
|
// Mensaje de error si existe (4 líneas después del inicio de los botones)
|
|
if (!remap_error_message_.empty()) {
|
|
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, GameCanvas::CENTER_X, BUTTONS_START_Y + (4 * LINE_SPACING), remap_error_message_, 1, ERROR_COLOR);
|
|
}
|
|
}
|
|
|
|
// Maneja la captura de botones del gamepad para redefinir
|
|
void Title::handleJoystickRemap(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) {
|
|
remap_completed_ = true;
|
|
state_time_ = 0.0F; // Resetear el timer para el delay
|
|
}
|
|
}
|
|
|
|
// Valida si un botón está duplicado
|
|
auto Title::isButtonDuplicate(int button, int current_step) -> bool {
|
|
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();
|
|
}
|
|
|
|
// Retorna el nombre amigable del botón del gamepad
|
|
auto Title::getButtonName(int button) -> std::string {
|
|
// 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 GAMEPAD_BUTTON = static_cast<SDL_GamepadButton>(button);
|
|
const char* button_name = SDL_GetGamepadStringForButton(GAMEPAD_BUTTON);
|
|
return (button_name != nullptr) ? std::string(button_name) : "UNKNOWN";
|
|
} |