#include "game/scenes/title.h" #include #include // for rand #include // for basic_ostream, operator<<, basic_ostrea... #include // for basic_string, operator+, char_traits #include "core/audio/audio.hpp" // for Audio #include "core/input/global_inputs.hpp" // for GlobalInputs::handle #include "core/input/input.h" // for Input, INPUT_USE_GAMECONTROLLER, INPUT_... #include "core/locale/lang.h" // for Lang, Lang::Code #include "core/rendering/animatedsprite.h" // for AnimatedSprite #include "core/rendering/fade.h" // for Fade #include "core/rendering/screen.h" // for Screen #include "core/rendering/smartsprite.h" // for SmartSprite #include "core/rendering/sprite.h" // for Sprite #include "core/rendering/text.h" // for Text, TXT_CENTER, TXT_SHADOW #include "core/rendering/texture.h" // for Texture #include "core/resources/asset.h" // for Asset #include "core/resources/resource.h" #include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, SECTION_PROG_QUIT #include "game/game.h" // for Game #include "game/options.hpp" // for Options #include "game/ui/menu.h" // for Menu // Constructor Title::Title(SDL_Renderer *renderer, Section *section) { // Copia las direcciones de los punteros this->renderer_ = renderer; this->section_ = section; // Reserva memoria para los punteros event_handler_ = new SDL_Event(); fade_ = new Fade(renderer); Resource *resource = Resource::get(); dust_texture_ = resource->getTexture("title_dust.png"); coffee_texture_ = resource->getTexture("title_coffee.png"); crisis_texture_ = resource->getTexture("title_crisis.png"); gradient_texture_ = resource->getTexture("title_gradient.png"); coffee_bitmap_ = new SmartSprite(coffee_texture_, renderer); crisis_bitmap_ = new SmartSprite(crisis_texture_, renderer); dust_bitmap_left_ = new AnimatedSprite(dust_texture_, renderer, "", &resource->getAnimationLines("title_dust.ani")); dust_bitmap_right_ = new AnimatedSprite(dust_texture_, renderer, "", &resource->getAnimationLines("title_dust.ani")); gradient_ = new Sprite({0, 0, 256, 192}, gradient_texture_, renderer); text1_ = resource->getText("smb2"); text2_ = resource->getText("8bithud"); #ifdef GAME_CONSOLE menu_.title = R->getMenu("title_gc"); menu_.options = R->getMenu("options_gc"); #else menu_.title = resource->getMenu("title"); menu_.options = resource->getMenu("options"); #endif menu_.player_select = resource->getMenu("player_select"); #ifdef __EMSCRIPTEN__ // En la versión web no se puede cerrar el programa: ocultamos la opción QUIT del menú de título menu_.title->setVisible(3, false); menu_.title->setSelectable(3, false); #endif // Sonidos y música (handles compartidos) crash_sound_ = resource->getSound("title.wav"); title_music_ = resource->getMusic("title.ogg"); // Inicializa los valores init(); } // Destructor Title::~Title() { delete event_handler_; delete fade_; // Las texturas, Text, Menu, sonido y música son propiedad de Resource — // no se liberan aquí. Solo los sprites que posee esta escena. delete coffee_bitmap_; delete crisis_bitmap_; delete dust_bitmap_left_; delete dust_bitmap_right_; delete gradient_; SDL_DestroyTexture(background_); } // Inicializa los valores void Title::init() { // Inicializa variables section_->subsection = SUBSECTION_TITLE_1; counter_ = COUNTER; background_counter_ = 0; background_mode_ = rand() % 2; menu_visible_ = false; menu_.active = menu_.title; next_section_.name = SECTION_PROG_GAME; post_fade_ = 0; ticks_ = 0; ticks_speed_ = 15; fade_->init(0x17, 0x17, 0x26); demo_ = true; vibration_step_ = 0; vibration_initialized_ = false; instructions_active_ = false; demo_game_active_ = false; demo_then_instructions_ = false; // Pone valores por defecto a las opciones de control Options::inputs.clear(); InputDevice inp; inp.id = 0; inp.name = "KEYBOARD"; inp.device_type = INPUT_USE_KEYBOARD; Options::inputs.push_back(inp); inp.id = 0; inp.name = "GAME CONTROLLER"; inp.device_type = INPUT_USE_GAMECONTROLLER; Options::inputs.push_back(inp); // Comprueba si hay mandos conectados checkInputDevices(); // Pone valores por defecto. El primer jugador el teclado. El segundo jugador el primer mando device_index_.clear(); device_index_.push_back(available_input_devices_.size() - 1); // El último dispositivo encontrado es el teclado device_index_.push_back(0); // El primer mando encontrado. Si no ha encontrado ninguno es el teclado // Si ha encontrado un mando se lo asigna al segundo jugador if (Input::get()->gameControllerFound()) { Options::inputs[1].id = available_input_devices_[device_index_[1]].id; Options::inputs[1].name = available_input_devices_[device_index_[1]].name; Options::inputs[1].device_type = available_input_devices_[device_index_[1]].device_type; } else { // Si no ha encontrado un mando, deshabilita la opción de jugar a 2 jugadores menu_.title->setSelectable(1, false); menu_.title->setGreyed(1, true); } // Inicializa el bitmap de Coffee coffee_bitmap_->init(); coffee_bitmap_->setPosX(45); coffee_bitmap_->setPosY(11 - 200); coffee_bitmap_->setWidth(167); coffee_bitmap_->setHeight(46); coffee_bitmap_->setVelX(0.0F); coffee_bitmap_->setVelY(2.5F); coffee_bitmap_->setAccelX(0.0F); coffee_bitmap_->setAccelY(0.1F); coffee_bitmap_->setSpriteClip(0, 0, 167, 46); coffee_bitmap_->setEnabled(true); coffee_bitmap_->setEnabledCounter(0); coffee_bitmap_->setDestX(45); coffee_bitmap_->setDestY(11); // Inicializa el bitmap de Crisis crisis_bitmap_->init(); crisis_bitmap_->setPosX(60); crisis_bitmap_->setPosY(57 + 200); crisis_bitmap_->setWidth(137); crisis_bitmap_->setHeight(46); crisis_bitmap_->setVelX(0.0F); crisis_bitmap_->setVelY(-2.5F); crisis_bitmap_->setAccelX(0.0F); crisis_bitmap_->setAccelY(-0.1F); crisis_bitmap_->setSpriteClip(0, 0, 137, 46); crisis_bitmap_->setEnabled(true); crisis_bitmap_->setEnabledCounter(0); crisis_bitmap_->setDestX(60); crisis_bitmap_->setDestY(57); // Inicializa el bitmap de DustRight dust_bitmap_right_->resetAnimation(); dust_bitmap_right_->setPosX(218); dust_bitmap_right_->setPosY(47); dust_bitmap_right_->setWidth(16); dust_bitmap_right_->setHeight(16); dust_bitmap_right_->setFlip(SDL_FLIP_HORIZONTAL); // Inicializa el bitmap de DustLeft dust_bitmap_left_->resetAnimation(); dust_bitmap_left_->setPosX(33); dust_bitmap_left_->setPosY(47); dust_bitmap_left_->setWidth(16); dust_bitmap_left_->setHeight(16); // Inicializa el sprite con el degradado gradient_->setSpriteClip(0, 96, 256, 192); // Crea el mosaico de fondo del titulo createTiledBackground(); // Coloca la ventana que recorre el mosaico de fondo de manera que coincida con el mosaico que hay pintado en el titulo al iniciar background_window_.x = 128; background_window_.y = 96; background_window_.w = GAMECANVAS_WIDTH; background_window_.h = GAMECANVAS_HEIGHT; // Inicializa los valores del vector con los valores del seno for (int i = 0; i < 360; ++i) { sin_[i] = SDL_sinf((float)i * 3.14F / 180.0F); } // Actualiza los textos de los menus updateMenuLabels(); } // Actualiza las variables del objeto void Title::update() { Audio::update(); checkInput(); if (SDL_GetTicks() - ticks_ <= ticks_speed_) { return; } ticks_ = SDL_GetTicks(); switch (section_->subsection) { case SUBSECTION_TITLE_1: updateTitle1(); break; case SUBSECTION_TITLE_2: updateTitle2(); break; case SUBSECTION_TITLE_3: updateTitle3(); break; default: break; } } // Sección 1 - Titulo desplazandose void Title::updateTitle1() { coffee_bitmap_->update(); crisis_bitmap_->update(); // Si los objetos han llegado a su destino, cambiamos de Sección if (coffee_bitmap_->hasFinished() && crisis_bitmap_->hasFinished()) { section_->subsection = SUBSECTION_TITLE_2; // Pantallazo blanco: pintar sobre el gameCanvas y dejar // que Screen::blit() presente por la ruta activa (GPU o // SDL_Renderer). Un `SDL_RenderPresent(renderer)` directe // crasheja quan el SDL3 GPU ha reclamat la ventana. Screen::get()->start(); SDL_SetRenderDrawColor(renderer_, 0xFF, 0xFF, 0xFF, 0xFF); SDL_RenderClear(renderer_); Screen::get()->blit(); Audio::get()->playSound(crash_sound_); } } // Sección 2 - Titulo vibrando void Title::updateTitle2() { // Captura las posiciones base la primera vez if (!vibration_initialized_) { vibration_coffee_base_x_ = coffee_bitmap_->getPosX(); vibration_crisis_base_x_ = crisis_bitmap_->getPosX(); vibration_initialized_ = true; } const int V[] = {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 0}; coffee_bitmap_->setPosX(vibration_coffee_base_x_ + V[vibration_step_ / 3]); crisis_bitmap_->setPosX(vibration_crisis_base_x_ + V[vibration_step_ / 3]); dust_bitmap_right_->update(); dust_bitmap_left_->update(); vibration_step_++; if (vibration_step_ >= 33) { section_->subsection = SUBSECTION_TITLE_3; vibration_step_ = 0; vibration_initialized_ = false; } } // Sección 3 - La pantalla de titulo con el menú y la música void Title::updateTitle3() { if (counter_ > 0) { if (Audio::getRealMusicState() == Audio::MusicState::STOPPED) { Audio::get()->playMusic(title_music_); } dust_bitmap_right_->update(); dust_bitmap_left_->update(); fade_->update(); if (fade_->hasEnded()) { handlePostFadeAction(); } updateBG(); if (menu_visible_ && !fade_->isEnabled()) { menu_.active->update(); } if (menu_.active->getName() == "TITLE") { handleTitleMenuSelection(); } if (menu_.active->getName() == "PLAYER_SELECT") { handlePlayerSelectMenuSelection(); } if (menu_.active->getName() == "OPTIONS") { handleOptionsMenuSelection(); } if (menu_.active->getName() == "TITLE") { counter_--; } } else if (counter_ == 0) { if (demo_) { demo_then_instructions_ = true; runDemoGame(); } else { section_->name = SECTION_PROG_LOGO; } } if (section_->subsection == SUBSECTION_TITLE_INSTRUCTIONS) { runInstructions(Instructions::Mode::AUTO); } } // Acción tras finalizar el fundido del título void Title::handlePostFadeAction() { switch (post_fade_) { case 0: // 1 PLAYER section_->name = SECTION_PROG_GAME; section_->subsection = SUBSECTION_GAME_PLAY_1P; Audio::get()->stopMusic(); break; case 1: // 2 PLAYERS section_->name = SECTION_PROG_GAME; section_->subsection = SUBSECTION_GAME_PLAY_2P; Audio::get()->stopMusic(); break; case 2: // QUIT #ifndef __EMSCRIPTEN__ section_->name = SECTION_PROG_QUIT; Audio::get()->stopMusic(); #endif break; case 3: // TIME OUT counter_ = COUNTER; menu_.active->reset(); if (demo_) { demo_then_instructions_ = true; runDemoGame(); } else { section_->name = SECTION_PROG_LOGO; } break; default: break; } } // Procesa selección del menú TITLE void Title::handleTitleMenuSelection() { switch (menu_.active->getItemSelected()) { case 0: // 1 PLAYER -> Cambia al manu de selección de jugador menu_.active = menu_.player_select; break; case 1: // 2 PLAYERS post_fade_ = 1; fade_->activateFade(); break; case 2: // OPTIONS menu_.active = menu_.options; prev_video_ = Options::video; prev_window_ = Options::window; prev_settings_ = Options::settings; prev_inputs_ = Options::inputs; break; case 3: // QUIT post_fade_ = 2; fade_->activateFade(); break; default: break; } } // Procesa selección del menú PLAYER_SELECT void Title::handlePlayerSelectMenuSelection() { switch (menu_.active->getItemSelected()) { case 0: // Este item no se puede seleccionar y actua de titulo break; case 1: // BAL1 post_fade_ = 0; Options::settings.player_selected = 0; fade_->activateFade(); break; case 2: // AROUNDER post_fade_ = 0; Options::settings.player_selected = 1; fade_->activateFade(); break; case 3: // BACK menu_.active = menu_.title; menu_.player_select->reset(); break; default: break; } } // Procesa selección del menú OPTIONS void Title::handleOptionsMenuSelection() { switch (menu_.active->getItemSelected()) { case 0: // Difficulty if (Options::settings.difficulty == DIFFICULTY_EASY) { Options::settings.difficulty = DIFFICULTY_NORMAL; } else if (Options::settings.difficulty == DIFFICULTY_NORMAL) { Options::settings.difficulty = DIFFICULTY_HARD; } else { Options::settings.difficulty = DIFFICULTY_EASY; } updateMenuLabels(); break; case 1: // PLAYER 1 CONTROLS updatePlayerInputs(0); updateMenuLabels(); break; case 3: // PLAYER 2 CONTROLS updatePlayerInputs(1); updateMenuLabels(); break; case 5: // Language Options::settings.language = Lang::nextLanguage(Options::settings.language); updateMenuLabels(); break; case 6: // Display mode switchFullScreenModeVar(); if (Options::video.fullscreen) { menu_.options->setSelectable(8, false); menu_.options->setGreyed(8, true); } else { menu_.options->setSelectable(8, true); menu_.options->setGreyed(8, false); } updateMenuLabels(); break; case 8: // Windows size Options::window.zoom++; if (Options::window.zoom > Options::window.max_zoom) { Options::window.zoom = 1; } updateMenuLabels(); break; case 9: // Scale mode Options::video.scale_mode = (Options::video.scale_mode == SDL_SCALEMODE_NEAREST) ? SDL_SCALEMODE_LINEAR : SDL_SCALEMODE_NEAREST; Texture::setGlobalScaleMode(Options::video.scale_mode); reLoadTextures(); updateMenuLabels(); break; case 10: // VSYNC Options::video.vsync = !Options::video.vsync; updateMenuLabels(); break; case 11: // HOW TO PLAY runInstructions(Instructions::Mode::MANUAL); break; case 12: // ACCEPT applyOptions(); menu_.active->reset(); menu_.active = menu_.title; break; case 13: // CANCEL Options::video = prev_video_; Options::window = prev_window_; Options::settings = prev_settings_; Options::inputs = prev_inputs_; updateMenuLabels(); menu_.active->reset(); menu_.active = menu_.title; break; default: break; } } // Dibuja el objeto en pantalla void Title::render() { switch (section_->subsection) { // Sección 1 - Titulo desplazandose case SUBSECTION_TITLE_1: { // Prepara para empezar a dibujar en la textura de juego Screen::get()->start(); // Limpia la pantalla Screen::get()->clean(bgColor); // Dibuja el tileado de fondo { SDL_FRect f_src = {(float)background_window_.x, (float)background_window_.y, (float)background_window_.w, (float)background_window_.h}; SDL_RenderTexture(renderer_, background_, &f_src, nullptr); }; // Dibuja el degradado gradient_->render(); // Dibuja los objetos coffee_bitmap_->render(); crisis_bitmap_->render(); // Vuelca el contenido del renderizador en pantalla Screen::get()->blit(); } break; // Sección 2 - Titulo vibrando case SUBSECTION_TITLE_2: { // Prepara para empezar a dibujar en la textura de juego Screen::get()->start(); // Limpia la pantalla Screen::get()->clean(bgColor); // Dibuja el tileado de fondo { SDL_FRect f_src = {(float)background_window_.x, (float)background_window_.y, (float)background_window_.w, (float)background_window_.h}; SDL_RenderTexture(renderer_, background_, &f_src, nullptr); }; // Dibuja el degradado gradient_->render(); // Dibuja los objetos (posiciones ya actualizadas por update) coffee_bitmap_->render(); crisis_bitmap_->render(); dust_bitmap_right_->render(); dust_bitmap_left_->render(); // Vuelca el contenido del renderizador en pantalla Screen::get()->blit(); } break; // Sección 3 - La pantalla de titulo con el menú y la música case SUBSECTION_TITLE_3: { // Prepara para empezar a dibujar en la textura de juego Screen::get()->start(); // Limpia la pantalla Screen::get()->clean(bgColor); // Dibuja el tileado de fondo { SDL_FRect f_src = {(float)background_window_.x, (float)background_window_.y, (float)background_window_.w, (float)background_window_.h}; SDL_RenderTexture(renderer_, background_, &f_src, nullptr); }; // Dibuja el degradado gradient_->render(); // Dibuja los objetos if (menu_.active->getName() != "OPTIONS") { // Bitmaps con el logo/titulo del juego coffee_bitmap_->render(); crisis_bitmap_->render(); // Texto con el copyright y versión text2_->writeDX(TXT_CENTER | TXT_SHADOW, GAMECANVAS_CENTER_X, GAMECANVAS_HEIGHT - (BLOCK * 2), COPYRIGHT, 1, noColor, 1, shdwTxtColor); } if (menu_visible_) { menu_.active->render(); } dust_bitmap_right_->render(); dust_bitmap_left_->render(); // PRESS ANY KEY! if ((counter_ % 50 > 14) && (!menu_visible_)) { text1_->writeDX(TXT_CENTER | TXT_SHADOW, GAMECANVAS_CENTER_X, PLAY_AREA_THIRD_QUARTER_Y + BLOCK, Lang::get()->getText(23), 1, noColor, 1, shdwTxtColor); } // Fade fade_->render(); // Vuelca el contenido del renderizador en pantalla Screen::get()->blit(); } break; default: break; } } // Comprueba las entradas void Title::checkInput() { #ifndef __EMSCRIPTEN__ if (Input::get()->checkInput(EXIT, REPEAT_FALSE)) { section_->name = SECTION_PROG_QUIT; return; } #endif GlobalInputs::handle(); } // Actualiza el tileado de fondo void Title::updateBG() { if (background_mode_ == 0) { // El tileado de fondo se desplaza en diagonal ++background_window_.x %= 64; ++background_window_.y %= 64; } else { // El tileado de fondo se desplaza en circulo ++background_counter_ %= 360; background_window_.x = 128 + (int(sin_[(background_counter_ + 270) % 360] * 128)); background_window_.y = 96 + (int(sin_[(360 - background_counter_) % 360] * 96)); } } // Cambia el valor de la variable de modo de pantalla completa void Title::switchFullScreenModeVar() { Options::video.fullscreen = !Options::video.fullscreen; } // Actualiza los elementos de los menus void Title::updateMenuLabels() const { int i = 0; // DIFFICULTY switch (Options::settings.difficulty) { case DIFFICULTY_EASY: menu_.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(66)); // EASY break; case DIFFICULTY_NORMAL: menu_.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(67)); // NORMAL break; case DIFFICULTY_HARD: menu_.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(68)); // HARD break; default: menu_.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(67)); // NORMAL break; } i++; // PLAYER 1 CONTROLS menu_.options->setItemCaption(i, Lang::get()->getText(62)); i++; // PLAYER 1 CONTROLS - OPTIONS switch (Options::inputs[0].device_type) { case INPUT_USE_KEYBOARD: menu_.options->setItemCaption(i, Lang::get()->getText(69)); // KEYBOARD menu_.options->setGreyed(i, false); break; case INPUT_USE_GAMECONTROLLER: menu_.options->setItemCaption(i, Lang::get()->getText(70)); // GAME CONTROLLER if (!Input::get()->gameControllerFound()) { menu_.options->setGreyed(i, true); } else { menu_.options->setGreyed(i, false); menu_.options->setItemCaption(i, Options::inputs[0].name); } break; default: menu_.options->setItemCaption(i, Lang::get()->getText(69)); // KEYBOARD break; } i++; // PLAYER 2 CONTROLS menu_.options->setItemCaption(i, Lang::get()->getText(63)); i++; // PLAYER 2 CONTROLS - OPTIONS switch (Options::inputs[1].device_type) { case INPUT_USE_KEYBOARD: menu_.options->setItemCaption(i, Lang::get()->getText(69)); // KEYBOARD menu_.options->setGreyed(i, false); break; case INPUT_USE_GAMECONTROLLER: menu_.options->setItemCaption(i, Lang::get()->getText(70)); // GAME CONTROLLER if (!Input::get()->gameControllerFound()) { menu_.options->setGreyed(i, true); } else { menu_.options->setGreyed(i, false); menu_.options->setItemCaption(i, Options::inputs[1].name); } break; default: menu_.options->setItemCaption(i, Lang::get()->getText(69)); // KEYBOARD break; } i++; // LANGUAGE switch (Options::settings.language) { case Lang::Code::ES_ES: menu_.options->setItemCaption(i, Lang::get()->getText(8) + ": " + Lang::get()->getText(24)); // SPANISH break; case Lang::Code::BA_BA: menu_.options->setItemCaption(i, Lang::get()->getText(8) + ": " + Lang::get()->getText(25)); // VALENCIAN break; case Lang::Code::EN_UK: default: // ENGLISH (i fallback per a llengües no reconegudes) menu_.options->setItemCaption(i, Lang::get()->getText(8) + ": " + Lang::get()->getText(26)); break; } i++; // DISPLAY MODE menu_.options->setItemCaption(i, Lang::get()->getText(58)); i++; // DISPLAY MODE - OPTIONS menu_.options->setItemCaption(i, Options::video.fullscreen ? Lang::get()->getText(5) : Lang::get()->getText(4)); i++; // WINDOW SIZE menu_.options->setItemCaption(i, Lang::get()->getText(7) + " x" + std::to_string(Options::window.zoom)); // WINDOW SIZE i++; // SCALE MODE if (Options::video.scale_mode == SDL_SCALEMODE_LINEAR) { menu_.options->setItemCaption(i, Lang::get()->getText(60) + ": " + Lang::get()->getText(71)); // BILINEAL } else { menu_.options->setItemCaption(i, Lang::get()->getText(60) + ": " + Lang::get()->getText(72)); // LINEAL } i++; // VSYNC if (Options::video.vsync) { menu_.options->setItemCaption(i, Lang::get()->getText(61) + ": " + Lang::get()->getText(73)); // ON } else { menu_.options->setItemCaption(i, Lang::get()->getText(61) + ": " + Lang::get()->getText(74)); // OFF } i++; // HOW TO PLAY menu_.options->setItemCaption(i, Lang::get()->getText(2)); i++; // ACCEPT menu_.options->setItemCaption(i, Lang::get()->getText(9)); // ACCEPT i++; // CANCEL menu_.options->setItemCaption(i, Lang::get()->getText(10)); // CANCEL // Recoloca el menu de opciones menu_.options->centerMenuOnX(GAMECANVAS_CENTER_X); menu_.options->centerMenuOnY(GAMECANVAS_CENTER_Y); menu_.options->centerMenuElementsOnX(); // Establece las etiquetas del menu de titulo #ifdef GAME_CONSOLE menu_.title->setItemCaption(0, Lang::get()->getText(0)); // PLAY #else menu_.title->setItemCaption(0, Lang::get()->getText(51)); // 1 PLAYER #endif menu_.title->setItemCaption(1, Lang::get()->getText(52)); // 2 PLAYERS menu_.title->setItemCaption(2, Lang::get()->getText(1)); // OPTIONS menu_.title->setItemCaption(3, Lang::get()->getText(3)); // QUIT #ifdef __EMSCRIPTEN__ menu_.title->setVisible(3, false); menu_.title->setSelectable(3, false); #endif // Recoloca el menu de titulo menu_.title->centerMenuOnX(GAMECANVAS_CENTER_X); menu_.title->centerMenuElementsOnX(); // Establece las etiquetas del menu de seleccion de jugador menu_.player_select->setItemCaption(0, Lang::get()->getText(39)); // SELECT PLAYER menu_.player_select->setItemCaption(3, Lang::get()->getText(40)); // BACK // Recoloca el menu de selección de jugador menu_.player_select->centerMenuOnX(GAMECANVAS_CENTER_X); menu_.player_select->centerMenuElementsOnX(); #ifdef GAME_CONSOLE menu_.options->setGreyed(1, true); menu_.options->setSelectable(1, false); menu_.options->setGreyed(2, true); menu_.options->setSelectable(2, false); menu_.options->setGreyed(3, true); menu_.options->setSelectable(3, false); menu_.options->setGreyed(4, true); menu_.options->setSelectable(4, false); #endif } // Aplica las opciones de menu seleccionadas void Title::applyOptions() { Screen::get()->setVideoMode(Options::video.fullscreen); Screen::get()->setWindowZoom(Options::window.zoom); Screen::get()->setVSync(Options::video.vsync); Lang::get()->setLang(Options::settings.language); updateMenuLabels(); createTiledBackground(); } // Ejecuta un frame void Title::iterate() { // Si las instrucciones están activas, delega el frame if (instructions_active_) { instructions_->update(); instructions_->render(); if (instructions_->hasFinished()) { bool was_quit = instructions_->isQuitRequested(); delete instructions_; instructions_ = nullptr; instructions_active_ = false; if (was_quit) { section_->name = SECTION_PROG_QUIT; } else if (instructions_mode_ == Instructions::Mode::AUTO) { section_->name = SECTION_PROG_TITLE; init(); demo_ = true; } else { section_->name = SECTION_PROG_TITLE; section_->subsection = SUBSECTION_TITLE_3; } } return; } // Si el juego demo está activo, delega el frame if (demo_game_active_) { // El demo Game necesita section->name == SECTION_PROG_GAME para funcionar section_->name = SECTION_PROG_GAME; demo_game_->iterate(); if (demo_game_->hasFinished()) { // cppcheck-suppress knownConditionTrueFalse const bool WAS_QUIT = (section_->name == SECTION_PROG_QUIT); delete demo_game_; demo_game_ = nullptr; demo_game_active_ = false; // cppcheck-suppress knownConditionTrueFalse if (WAS_QUIT) { section_->name = SECTION_PROG_QUIT; } else if (demo_then_instructions_) { section_->name = SECTION_PROG_TITLE; section_->subsection = SUBSECTION_TITLE_3; demo_then_instructions_ = false; runInstructions(Instructions::Mode::AUTO); } else { section_->name = SECTION_PROG_TITLE; section_->subsection = SUBSECTION_TITLE_1; } } else { // Restaura section para que Director no transicione fuera de Title section_->name = SECTION_PROG_TITLE; } return; } // Ejecución normal del título update(); render(); } // Procesa un evento individual void Title::handleEvent(const SDL_Event *event) { // Si hay un sub-estado activo, delega el evento if (instructions_active_ && (instructions_ != nullptr)) { // SDL_EVENT_QUIT ya lo maneja Director return; } if (demo_game_active_ && (demo_game_ != nullptr)) { demo_game_->handleEvent(event); return; } // SDL_EVENT_QUIT ya lo maneja Director if (event->type == SDL_EVENT_RENDER_DEVICE_RESET || event->type == SDL_EVENT_RENDER_TARGETS_RESET) { reLoadTextures(); } if (section_->subsection == SUBSECTION_TITLE_3) { if ((event->type == SDL_EVENT_KEY_UP) || (event->type == SDL_EVENT_JOYSTICK_BUTTON_UP)) { menu_visible_ = true; counter_ = COUNTER; } } } // Bucle para el titulo del juego (compatibilidad) void Title::run() { while (section_->name == SECTION_PROG_TITLE || instructions_active_ || demo_game_active_) { iterate(); } } // Inicia la parte donde se muestran las instrucciones void Title::runInstructions(Instructions::Mode mode) { instructions_ = new Instructions(renderer_, section_); instructions_->start(mode); instructions_active_ = true; instructions_mode_ = mode; } // Inicia el juego en modo demo void Title::runDemoGame() { // Temporalmente ponemos section para que el constructor de Game funcione section_->name = SECTION_PROG_GAME; section_->subsection = SUBSECTION_GAME_PLAY_1P; demo_game_ = new Game(1, 0, renderer_, true, section_); demo_game_active_ = true; // Restauramos section para que Director no transicione fuera de Title section_->name = SECTION_PROG_TITLE; } // Modifica las opciones para los controles de los jugadores auto Title::updatePlayerInputs(int num_player) -> bool { const int NUM_DEVICES = available_input_devices_.size(); if (!Input::get()->gameControllerFound()) { // Si no hay mandos se deja todo de manera prefijada device_index_[0] = 0; device_index_[1] = 0; Options::inputs[0].id = -1; Options::inputs[0].name = "KEYBOARD"; Options::inputs[0].device_type = INPUT_USE_KEYBOARD; Options::inputs[1].id = 0; Options::inputs[1].name = "GAME CONTROLLER"; Options::inputs[1].device_type = INPUT_USE_GAMECONTROLLER; return true; } // Si hay mas de un dispositivo, se recorre el vector if (Options::settings.console) { std::cout << "numplayer:" << num_player << '\n'; std::cout << "deviceindex:" << device_index_[num_player] << '\n'; } // Incrementa el indice if (device_index_[num_player] < NUM_DEVICES - 1) { device_index_[num_player]++; } else { device_index_[num_player] = 0; } if (Options::settings.console) { std::cout << "deviceindex:" << device_index_[num_player] << '\n'; } // Si coincide con el del otro jugador, se lo intercambian if (device_index_[0] == device_index_[1]) { const int THE_OTHER_PLAYER = (num_player + 1) % 2; device_index_[THE_OTHER_PLAYER]--; if (device_index_[THE_OTHER_PLAYER] < 0) { device_index_[THE_OTHER_PLAYER] = NUM_DEVICES - 1; } } // Copia el dispositivo marcado por el indice a la variable de opciones de cada jugador Options::inputs[0] = available_input_devices_[device_index_[0]]; Options::inputs[1] = available_input_devices_[device_index_[1]]; return true; } // Crea el mosaico de fondo del titulo void Title::createTiledBackground() { // Crea la textura para el mosaico de fondo background_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH * 2, GAMECANVAS_HEIGHT * 2); if (background_ != nullptr) { SDL_SetTextureScaleMode(background_, Texture::current_scale_mode); } if (background_ == nullptr) { if (Options::settings.console) { std::cout << "TitleSurface could not be created!\nSDL Error: " << SDL_GetError() << '\n'; } } // Crea los objetos para pintar en la textura de fondo auto *bg_tile_texture = new Texture(renderer_, Asset::get()->get("title_bg_tile.png")); auto *tile = new Sprite({0, 0, 64, 64}, bg_tile_texture, renderer_); // Prepara para dibujar sobre la textura SDL_SetRenderTarget(renderer_, background_); SDL_SetRenderDrawColor(renderer_, 0x43, 0x43, 0x4F, 0xFF); SDL_RenderClear(renderer_); // Rellena la textura con el tile tile->setSpriteClip(0, 0, 64, 64); for (int i = 0; i < 8; ++i) { for (int j = 0; j < 6; ++j) { tile->setPosX(i * 64); tile->setPosY(j * 64); tile->render(); } } // Vuelve a colocar el renderizador apuntando a la pantalla SDL_SetRenderTarget(renderer_, nullptr); // Libera la memoria utilizada por los objetos bg_tile_texture->unload(); delete bg_tile_texture; delete tile; } // Comprueba cuantos mandos hay conectados para gestionar el menu de opciones. // El estado de Input lo mantiene al día Director via eventos SDL_EVENT_GAMEPAD_ADDED/REMOVED, // así que aquí solo leemos la lista actual sin reescanear. void Title::checkInputDevices() { if (Options::settings.console) { std::cout << "Filling devices for options menu_..." << '\n'; } const int NUM_CONTROLLERS = Input::get()->getNumControllers(); available_input_devices_.clear(); InputDevice temp; // Añade todos los mandos if (NUM_CONTROLLERS > 0) { for (int i = 0; i < NUM_CONTROLLERS; ++i) { temp.id = i; temp.name = Input::get()->getControllerName(i); temp.device_type = INPUT_USE_GAMECONTROLLER; available_input_devices_.push_back(temp); if (Options::settings.console) { std::cout << "Device " << (int)available_input_devices_.size() << " - " << temp.name.c_str() << '\n'; } } } // Añade el teclado al final temp.id = -1; temp.name = "KEYBOARD"; temp.device_type = INPUT_USE_KEYBOARD; available_input_devices_.push_back(temp); if (Options::settings.console) { std::cout << "Device " << (int)available_input_devices_.size() << " - " << temp.name.c_str() << '\n'; std::cout << '\n'; } } // Recarga las texturas void Title::reLoadTextures() { dust_texture_->reLoad(); coffee_texture_->reLoad(); crisis_texture_->reLoad(); gradient_texture_->reLoad(); createTiledBackground(); }