#include "hiscore_table.hpp" #include // Para SDL_GetTicks, SDL_SetRenderTarget #include // Para max #include // Para rand, size_t #include // Para function #include // Para std::cmp_less #include // Para vector #include "audio.hpp" // Para Audio #include "background.hpp" // Para Background #include "color.hpp" // Para Color, easeOutQuint, Colors::NO_COLOR_MOD #include "fade.hpp" // Para Fade, FadeMode, FadeType #include "global_events.hpp" // Para check #include "global_inputs.hpp" // Para check #include "input.hpp" // Para Input #include "lang.hpp" // Para getText #include "manage_hiscore_table.hpp" // Para HiScoreEntry #include "options.hpp" // Para SettingsOptions, settings #include "param.hpp" // Para Param, param, ParamGame, ParamFade #include "path_sprite.hpp" // Para PathSprite, Path, PathType #include "resource.hpp" // Para Resource #include "screen.hpp" // Para Screen #include "section.hpp" // Para Name, name, Options, options #include "sprite.hpp" // Para Sprite #include "text.hpp" // Para Text, Text::SHADOW, Text::COLOR #include "texture.hpp" // Para Texture #include "utils.hpp" // Constructor HiScoreTable::HiScoreTable() : renderer_(Screen::get()->getRenderer()), backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)), fade_(std::make_unique()), background_(std::make_unique()), view_area_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}), fade_mode_(Fade::Mode::IN), background_fade_color_(Color(0, 0, 0)) { // Inicializa el resto Section::name = Section::Name::HI_SCORE_TABLE; SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND); initFade(); initBackground(); iniEntryColors(); createSprites(); } // Destructor HiScoreTable::~HiScoreTable() { SDL_DestroyTexture(backbuffer_); Options::settings.clearLastHiScoreEntries(); } // Actualiza las variables void HiScoreTable::update(float delta_time) { elapsed_time_ += delta_time; // Incrementa el tiempo transcurrido static auto* const SCREEN = Screen::get(); SCREEN->update(delta_time); // Actualiza el objeto screen Audio::update(); // Actualiza el objeto audio updateSprites(delta_time); // Actualiza las posiciones de los sprites de texto background_->update(delta_time); // Actualiza el fondo updateFade(delta_time); // Gestiona el fade updateCounter(); // Gestiona el contador y sus eventos fillTexture(); // Dibuja los sprites en la textura } // Pinta en pantalla void HiScoreTable::render() { static auto* const SCREEN = Screen::get(); SCREEN->start(); // Prepara para empezar a dibujar en la textura de juego SCREEN->clean(); // Limpia la pantalla background_->render(); // Pinta el fondo float scroll_offset = elapsed_time_ * SCROLL_SPEED_PPS; // Calcula el desplazamiento del scroll usando velocidad en pixels/segundo view_area_.y = std::round(std::max(0.0F, (param.game.height + 100.0F) - scroll_offset)); // Establece la ventana del backbuffer (redondeado para evitar deformaciones) SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_area_); // Copia el backbuffer al renderizador fade_->render(); // Renderiza el fade SCREEN->render(); // Vuelca el contenido del renderizador en pantalla } // Dibuja los sprites en la textura void HiScoreTable::fillTexture() { // Pinta en el backbuffer el texto y los sprites auto* temp = SDL_GetRenderTarget(renderer_); SDL_SetRenderTarget(renderer_, backbuffer_); SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0); SDL_RenderClear(renderer_); // Escribe el texto: Mejores puntuaciones header_->render(); // Escribe los nombres de la tabla de puntuaciones for (auto const& entry : entry_names_) { entry->render(); } // Cambia el destino de renderizado SDL_SetRenderTarget(renderer_, temp); } // Comprueba los eventos void HiScoreTable::checkEvents() { SDL_Event event; while (SDL_PollEvent(&event)) { GlobalEvents::handle(event); } } // Comprueba las entradas void HiScoreTable::checkInput() { Input::get()->update(); GlobalInputs::check(); } // Calcula el tiempo transcurrido desde el último frame auto HiScoreTable::calculateDeltaTime() -> float { const Uint64 CURRENT_TIME = SDL_GetTicks(); const float DELTA_TIME = static_cast(CURRENT_TIME - last_time_) / 1000.0F; // Convertir ms a segundos last_time_ = CURRENT_TIME; return DELTA_TIME; } // Bucle para la pantalla de instrucciones void HiScoreTable::run() { last_time_ = SDL_GetTicks(); Audio::get()->playMusic("title.ogg"); while (Section::name == Section::Name::HI_SCORE_TABLE) { const float DELTA_TIME = calculateDeltaTime(); checkInput(); update(DELTA_TIME); checkEvents(); // Tiene que ir antes del render render(); } } // Gestiona el fade void HiScoreTable::updateFade(float delta_time) { fade_->update(delta_time); if (fade_->hasEnded() && fade_mode_ == Fade::Mode::IN) { (*fade_).reset(); fade_mode_ = Fade::Mode::OUT; fade_->setMode(fade_mode_); } if (fade_->hasEnded() && fade_mode_ == Fade::Mode::OUT) { Section::name = (Section::options == Section::Options::HI_SCORE_AFTER_PLAYING) ? Section::Name::TITLE : Section::Name::INSTRUCTIONS; Section::options = Section::Options::NONE; } } // Convierte un entero a un string con separadores de miles auto HiScoreTable::format(int number) -> std::string { const std::string SEPARATOR = "."; const std::string SCORE = std::to_string(number); auto index = static_cast(SCORE.size()) - 1; std::string result; auto i = 0; while (index >= 0) { result = SCORE.at(index) + result; index--; i++; if (i == 3) { i = 0; result = SEPARATOR + result; } } return result; } // Crea los sprites con los textos void HiScoreTable::createSprites() { auto header_text = Resource::get()->getText("04b_25_grey"); auto entry_text = Resource::get()->getText("smb2"); // Obtiene el tamaño de la textura float backbuffer_width; float backbuffer_height; SDL_GetTextureSize(backbuffer_, &backbuffer_width, &backbuffer_height); constexpr int ENTRY_LENGTH = 22; constexpr int MAX_NAMES = 10; const int SPACE_BETWEEN_HEADER = entry_text->getCharacterSize() * 4; const int SPACE_BETWEEN_LINES = entry_text->getCharacterSize() * 2; const int SIZE = SPACE_BETWEEN_HEADER + (SPACE_BETWEEN_LINES * (MAX_NAMES - 1)) + entry_text->getCharacterSize(); const int FIRST_LINE = (param.game.height - SIZE) / 2; // Crea el sprite para el texto de cabecera header_ = std::make_unique(header_text->writeDXToTexture(Text::COLOR, Lang::getText("[HIGHSCORE_TABLE] CAPTION"), -2, background_fade_color_.INVERSE().LIGHTEN(25))); header_->setPosition(param.game.game_area.center_x - (header_->getWidth() / 2), FIRST_LINE); // Crea los sprites para las entradas en la tabla de puntuaciones const int ANIMATION = rand() % 4; const std::string SAMPLE_LINE(ENTRY_LENGTH + 3, ' '); auto sample_entry = std::make_unique(entry_text->writeDXToTexture(Text::SHADOW, SAMPLE_LINE, 1, Colors::NO_COLOR_MOD, 1, Colors::SHADOW_TEXT)); const auto ENTRY_WIDTH = sample_entry->getWidth(); for (int i = 0; i < MAX_NAMES; ++i) { const auto TABLE_POSITION = format(i + 1) + ". "; const auto SCORE = format(Options::settings.hi_score_table.at(i).score); const auto NUM_DOTS = ENTRY_LENGTH - Options::settings.hi_score_table.at(i).name.size() - SCORE.size(); const auto* const ONE_CC = Options::settings.hi_score_table.at(i).one_credit_complete ? " }" : ""; std::string dots; for (int j = 0; std::cmp_less(j, NUM_DOTS); ++j) { dots = dots + "."; } const auto LINE = TABLE_POSITION + Options::settings.hi_score_table.at(i).name + dots + SCORE + ONE_CC; entry_names_.emplace_back(std::make_shared(entry_text->writeDXToTexture(Text::SHADOW, LINE, 1, Colors::NO_COLOR_MOD, 1, Colors::SHADOW_TEXT))); const int DEFAULT_POS_X = (backbuffer_width - ENTRY_WIDTH) / 2; const int POS_X = (i < 9) ? DEFAULT_POS_X : DEFAULT_POS_X - entry_text->getCharacterSize(); const int POS_Y = (i * SPACE_BETWEEN_LINES) + FIRST_LINE + SPACE_BETWEEN_HEADER; switch (ANIMATION) { case 0: // Ambos lados alternativamente { if (i % 2 == 0) { entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint); entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0); } else { entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint); entry_names_.back()->setPosition(backbuffer_width, 0); } break; } case 1: // Entran por la izquierda { entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint); entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0); break; } case 2: // Entran por la derecha { entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint); entry_names_.back()->setPosition(backbuffer_width, 0); break; } case 3: // Entran desde la parte inferior { entry_names_.back()->addPath(backbuffer_height, POS_Y, PathType::VERTICAL, POS_X, ANIM_DURATION_S, easeOutQuint); entry_names_.back()->setPosition(0, backbuffer_height); } default: break; } } } // Actualiza las posiciones de los sprites de texto void HiScoreTable::updateSprites(float delta_time) { if (elapsed_time_ >= INIT_DELAY_S) { const float ELAPSED_SINCE_INIT = elapsed_time_ - INIT_DELAY_S; int index = static_cast(ELAPSED_SINCE_INIT / ENTRY_DELAY_S); if (std::cmp_less(index, entry_names_.size()) && index >= 0) { // Verificar si este índice debe activarse ahora float expected_time = index * ENTRY_DELAY_S; if (ELAPSED_SINCE_INIT >= expected_time && ELAPSED_SINCE_INIT < expected_time + delta_time) { entry_names_.at(index)->enable(); } } } for (auto const& entry : entry_names_) { entry->update(delta_time); } glowEntryNames(); } // Inicializa el fade void HiScoreTable::initFade() { fade_->setColor(param.fade.color); fade_->setType(Fade::Type::RANDOM_SQUARE2); fade_->setPostDuration(param.fade.post_duration_ms); fade_->setMode(fade_mode_); fade_->activate(); } // Inicializa el fondo void HiScoreTable::initBackground() { background_->setManualMode(true); background_->setPos(param.game.game_area.rect); background_->setCloudsSpeed(CLOUDS_SPEED); const int LUCKY = rand() % 3; switch (LUCKY) { case 0: // Fondo verde { background_->setGradientNumber(2); background_->setTransition(0.0F); background_->setSunProgression(1.0F); background_->setMoonProgression(0.0F); background_fade_color_ = Colors::GREEN_SKY; break; } case 1: // Fondo naranja { background_->setGradientNumber(1); background_->setTransition(0.0F); background_->setSunProgression(0.65F); background_->setMoonProgression(0.0F); background_fade_color_ = Colors::PINK_SKY; break; } case 2: // Fondo azul { background_->setGradientNumber(0); background_->setTransition(0.0F); background_->setSunProgression(0.0F); background_->setMoonProgression(0.0F); background_fade_color_ = Colors::BLUE_SKY; break; } default: break; } } // Obtiene un color del vector de colores de entradas auto HiScoreTable::getEntryColor(int counter) -> Color { int cycle_length = (entry_colors_.size() * 2) - 2; size_t n = counter % cycle_length; size_t index; if (n < entry_colors_.size()) { index = n; // Avanza: 0,1,2,3 } else { index = (2 * (entry_colors_.size() - 1)) - n; // Retrocede: 2,1 } return entry_colors_[index]; } // Inicializa los colores de las entradas void HiScoreTable::iniEntryColors() { entry_colors_.clear(); entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(75)); entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(50)); entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(25)); entry_colors_.emplace_back(background_fade_color_.INVERSE()); } // Hace brillar los nombres de la tabla de records void HiScoreTable::glowEntryNames() { int color_counter = static_cast(elapsed_time_ * 60.0F / 5.0F); // Convertir tiempo a equivalente frame const Color ENTRY_COLOR = getEntryColor(color_counter); for (const auto& entry_index : Options::settings.glowing_entries) { if (entry_index != -1) { entry_names_.at(entry_index)->getTexture()->setColor(ENTRY_COLOR); } } } // Gestiona el contador void HiScoreTable::updateCounter() { if (elapsed_time_ >= BACKGROUND_CHANGE_S && !hiscore_flags_.background_changed) { background_->setColor(background_fade_color_.DARKEN()); background_->setAlpha(96); hiscore_flags_.background_changed = true; } if (elapsed_time_ >= COUNTER_END_S && !hiscore_flags_.fade_activated) { fade_->activate(); hiscore_flags_.fade_activated = true; } }