#include "game/scenes/game.hpp" #include #include #include // Para vector #include "core/input/global_inputs.hpp" // Para check #include "core/input/input.hpp" // Para Input, InputAction, INPUT_DO_NOT_ALLOW_REPEAT #include "core/rendering/screen.hpp" // Para Screen #include "core/rendering/surface.hpp" // Para Surface #include "core/rendering/text.hpp" // Para Text, TEXT_CENTER, TEXT_COLOR #include "core/resources/asset.hpp" // Para Asset #include "core/resources/resource.hpp" // Para ResourceRoom, Resource #include "core/system/debug.hpp" // Para Debug #include "core/system/global_events.hpp" // Para check #include "external/jail_audio.h" // Para JA_PauseMusic, JA_GetMusicState, JA_P... #include "game/gameplay/cheevos.hpp" // Para Cheevos #include "game/gameplay/item_tracker.hpp" // Para ItemTracker #include "game/gameplay/room.hpp" // Para Room, RoomData #include "game/gameplay/room_tracker.hpp" // Para RoomTracker #include "game/gameplay/scoreboard.hpp" // Para ScoreboardData, Scoreboard #include "game/gameplay/stats.hpp" // Para Stats #include "game/options.hpp" // Para Options, options, Cheat, SectionState #include "game/scene_manager.hpp" // Para SceneManager #include "game/ui/notifier.hpp" // Para Notifier, NotificationText, CHEEVO_NO... #include "utils/defines.hpp" // Para BLOCK, PLAY_AREA_HEIGHT, RoomBorder::BOTTOM #include "utils/utils.hpp" // Para PaletteColor, stringToColor // Constructor Game::Game(GameMode mode) : board_(std::make_shared(0, 9, 0, true, 0, SDL_GetTicks(), Options::cheats.jail_is_open == Options::Cheat::State::ENABLED)), scoreboard_(std::make_shared(board_)), room_tracker_(std::make_shared()), stats_(std::make_shared(Asset::get()->get("stats.csv"), Asset::get()->get("stats_buffer.csv"))), mode_(mode), #ifdef _DEBUG current_room_("03.room"), spawn_data_(Player::SpawnData(25 * BLOCK, 13 * BLOCK, 0, 0, 0, Player::State::STANDING, SDL_FLIP_HORIZONTAL)) #else current_room_("03.room"), spawn_point_(PlayerSpawn(25 * BLOCK, 13 * BLOCK, 0, 0, 0, PlayerState::STANDING, SDL_FLIP_HORIZONTAL)) #endif { #ifdef _DEBUG Debug::get()->setEnabled(false); #endif // Crea objetos e inicializa variables ItemTracker::init(); demoInit(); room_ = std::make_shared(current_room_, board_); initPlayer(spawn_data_, room_); initStats(); total_items_ = getTotalItems(); createRoomNameTexture(); changeRoom(current_room_); Cheevos::get()->enable(!Options::cheats.enabled()); // Deshabilita los logros si hay trucos activados Cheevos::get()->clearUnobtainableState(); SceneManager::current = (mode_ == GameMode::GAME) ? SceneManager::Scene::GAME : SceneManager::Scene::DEMO; SceneManager::options = SceneManager::Options::NONE; } Game::~Game() { ItemTracker::destroy(); } // Comprueba los eventos de la cola void Game::checkEvents() { SDL_Event event; while (SDL_PollEvent(&event)) { GlobalEvents::check(event); #ifdef _DEBUG checkDebugEvents(event); #endif } } // Comprueba el teclado void Game::checkInput() { if (Input::get()->checkInput(InputAction::TOGGLE_MUSIC, INPUT_DO_NOT_ALLOW_REPEAT)) { board_->music = !board_->music; board_->music ? JA_ResumeMusic() : JA_PauseMusic(); Notifier::get()->show({"MUSIC " + std::string(board_->music ? "ENABLED" : "DISABLED")}, NotificationText::CENTER); } else if (Input::get()->checkInput(InputAction::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT)) { togglePause(); Notifier::get()->show({std::string(paused_ ? "GAME PAUSED" : "GAME RUNNING")}, NotificationText::CENTER); } GlobalInputs::check(); } // Bucle para el juego void Game::run() { keepMusicPlaying(); if (!board_->music && mode_ == GameMode::GAME) { JA_PauseMusic(); } while (SceneManager::current == SceneManager::Scene::GAME || SceneManager::current == SceneManager::Scene::DEMO) { update(); checkEvents(); render(); } if (mode_ == GameMode::GAME) { JA_StopMusic(); } } // Actualiza el juego, las variables, comprueba la entrada, etc. void Game::update() { // Calcula el delta time const float DELTA_TIME = delta_timer_.tick(); // Comprueba el teclado checkInput(); #ifdef _DEBUG Debug::get()->clear(); #endif // Actualiza los objetos room_->update(DELTA_TIME); if (mode_ == GameMode::GAME) { player_->update(DELTA_TIME); checkPlayerIsOnBorder(); checkPlayerAndItems(); checkPlayerAndEnemies(); checkIfPlayerIsAlive(); checkGameOver(); checkEndGame(); checkRestoringJail(DELTA_TIME); checkSomeCheevos(); } demoCheckRoomChange(DELTA_TIME); scoreboard_->update(); keepMusicPlaying(); updateBlackScreen(DELTA_TIME); Screen::get()->update(DELTA_TIME); #ifdef _DEBUG updateDebugInfo(); #endif } // Pinta los objetos en pantalla void Game::render() { // Prepara para dibujar el frame Screen::get()->start(); // Dibuja los elementos del juego en orden room_->renderMap(); room_->renderEnemies(); room_->renderItems(); if (mode_ == GameMode::GAME) { player_->render(); } renderRoomName(); scoreboard_->render(); renderBlackScreen(); #ifdef _DEBUG // Debug info renderDebugInfo(); #endif // Actualiza la pantalla Screen::get()->render(); } #ifdef _DEBUG // Pasa la información de debug void Game::updateDebugInfo() { // Debug::get()->add("X = " + std::to_string(static_cast(player_->x_)) + ", Y = " + std::to_string(static_cast(player_->y_))); // Debug::get()->add("VX = " + std::to_string(player_->vx_).substr(0, 4) + ", VY = " + std::to_string(player_->vy_).substr(0, 4)); // Debug::get()->add("STATE = " + std::to_string(static_cast(player_->state_))); } // Pone la información de debug en pantalla void Game::renderDebugInfo() { if (!Debug::get()->getEnabled()) { return; } auto surface = Screen::get()->getRendererSurface(); // Borra el marcador SDL_FRect rect = {0, 18 * BLOCK, PLAY_AREA_WIDTH, GAMECANVAS_HEIGHT - PLAY_AREA_HEIGHT}; surface->fillRect(&rect, static_cast(PaletteColor::BLACK)); // Pinta la rejilla /*for (int i = 0; i < PLAY_AREA_BOTTOM; i += 8) { // Lineas horizontales surface->drawLine(0, i, PLAY_AREA_RIGHT, i, static_cast(PaletteColor::BRIGHT_BLACK)); } for (int i = 0; i < PLAY_AREA_RIGHT; i += 8) { // Lineas verticales surface->drawLine(i, 0, i, PLAY_AREA_BOTTOM - 1, static_cast(PaletteColor::BRIGHT_BLACK)); }*/ // Pinta el texto Debug::get()->setPos({1, 18 * 8}); Debug::get()->render(); } // Comprueba los eventos void Game::checkDebugEvents(const SDL_Event& event) { if (event.type == SDL_EVENT_KEY_DOWN && static_cast(event.key.repeat) == 0) { switch (event.key.key) { case SDL_SCANCODE_G: Debug::get()->toggleEnabled(); Options::cheats.invincible = static_cast(Debug::get()->getEnabled()); board_->music = !Debug::get()->getEnabled(); board_->music ? JA_ResumeMusic() : JA_PauseMusic(); break; case SDL_SCANCODE_R: Resource::get()->reload(); break; case SDL_SCANCODE_W: changeRoom(room_->getRoom(RoomBorder::TOP)); break; case SDL_SCANCODE_A: changeRoom(room_->getRoom(RoomBorder::LEFT)); break; case SDL_SCANCODE_S: changeRoom(room_->getRoom(RoomBorder::BOTTOM)); break; case SDL_SCANCODE_D: changeRoom(room_->getRoom(RoomBorder::RIGHT)); break; case SDL_SCANCODE_7: Notifier::get()->show({"ACHIEVEMENT UNLOCKED!", "I LIKE MY MULTICOLOURED FRIENDS"}, NotificationText::CENTER, CHEEVO_NOTIFICATION_DURATION, -1, false, "F7"); break; default: break; } } } #endif // Escribe el nombre de la pantalla void Game::renderRoomName() { // Dibuja la textura con el nombre de la habitación room_name_surface_->render(nullptr, &room_name_rect_); } // Cambia de habitación auto Game::changeRoom(const std::string& room_path) -> bool { // En las habitaciones los limites tienen la cadena del fichero o un 0 en caso de no limitar con nada if (room_path == "0") { return false; } // Verifica que exista el fichero que se va a cargar if (!Asset::get()->get(room_path).empty()) { // Crea un objeto habitación nuevo a partir del fichero room_ = std::make_shared(room_path, board_); // Pone el nombre de la habitación en la textura fillRoomNameTexture(); // Pone el color del marcador en función del color del borde de la habitación setScoreBoardColor(); if (room_tracker_->addRoom(room_path)) { // Incrementa el contador de habitaciones visitadas board_->rooms++; Options::stats.rooms = board_->rooms; // Actualiza las estadisticas stats_->addVisit(room_->getName()); } // Pasa la nueva habitación al jugador player_->setRoom(room_); // Cambia la habitación actual current_room_ = room_path; return true; } return false; } // Comprueba si el jugador esta en el borde de la pantalla void Game::checkPlayerIsOnBorder() { if (player_->getOnBorder()) { const std::string ROOM_NAME = room_->getRoom(player_->getBorder()); if (changeRoom(ROOM_NAME)) { player_->switchBorders(); spawn_data_ = player_->getSpawnParams(); } } } // Comprueba las colisiones del jugador con los enemigos auto Game::checkPlayerAndEnemies() -> bool { const bool DEATH = room_->enemyCollision(player_->getCollider()); if (DEATH) { killPlayer(); } return DEATH; } // Comprueba las colisiones del jugador con los objetos void Game::checkPlayerAndItems() { room_->itemCollision(player_->getCollider()); } // Comprueba si el jugador esta vivo void Game::checkIfPlayerIsAlive() { if (!player_->isAlive()) { killPlayer(); } } // Comprueba si ha terminado la partida void Game::checkGameOver() { if (board_->lives < 0 && black_screen_time_ > GAME_OVER_THRESHOLD) { SceneManager::current = SceneManager::Scene::GAME_OVER; } } // Mata al jugador void Game::killPlayer() { if (Options::cheats.invincible == Options::Cheat::State::ENABLED) { return; } // Resta una vida al jugador if (Options::cheats.infinite_lives == Options::Cheat::State::DISABLED) { --board_->lives; } // Actualiza las estadisticas stats_->addDeath(room_->getName()); // Invalida el logro de pasarse el juego sin morir Cheevos::get()->setUnobtainable(11); // Sonido JA_PlaySound(Resource::get()->getSound("death.wav")); // Pone la pantalla en negro un tiempo setBlackScreen(); // Crea la nueva habitación y el nuevo jugador room_ = std::make_shared(current_room_, board_); initPlayer(spawn_data_, room_); // Pone los objetos en pausa mientras esta la habitación en negro room_->setPaused(true); player_->setPaused(true); } // Establece la pantalla en negro void Game::setBlackScreen() { black_screen_ = true; } // Actualiza las variables relativas a la pantalla en negro void Game::updateBlackScreen(float delta_time) { if (black_screen_) { black_screen_time_ += delta_time; if (black_screen_time_ > BLACK_SCREEN_DURATION) { black_screen_ = false; black_screen_time_ = 0.0F; player_->setPaused(false); room_->setPaused(false); Screen::get()->setBorderColor(room_->getBorderColor()); } } } // Dibuja la pantalla negra void Game::renderBlackScreen() const { if (black_screen_) { auto const COLOR = static_cast(PaletteColor::BLACK); Screen::get()->setRendererSurface(); Screen::get()->clearSurface(COLOR); Screen::get()->setBorderColor(COLOR); } } // Pone el color del marcador en función del color del borde de la habitación void Game::setScoreBoardColor() { // Obtiene el color del borde const Uint8 BORDER_COLOR = room_->getBorderColor(); const bool IS_BLACK = BORDER_COLOR == static_cast(PaletteColor::BLACK); const bool IS_BRIGHT_BLACK = BORDER_COLOR == static_cast(PaletteColor::BRIGHT_BLACK); // Si el color del borde es negro o negro brillante cambia el texto del marcador a blanco board_->color = IS_BLACK || IS_BRIGHT_BLACK ? static_cast(PaletteColor::WHITE) : BORDER_COLOR; } // Comprueba si ha finalizado el juego auto Game::checkEndGame() -> bool { const bool IS_ON_THE_ROOM = room_->getName() == "THE JAIL"; // Estar en la habitación que toca const bool HAVE_THE_ITEMS = board_->items >= int(total_items_ * 0.9F) || Options::cheats.jail_is_open == Options::Cheat::State::ENABLED; // Con mas del 90% de los items recogidos const bool IS_ON_THE_DOOR = player_->getRect().x <= 128; // Y en la ubicación que toca (En la puerta) if (HAVE_THE_ITEMS) { board_->jail_is_open = true; } if (HAVE_THE_ITEMS && IS_ON_THE_ROOM && IS_ON_THE_DOOR) { // Comprueba los logros de completar el juego checkEndGameCheevos(); SceneManager::current = SceneManager::Scene::ENDING; return true; } return false; } // Obtiene la cantidad total de items que hay en el mapeado del juego auto Game::getTotalItems() -> int { int items = 0; auto rooms = Resource::get()->getRooms(); for (const auto& room : rooms) { items += room.room->items.size(); } return items; } // Pone el juego en pausa void Game::togglePause() { paused_ = !paused_; player_->setPaused(paused_); room_->setPaused(paused_); scoreboard_->setPaused(paused_); } // Da vidas al jugador cuando está en la Jail void Game::checkRestoringJail(float delta_time) { if (room_->getName() != "THE JAIL" || board_->lives == 9) { jail_restore_time_ = 0.0F; // Reset timer cuando no está en la Jail return; } if (!paused_) { jail_restore_time_ += delta_time; } // Incrementa el numero de vidas if (jail_restore_time_ >= JAIL_RESTORE_INTERVAL) { jail_restore_time_ -= JAIL_RESTORE_INTERVAL; // Mantiene el excedente para precisión board_->lives++; JA_PlaySound(Resource::get()->getSound("death.wav")); // Invalida el logro de completar el juego sin entrar a la jail const bool HAVE_THE_ITEMS = board_->items >= int(total_items_ * 0.9F); if (!HAVE_THE_ITEMS) { Cheevos::get()->setUnobtainable(9); } } } // Inicializa el diccionario de las estadísticas void Game::initStats() { auto rooms = Resource::get()->getRooms(); for (const auto& room : rooms) { stats_->addDictionary(room.room->number, room.room->name); } stats_->init(); } // Crea la textura con el nombre de la habitación void Game::fillRoomNameTexture() { // Pone la textura como destino de renderizado auto previuos_renderer = Screen::get()->getRendererSurface(); Screen::get()->setRendererSurface(room_name_surface_); // Rellena la textura de color room_name_surface_->clear(stringToColor("white")); // Escribe el texto en la textura auto text = Resource::get()->getText("smb2"); text->writeDX(TEXT_CENTER | TEXT_COLOR, GAMECANVAS_CENTER_X, text->getCharacterSize() / 2, room_->getName(), 1, room_->getBGColor()); // Deja el renderizador por defecto Screen::get()->setRendererSurface(previuos_renderer); } // Comprueba algunos logros void Game::checkSomeCheevos() { auto* cheevos = Cheevos::get(); // Logros sobre la cantidad de items if (board_->items == total_items_) { cheevos->unlock(4); cheevos->unlock(3); cheevos->unlock(2); cheevos->unlock(1); } else if (board_->items >= total_items_ * 0.75F) { cheevos->unlock(3); cheevos->unlock(2); cheevos->unlock(1); } else if (board_->items >= total_items_ * 0.5F) { cheevos->unlock(2); cheevos->unlock(1); } else if (board_->items >= total_items_ * 0.25F) { cheevos->unlock(1); } // Logros sobre las habitaciones visitadas if (board_->rooms >= 60) { cheevos->unlock(7); cheevos->unlock(6); cheevos->unlock(5); } else if (board_->rooms >= 40) { cheevos->unlock(6); cheevos->unlock(5); } else if (board_->rooms >= 20) { cheevos->unlock(5); } } // Comprueba los logros de completar el juego void Game::checkEndGameCheevos() { auto* cheevos = Cheevos::get(); // "Complete the game" cheevos->unlock(8); // "Complete the game without entering the jail" cheevos->unlock(9); // "Complete the game with all items" if (board_->items == total_items_) { cheevos->unlock(10); } // "Complete the game without dying" cheevos->unlock(11); // "Complete the game in under 30 minutes" if (scoreboard_->getMinutes() < 30) { cheevos->unlock(12); } } // Inicializa al jugador void Game::initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr room) { std::string player_texture = Options::cheats.alternate_skin == Options::Cheat::State::ENABLED ? "player2.gif" : "player.gif"; std::string player_animations = Options::cheats.alternate_skin == Options::Cheat::State::ENABLED ? "player2.ani" : "player.ani"; const Player::Data PLAYER(spawn_point, player_texture, player_animations, std::move(room)); player_ = std::make_shared(PLAYER); } // Crea la textura para poner el nombre de la habitación void Game::createRoomNameTexture() { auto text = Resource::get()->getText("smb2"); room_name_surface_ = std::make_shared(Options::game.width, text->getCharacterSize() * 2); // Establece el destino de la textura room_name_rect_ = {.x = 0.0F, .y = PLAY_AREA_HEIGHT, .w = Options::game.width, .h = text->getCharacterSize() * 2.0F}; } // Hace sonar la música void Game::keepMusicPlaying() { const std::string MUSIC_PATH = mode_ == GameMode::GAME ? "game.ogg" : "title.ogg"; // Si la música no está sonando if (JA_GetMusicState() == JA_MUSIC_INVALID || JA_GetMusicState() == JA_MUSIC_STOPPED) { JA_PlayMusic(Resource::get()->getMusic(MUSIC_PATH)); } } // DEMO MODE: Inicializa las variables para el modo demo void Game::demoInit() { if (mode_ == GameMode::DEMO) { demo_ = DemoData(0.0F, 0, {"04.room", "54.room", "20.room", "09.room", "05.room", "11.room", "31.room", "44.room"}); current_room_ = demo_.rooms.front(); } } // DEMO MODE: Comprueba si se ha de cambiar de habitación void Game::demoCheckRoomChange(float delta_time) { if (mode_ == GameMode::DEMO) { demo_.time_accumulator += delta_time; if (demo_.time_accumulator >= DEMO_ROOM_DURATION) { demo_.time_accumulator = 0.0F; demo_.room_index++; if (demo_.room_index == (int)demo_.rooms.size()) { SceneManager::current = SceneManager::Scene::LOGO; SceneManager::options = SceneManager::Options::LOGO_TO_TITLE; } else { changeRoom(demo_.rooms[demo_.room_index]); } } } }