#include "game/scenes/loading_screen.hpp" #include #include // Para rand #include "core/audio/audio.hpp" // Para Audio #include "core/input/global_inputs.hpp" // Para check #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_shared(mono_loading_screen_surface_, 0, 0, mono_loading_screen_surface_->getWidth(), mono_loading_screen_surface_->getHeight())), color_loading_screen_sprite_(std::make_shared(color_loading_screen_surface_, 0, 0, color_loading_screen_surface_->getWidth(), color_loading_screen_surface_->getHeight())), screen_surface_(std::make_shared(Options::game.width, Options::game.height)), delta_timer_(std::make_unique()), state_(LoadingState::SILENT1), state_time_(0.0F), current_border_type_(BorderType::NONE), load_rect_{0, 0, 0, 1.0F} { // 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; // Inicializa el array de índices de líneas initLineIndexArray(); // Cambia el color del borde Screen::get()->setBorderColor(stringToColor("white")); transitionToState(LoadingState::SILENT1); } // Destructor LoadingScreen::~LoadingScreen() { Audio::get()->stopMusic(); } // Comprueba el manejador de eventos void LoadingScreen::checkEvents() { SDL_Event event; while (SDL_PollEvent(&event)) { GlobalEvents::check(event); } } // Comprueba las entradas void LoadingScreen::checkInput() { GlobalInputs::check(); } // 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(LoadingState new_state) { state_ = new_state; state_time_ = 0.0F; // Acciones específicas al entrar en cada estado switch (new_state) { case LoadingState::SILENT1: case LoadingState::SILENT2: current_border_type_ = BorderType::WHITE; Audio::get()->stopMusic(); break; case LoadingState::HEADER1: case LoadingState::HEADER2: current_border_type_ = BorderType::RED; // Reproducir sonido de cargar el header Audio::get()->playMusic("loading_sound1.ogg"); break; case LoadingState::BYTES1: case LoadingState::BYTES2: case LoadingState::LOADING_MONO: current_border_type_ = BorderType::YELLOW; // Reproducir sonido de carga monocromática Audio::get()->playMusic("loading_sound2.ogg"); break; case LoadingState::LOADING_COLOR: current_border_type_ = BorderType::YELLOW; // Reproducir sonido de carga en color Audio::get()->playMusic("loading_sound3.ogg"); break; case LoadingState::COMPLETE: current_border_type_ = BorderType::WHITE; // 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 LoadingState::SILENT1: if (state_time_ >= SILENT1_DURATION) { transitionToState(LoadingState::HEADER1); } break; case LoadingState::HEADER1: if (state_time_ >= HEADER1_DURATION) { transitionToState(LoadingState::BYTES1); } break; case LoadingState::BYTES1: if (state_time_ >= BYTES1_DURATION) { transitionToState(LoadingState::SILENT2); } break; case LoadingState::SILENT2: if (state_time_ >= SILENT2_DURATION) { transitionToState(LoadingState::HEADER2); } break; case LoadingState::HEADER2: if (state_time_ >= HEADER2_DURATION) { transitionToState(LoadingState::LOADING_MONO); } break; case LoadingState::BYTES2: if (state_time_ >= BYTES2_DURATION) { transitionToState(LoadingState::COMPLETE); } break; case LoadingState::LOADING_MONO: case LoadingState::LOADING_COLOR: case LoadingState::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(LoadingState::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(1, stringToColor("black")); 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(LoadingState::BYTES2); 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::renderYellowBorder() { // 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::renderRedBorder() { // Obtiene la Surface del borde auto border = Screen::get()->getBorderSurface(); // Pinta el borde de color azul border->clear(static_cast(PaletteColor::CYAN)); // Añade lineas rojas const auto COLOR = static_cast(PaletteColor::RED); 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 (para que tenga poca variacion) int row = 0; const int FIRST_ROW_HEIGHT = (rand() % 4) + 3; 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 while (row < HEIGHT) { const int ROW_HEIGHT = (rand() % 3) + 8; 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 borde de color blanco void LoadingScreen::renderWhiteBorder() { // Obtiene la Surface del borde auto border = Screen::get()->getBorderSurface(); // Pinta el borde de color azul border->clear(static_cast(PaletteColor::WHITE)); } // Actualiza las variables void LoadingScreen::update() { // Obtener delta time desde el último frame const float DELTA_TIME = delta_timer_->tick(); checkInput(); // Comprueba las entradas updateState(DELTA_TIME); // Actualiza el estado y gestiona transiciones // Actualizar la carga según el estado actual switch (state_) { case LoadingState::SILENT1: case LoadingState::HEADER1: case LoadingState::BYTES1: case LoadingState::SILENT2: case LoadingState::HEADER2: case LoadingState::BYTES2: // Por ahora no hacen nada específico // Tú definirás la lógica de cada estado aquí break; case LoadingState::LOADING_MONO: updateMonoLoad(DELTA_TIME); break; case LoadingState::LOADING_COLOR: updateColorLoad(DELTA_TIME); break; case LoadingState::COMPLETE: // No hay más actualizaciones break; } // Singletones Audio::update(); // Actualiza el objeto Audio Screen::get()->update(); // 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(); checkEvents(); 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 BorderType::YELLOW: renderYellowBorder(); break; case BorderType::RED: renderRedBorder(); break; case BorderType::WHITE: renderWhiteBorder(); break; case BorderType::NONE: // No renderizar borde break; } } }