#include "core/system/director.hpp" #include #include #include "core/input/gamepad.hpp" #include "core/input/global_inputs.hpp" #include "core/input/key_remap.hpp" #include "core/input/mouse.hpp" #include "core/jail/jail_audio.hpp" #include "core/jail/jgame.hpp" #include "core/jail/jinput.hpp" #include "core/rendering/menu.hpp" #include "core/rendering/overlay.hpp" #include "core/rendering/screen.hpp" #include "game/info.hpp" #include "game/modulegame.hpp" #include "game/modulesequence.hpp" #include "game/options.hpp" // Cheats del joc original — declarats a jinput.cpp extern void JI_moveCheats(Uint8 new_key); Director* Director::instance_ = nullptr; void Director::init() { instance_ = new Director(); Gamepad::init(); } void Director::destroy() { Gamepad::destroy(); delete instance_; instance_ = nullptr; } auto Director::get() -> Director* { return instance_; } void Director::togglePause() { paused_ = !paused_; if (paused_) { JA_PauseMusic(); } else { JA_ResumeMusic(); } } void Director::run() { // Llança el game thread game_thread_ = std::thread(&Director::gameThreadFunc, this); // Doble buffer: game_frame és el frame net del joc, presentation_buffer // és el frame + overlay (es regenera cada iteració des de game_frame) Uint32 game_frame[320 * 200]{}; Uint32 presentation_buffer[320 * 200]{}; bool has_frame = false; constexpr Uint32 TARGET_FRAME_MS = 16; // ~60 FPS // Bucle principal del director (no-bloquejant) while (!game_thread_done_ && !quit_requested_) { Uint32 frame_start = SDL_GetTicks(); handleEvents(); Gamepad::update(); KeyRemap::update(); GlobalInputs::handle(); Mouse::updateCursorVisibility(); // Si l'overlay ja no bloqueja ESC (timeout), desbloquegem if (esc_blocked_ && !Overlay::isEscConsumed()) { esc_blocked_ = false; } // Consumeix un frame nou si n'hi ha un disponible (no bloqueja). // Si estem en pausa, no consumim: el game thread es queda bloquejat a publishFrame. bool new_frame = false; if (!paused_) { std::lock_guard lock(mutex_); if (frame_ready_ && latest_frame_ != nullptr) { memcpy(game_frame, latest_frame_, sizeof(game_frame)); frame_ready_ = false; frame_consumed_ = true; has_frame = true; new_frame = true; } } if (new_frame) { frame_consumed_cv_.notify_one(); // desbloqueja el joc } // Presenta sempre: parteix del frame net del joc, afegeix overlay i envia if (has_frame) { memcpy(presentation_buffer, game_frame, sizeof(presentation_buffer)); Screen::get()->present(presentation_buffer); } // Limita a ~60 FPS Uint32 elapsed = SDL_GetTicks() - frame_start; if (elapsed < TARGET_FRAME_MS) { SDL_Delay(TARGET_FRAME_MS - elapsed); } } // Assegura que el game thread ix (despertar-lo per si està esperant) quit_requested_ = true; JG_QuitSignal(); { std::lock_guard lock(mutex_); frame_consumed_ = true; } frame_consumed_cv_.notify_all(); if (game_thread_.joinable()) { game_thread_.join(); } } void Director::handleEvents() { SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_EVENT_QUIT) { JG_QuitSignal(); requestQuit(); } // Hot-plug de gamepad if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED) { Gamepad::handleEvent(event); continue; } // Empassar-se el KEY_UP de qualsevol tecla que el menú va consumir en KEY_DOWN if (event.type == SDL_EVENT_KEY_UP && event.key.scancode >= 0 && event.key.scancode < SDL_SCANCODE_COUNT && menu_keys_held_[event.key.scancode]) { menu_keys_held_[event.key.scancode] = false; continue; } // Captura de tecla (remapeig al menú): intercepta KEY_DOWN abans de tot if (Menu::isCapturing() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) { Menu::captureKey(event.key.scancode); menu_keys_held_[event.key.scancode] = true; continue; } // Pausa: F11 (o tecla configurada) pausa/reprén la simulació if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && event.key.scancode == Options::keys_gui.pause_toggle) { togglePause(); Overlay::showNotification(paused_ ? "PAUSA" : "REPRES"); menu_keys_held_[event.key.scancode] = true; continue; } // Menú: F12 (o tecla configurada) obre/tanca el menú flotant if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && event.key.scancode == Options::keys_gui.menu_toggle) { Menu::toggle(); JI_SetInputBlocked(Menu::isOpen()); menu_keys_held_[event.key.scancode] = true; continue; } // Si el menú està obert, consumeix tot l'input de teclat if (Menu::isOpen() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) { if (event.key.scancode == SDL_SCANCODE_ESCAPE) { Menu::close(); JI_SetInputBlocked(false); // Empassa l'ESC fins al release perquè el joc no la veja per polling esc_swallow_until_release_ = true; } else { Menu::handleKey(event.key.scancode); // El menú pot haver-se tancat (p.ex. Backspace al nivell arrel) if (!Menu::isOpen()) { JI_SetInputBlocked(false); } } menu_keys_held_[event.key.scancode] = true; continue; } if (Menu::isOpen() && event.type == SDL_EVENT_KEY_UP) { continue; // no deixem passar KEY_UP al joc tampoc } // Allibera el bloqueig d'ESC quan l'usuari la deixa anar if (event.type == SDL_EVENT_KEY_UP && event.key.scancode == SDL_SCANCODE_ESCAPE && esc_swallow_until_release_) { esc_swallow_until_release_ = false; continue; } // ESC: interceptem KEY_DOWN per bloquejar-la ABANS que el joc la veja per polling if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_ESCAPE && !event.key.repeat) { esc_blocked_ = true; // Bloqueja ESC per polling immediatament if (!Overlay::isEscConsumed()) { // Primera pulsació: mostra notificació Overlay::handleEscape(); } else { // Segona pulsació: senyal d'eixida al joc esc_blocked_ = false; key_pressed_ = true; JG_QuitSignal(); } continue; // no processa més aquest event } if (event.type == SDL_EVENT_KEY_UP) { if (event.key.scancode == SDL_SCANCODE_ESCAPE) { // Ja processat a KEY_DOWN, només deixem netejar el bloqueig // quan l'overlay faça timeout continue; } else { // Comprova si és una tecla GUI (no passa al joc) const auto sc = event.key.scancode; const bool is_gui_key = (sc == Options::keys_gui.dec_zoom || sc == Options::keys_gui.inc_zoom || sc == Options::keys_gui.fullscreen || sc == Options::keys_gui.toggle_shader || sc == Options::keys_gui.toggle_aspect_ratio || sc == Options::keys_gui.toggle_supersampling || sc == Options::keys_gui.next_shader || sc == Options::keys_gui.next_shader_preset || sc == Options::keys_gui.toggle_stretch_filter || sc == Options::keys_gui.toggle_render_info); if (!is_gui_key) { key_pressed_ = true; JI_moveCheats(sc); } } } Mouse::handleEvent(event); } } void Director::publishFrame(Uint32* pixels) { { std::lock_guard lock(mutex_); latest_frame_ = pixels; frame_ready_ = true; frame_consumed_ = false; } frame_produced_cv_.notify_one(); // Espera que el director consumeixca el frame { std::unique_lock lock(mutex_); frame_consumed_cv_.wait(lock, [this] { return frame_consumed_ || quit_requested_; }); } } void Director::requestQuit() { quit_requested_ = true; JG_QuitSignal(); frame_consumed_cv_.notify_all(); frame_produced_cv_.notify_all(); } auto Director::consumeKeyPressed() -> bool { return key_pressed_.exchange(false); } void Director::gameThreadFunc() { info::num_habitacio = Options::game.habitacio_inicial; info::num_piramide = Options::game.piramide_inicial; info::diners = 0; info::diamants = 0; info::vida = Options::game.vides; info::momies = 0; info::nou_personatge = false; info::pepe_activat = false; FILE* ini = fopen("trick.ini", "rb"); if (ini != nullptr) { info::nou_personatge = true; fclose(ini); } int gameState = 1; while (gameState != -1 && !quit_requested_) { switch (gameState) { case 0: { auto* moduleGame = new ModuleGame(); gameState = moduleGame->Go(); delete moduleGame; break; } case 1: { auto* moduleSequence = new ModuleSequence(); gameState = moduleSequence->Go(); delete moduleSequence; break; } } } game_thread_done_ = true; // Despertar el director per si esperava un frame frame_produced_cv_.notify_all(); }