#include "game/scenes/loading_screen.hpp" #include #include // Para std::sin #include // 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(mono_loading_screen_surface_, 0, 0, mono_loading_screen_surface_->getWidth(), mono_loading_screen_surface_->getHeight())), color_loading_screen_sprite_(std::make_unique(color_loading_screen_surface_, 0, 0, color_loading_screen_surface_->getWidth(), color_loading_screen_surface_->getHeight())), program_sprite_(std::make_unique(Resource::get()->getSurface("program_jaildoc.gif"))), screen_surface_(std::make_shared(Options::game.width, Options::game.height)), delta_timer_(std::make_unique()) { // Configura la superficie donde se van a pintar los sprites screen_surface_->clear(static_cast(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(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(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(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((CURRENT_BLOCK * COLOR_BLOCK_SPACING) % 256); load_rect_.y = static_cast((CURRENT_BLOCK / COLOR_BLOCKS_PER_ROW) * COLOR_BLOCK_SPACING); load_rect_.w = static_cast(COLOR_BLOCK_WIDTH); load_rect_.h = static_cast(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(PaletteColor::BLUE)); // Añade lineas amarillas const auto COLOR = static_cast(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(PaletteColor::CYAN) : static_cast(PaletteColor::RED)); // Añade lineas rojas o azules const auto COLOR = carrier_.toggle ? static_cast(PaletteColor::RED) : static_cast(PaletteColor::CYAN); 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 = true; // Primera linea (con el color y tamaño de la portadora) int row = 0; const int FIRST_ROW_HEIGHT = static_cast(carrier_.offset); if (draw_enabled) { for (int i = row; i < row + FIRST_ROW_HEIGHT; ++i) { border->drawLine(0, i, WIDTH, i, COLOR); } } row += FIRST_ROW_HEIGHT; draw_enabled = !draw_enabled; // Resto de lineas (siguen a la portadora) 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(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; }