1088 lines
36 KiB
C++
1088 lines
36 KiB
C++
#include "game/scenes/title.h"
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <cstdlib> // for rand
|
|
#include <iostream> // for basic_ostream, operator<<, basic_ostrea...
|
|
#include <string> // 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::Device::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::Device::KEYBOARD;
|
|
Options::inputs.push_back(inp);
|
|
|
|
inp.id = 0;
|
|
inp.name = "GAME CONTROLLER";
|
|
inp.device_type = Input::Device::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(BG_COLOR);
|
|
|
|
// 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(BG_COLOR);
|
|
|
|
// 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(BG_COLOR);
|
|
|
|
// 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, NO_COLOR, 1, SHADOW_COLOR);
|
|
}
|
|
|
|
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, NO_COLOR, 1, SHADOW_COLOR);
|
|
}
|
|
|
|
// 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(Input::Action::EXIT, Input::Repeat::OFF)) {
|
|
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::Device::KEYBOARD:
|
|
menu_.options->setItemCaption(i, Lang::get()->getText(69)); // KEYBOARD
|
|
menu_.options->setGreyed(i, false);
|
|
break;
|
|
|
|
case Input::Device::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::Device::KEYBOARD:
|
|
menu_.options->setItemCaption(i, Lang::get()->getText(69)); // KEYBOARD
|
|
menu_.options->setGreyed(i, false);
|
|
break;
|
|
|
|
case Input::Device::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::Device::KEYBOARD;
|
|
|
|
Options::inputs[1].id = 0;
|
|
Options::inputs[1].name = "GAME CONTROLLER";
|
|
Options::inputs[1].device_type = Input::Device::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::Device::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::Device::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();
|
|
} |