Files
jaildoctors_dilemma/source/game/scenes/loading_screen.cpp
2025-11-07 17:58:47 +01:00

474 lines
16 KiB
C++

#include "game/scenes/loading_screen.hpp"
#include <SDL3/SDL.h>
#include <cmath> // Para std::sin
#include <cstdlib> // Para rand
#include "core/audio/audio.hpp" // Para Audio
#include "core/input/global_inputs.hpp" // Para check
#include "core/input/input.hpp" // Para Input
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/surface.hpp" // Para Surface
#include "core/rendering/surface_sprite.hpp" // Para SSprite
#include "core/resources/resource.hpp" // Para Resource
#include "core/system/global_events.hpp" // Para check
#include "game/options.hpp" // Para Options, options, SectionState, Options...
#include "game/scene_manager.hpp" // Para SceneManager
#include "utils/defines.hpp" // Para GAME_SPEED
#include "utils/utils.hpp" // Para stringToColor, PaletteColor
// Constructor
LoadingScreen::LoadingScreen()
: mono_loading_screen_surface_(Resource::get()->getSurface("loading_screen_bn.gif")),
color_loading_screen_surface_(Resource::get()->getSurface("loading_screen_color.gif")),
mono_loading_screen_sprite_(std::make_unique<SurfaceSprite>(mono_loading_screen_surface_, 0, 0, mono_loading_screen_surface_->getWidth(), mono_loading_screen_surface_->getHeight())),
color_loading_screen_sprite_(std::make_unique<SurfaceSprite>(color_loading_screen_surface_, 0, 0, color_loading_screen_surface_->getWidth(), color_loading_screen_surface_->getHeight())),
program_sprite_(std::make_unique<SurfaceSprite>(Resource::get()->getSurface("program_jaildoc.gif"))),
screen_surface_(std::make_shared<Surface>(Options::game.width, Options::game.height)),
delta_timer_(std::make_unique<DeltaTimer>()) {
// Configura la superficie donde se van a pintar los sprites
screen_surface_->clear(static_cast<Uint8>(PaletteColor::WHITE));
// Inicializa variables
SceneManager::current = SceneManager::Scene::LOADING_SCREEN;
SceneManager::options = SceneManager::Options::NONE;
program_sprite_->setPosition(0.0F, 8.0F);
// Inicializa el array de índices de líneas
initLineIndexArray();
// Cambia el color del borde
Screen::get()->setBorderColor(stringToColor("white"));
transitionToState(State::SILENT1);
}
// Destructor
LoadingScreen::~LoadingScreen() {
Audio::get()->stopMusic();
}
// Comprueba el manejador de eventos
void LoadingScreen::handleEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
GlobalEvents::handle(event);
}
}
// Comprueba las entradas
void LoadingScreen::handleInput() {
Input::get()->update();
GlobalInputs::handle();
}
// Inicializa el array de índices de líneas (imita el direccionamiento de memoria del Spectrum)
void LoadingScreen::initLineIndexArray() {
for (int i = 0; i < MONO_TOTAL_LINES; ++i) {
if (i < 64) { // Primer bloque de 2K
line_index_[i] = ((i % 8) * 8) + (i / 8);
} else if (i < 128) { // Segundo bloque de 2K
line_index_[i] = 64 + ((i % 8) * 8) + ((i - 64) / 8);
} else { // Tercer bloque de 2K
line_index_[i] = 128 + ((i % 8) * 8) + ((i - 128) / 8);
}
}
}
// Transiciona a un nuevo estado
void LoadingScreen::transitionToState(State new_state) {
state_ = new_state;
state_time_ = 0.0F;
// Acciones específicas al entrar en cada estado
switch (new_state) {
case State::SILENT1:
case State::SILENT2:
current_border_type_ = Border::RED;
Audio::get()->stopMusic();
break;
case State::HEADER1:
case State::HEADER2:
current_border_type_ = Border::RED_AND_CYAN;
Audio::get()->playMusic("loading_header.ogg", 0);
break;
case State::DATA1:
printProgramName();
current_border_type_ = Border::YELLOW_AND_BLUE;
Audio::get()->playMusic("loading_data1.ogg", 0);
break;
case State::DATA2:
current_border_type_ = Border::YELLOW_AND_BLUE;
Audio::get()->playMusic("loading_data2.ogg", 0);
break;
case State::LOADING_MONO:
current_border_type_ = Border::YELLOW_AND_BLUE;
Audio::get()->playMusic("loading_screen_data.ogg", 0);
break;
case State::LOADING_COLOR:
current_border_type_ = Border::YELLOW_AND_BLUE;
Audio::get()->playMusic("loading_screen_color.ogg", 0);
break;
case State::COMPLETE:
current_border_type_ = Border::BLACK;
// Transicionar a la pantalla de título
SceneManager::current = SceneManager::Scene::TITLE;
SceneManager::options = SceneManager::Options::TITLE_WITH_LOADING_SCREEN;
Audio::get()->stopMusic();
break;
}
}
// Actualiza el estado actual
void LoadingScreen::updateState(float delta_time) {
state_time_ += delta_time;
// Transiciones automáticas por tiempo para los estados iniciales
// LOADING_MONO y LOADING_COLOR transicionan en sus propias funciones
switch (state_) {
case State::SILENT1:
if (state_time_ >= SILENT1_DURATION) {
transitionToState(State::HEADER1);
}
break;
case State::HEADER1:
if (state_time_ >= HEADER1_DURATION) {
transitionToState(State::DATA1);
}
break;
case State::DATA1:
if (state_time_ >= DATA1_DURATION) {
transitionToState(State::SILENT2);
}
break;
case State::SILENT2:
if (state_time_ >= SILENT2_DURATION) {
transitionToState(State::HEADER2);
}
break;
case State::HEADER2:
if (state_time_ >= HEADER2_DURATION) {
transitionToState(State::LOADING_MONO);
}
break;
case State::DATA2:
if (state_time_ >= DATA2_DURATION) {
transitionToState(State::COMPLETE);
}
break;
case State::LOADING_MONO:
case State::LOADING_COLOR:
case State::COMPLETE:
// Estos estados se gestionan en updateMonoLoad/updateColorLoad
break;
}
}
// Gestiona la carga monocromática (time-based simplificado)
void LoadingScreen::updateMonoLoad(float delta_time) {
// Calcular progreso lineal (0.0 - 1.0)
float progress = state_time_ / LOADING_MONO_DURATION;
progress = std::min(progress, 1.0F);
// Calcular paso total actual (0-959)
const int TOTAL_STEPS = MONO_TOTAL_LINES * MONO_STEPS_PER_LINE; // 192 * 5 = 960
const int CURRENT_STEP = static_cast<int>(progress * TOTAL_STEPS);
// Calcular línea y sub-paso
const int CURRENT_LINE = CURRENT_STEP / MONO_STEPS_PER_LINE; // 0-191
const int CURRENT_SUBSTEP = CURRENT_STEP % MONO_STEPS_PER_LINE; // 0-4
// Verificar si ha completado todas las líneas
if (CURRENT_LINE >= MONO_TOTAL_LINES) {
transitionToState(State::LOADING_COLOR);
return;
}
// Calcular rectángulo de clip (con floats para mayor precisión)
const float TEXTURE_WIDTH = mono_loading_screen_surface_->getWidth();
const float CLIP_WIDTH = TEXTURE_WIDTH / MONO_STEPS_PER_LINE;
const float CLIP_X = CURRENT_SUBSTEP * CLIP_WIDTH;
load_rect_.x = CLIP_X;
load_rect_.y = static_cast<float>(line_index_[CURRENT_LINE]);
load_rect_.w = CLIP_WIDTH;
load_rect_.h = 1.0F;
// Configurar y dibujar sobre screen_surface_
mono_loading_screen_sprite_->setClip(load_rect_);
mono_loading_screen_sprite_->setPosition(load_rect_);
auto previous_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(screen_surface_);
mono_loading_screen_sprite_->render();
Screen::get()->setRendererSurface(previous_renderer);
}
// Gestiona la carga en color
void LoadingScreen::updateColorLoad(float delta_time) {
// Calcular progreso lineal (0.0 - 1.0)
float progress = state_time_ / LOADING_COLOR_DURATION;
progress = std::min(progress, 1.0F);
// Calcular iteración actual (el código original incrementaba de 2 en 2)
const int TOTAL_ITERATIONS = COLOR_TOTAL_BLOCKS / 2; // 768 / 2 = 384 iteraciones
const int CURRENT_ITERATION = static_cast<int>(progress * TOTAL_ITERATIONS);
// Convertir a bloque (incrementa de 2 en 2, empezando en 0)
const int CURRENT_BLOCK = CURRENT_ITERATION * 2;
// Verificar si ha completado todos los bloques
if (CURRENT_BLOCK >= COLOR_TOTAL_BLOCKS) {
transitionToState(State::DATA2);
return;
}
// Calcular posición del bloque
load_rect_.x = static_cast<float>((CURRENT_BLOCK * COLOR_BLOCK_SPACING) % 256);
load_rect_.y = static_cast<float>((CURRENT_BLOCK / COLOR_BLOCKS_PER_ROW) * COLOR_BLOCK_SPACING);
load_rect_.w = static_cast<float>(COLOR_BLOCK_WIDTH);
load_rect_.h = static_cast<float>(COLOR_BLOCK_HEIGHT);
// Configurar y dibujar sobre screen_surface_
color_loading_screen_sprite_->setClip(load_rect_);
color_loading_screen_sprite_->setPosition(load_rect_);
auto previous_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(screen_surface_);
color_loading_screen_sprite_->render();
Screen::get()->setRendererSurface(previous_renderer);
}
// Dibuja el efecto de carga amarillo y azul en el borde
void LoadingScreen::renderDataBorder() {
// Obtiene la Surface del borde
auto border = Screen::get()->getBorderSurface();
// Pinta el borde de color azul
border->clear(static_cast<Uint8>(PaletteColor::BLUE));
// Añade lineas amarillas
const auto COLOR = static_cast<Uint8>(PaletteColor::YELLOW);
const int WIDTH = Options::game.width + (Options::video.border.width * 2);
const int HEIGHT = Options::game.height + (Options::video.border.height * 2);
bool draw_enabled = rand() % 2 == 0;
int row = 0;
while (row < HEIGHT) {
const int ROW_HEIGHT = (rand() % 4) + 3;
if (draw_enabled) {
for (int i = row; i < row + ROW_HEIGHT; ++i) {
border->drawLine(0, i, WIDTH, i, COLOR);
}
}
row += ROW_HEIGHT;
draw_enabled = !draw_enabled;
}
}
// Dibuja el efecto de carga rojo y azul en el borde
void LoadingScreen::renderHeaderBorder() const {
// Obtiene la Surface del borde
auto border = Screen::get()->getBorderSurface();
// Pinta el borde de color azul o rojo
border->clear(carrier_.toggle ? static_cast<Uint8>(PaletteColor::CYAN) : static_cast<Uint8>(PaletteColor::RED));
// Añade lineas rojas o azules
const auto COLOR = carrier_.toggle ? static_cast<Uint8>(PaletteColor::RED) : static_cast<Uint8>(PaletteColor::CYAN);
const int WIDTH = Options::game.width + (Options::video.border.width * 2);
const int HEIGHT = Options::game.height + (Options::video.border.height * 2);
// Primera linea (con el color y tamaño de la portadora)
int row = 0;
const int FIRST_ROW_HEIGHT = static_cast<int>(carrier_.offset);
for (int i = row; i < row + FIRST_ROW_HEIGHT; ++i) {
border->drawLine(0, i, WIDTH, i, COLOR);
}
row += FIRST_ROW_HEIGHT;
// Resto de lineas (siguen a la portadora)
bool draw_enabled = false;
while (row < HEIGHT) {
if (draw_enabled) {
for (int i = row; i < row + HEADER_DATAROW_HEIGHT; ++i) {
border->drawLine(0, i, WIDTH, i, COLOR);
}
}
row += HEADER_DATAROW_HEIGHT;
draw_enabled = !draw_enabled;
}
}
// Dibuja el borde de color
void LoadingScreen::renderColoredBorder(PaletteColor color) {
// Obtiene la Surface del borde
auto border = Screen::get()->getBorderSurface();
// Pinta el borde de color azul
border->clear(static_cast<Uint8>(color));
}
// Actualiza las variables
void LoadingScreen::update() {
const float DELTA_TIME = delta_timer_->tick();
handleEvents(); // Comprueba los eventos
handleInput(); // Comprueba las entradas
updateState(DELTA_TIME); // Actualiza el estado y gestiona transiciones
// Actualizar la carga según el estado actual
switch (state_) {
case State::DATA1:
case State::DATA2:
// Por ahora no hacen nada específico
break;
case State::SILENT1:
case State::SILENT2:
updateSilent(DELTA_TIME);
break;
case State::HEADER1:
case State::HEADER2:
updateCarrier(DELTA_TIME);
break;
case State::LOADING_MONO:
updateMonoLoad(DELTA_TIME);
break;
case State::LOADING_COLOR:
updateColorLoad(DELTA_TIME);
break;
case State::COMPLETE:
// No hay más actualizaciones
break;
}
Audio::update(); // Actualiza el objeto Audio
Screen::get()->update(DELTA_TIME); // Actualiza el objeto Screen
}
// Dibuja en pantalla
void LoadingScreen::render() {
// Pinta el borde
renderBorder();
// Prepara para empezar a dibujar en la textura de juego
Screen::get()->start();
Screen::get()->clearSurface(stringToColor("white"));
// Copia la surface a la surface de Screen
screen_surface_->render(0, 0);
// Vuelca el contenido del renderizador en pantalla
Screen::get()->render();
}
// Bucle para el logo del juego
void LoadingScreen::run() {
// Ajusta el volumen
Audio::get()->setMusicVolume(50);
// Limpia la pantalla
Screen::get()->start();
Screen::get()->clearRenderer();
Screen::get()->render();
while (SceneManager::current == SceneManager::Scene::LOADING_SCREEN) {
update();
render();
}
Audio::get()->setMusicVolume(100);
}
// Pinta el borde
void LoadingScreen::renderBorder() {
if (Options::video.border.enabled) {
// Dibuja el efecto de carga en el borde según el tipo actual
switch (current_border_type_) {
case Border::YELLOW_AND_BLUE:
renderDataBorder();
break;
case Border::RED_AND_CYAN:
renderHeaderBorder();
break;
case Border::WHITE:
renderColoredBorder(PaletteColor::WHITE);
break;
case Border::BLACK:
renderColoredBorder(PaletteColor::BLACK);
break;
case Border::RED:
renderColoredBorder(PaletteColor::RED);
break;
case Border::CYAN:
renderColoredBorder(PaletteColor::CYAN);
break;
case Border::NONE:
// No renderizar borde
break;
}
}
}
// Escribe el nombre del programa
void LoadingScreen::printProgramName() {
auto previous_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(screen_surface_);
program_sprite_->render();
Screen::get()->setRendererSurface(previous_renderer);
}
// Actualiza la portadora
void LoadingScreen::updateCarrier(float delta_time) {
constexpr float CARRIER_BASE_SPEED = -250.0F;
constexpr float CARRIER_HEIGHT = HEADER_DATAROW_HEIGHT;
// Oscilación compuesta: mezcla de dos frecuencias para evitar patrón predecible
const float MODULATION = std::sin(carrier_.total_time * 1.2F) * std::sin((carrier_.total_time * 0.35F) + 1.0F);
const float SPEED = CARRIER_BASE_SPEED * (0.5F + 0.5F * MODULATION); // rango [-200, 0]
carrier_.offset += SPEED * delta_time;
if (carrier_.offset < 0.0F) {
carrier_.offset += CARRIER_HEIGHT; // reinicia al rango [0,HEADER_DATAROW_HEIGHT]
carrier_.toggle = !carrier_.toggle;
}
carrier_.total_time += delta_time;
}
// Actualiza el ruido durante el tiempo de silencio
void LoadingScreen::updateSilent(float delta_time) {
constexpr float NOISE_THRESHOLD = 0.35F;
// Oscilación compuesta para simular picos de ruido
const float MODULATION = std::sin(noise_.total_time * 4.2F) * std::sin((noise_.total_time * 1.7F) + 0.5F);
noise_.value = std::fabs(MODULATION); // rango [0.0, 1.0]
// Detecta cruce de umbral solo si venía de abajo
if (noise_.value > NOISE_THRESHOLD && !noise_.crossed) {
noise_.crossed = true;
current_border_type_ = (current_border_type_ == Border::RED) ? Border::CYAN : Border::RED;
}
// Restablece el flag cuando baja del umbral
if (noise_.value < NOISE_THRESHOLD) {
noise_.crossed = false;
}
noise_.total_time += delta_time;
}