refactor: extreure helpers per reduir complexitat cognitiva (tidy net)
This commit is contained in:
+107
-137
@@ -136,120 +136,100 @@ void Director::setup() {
|
||||
has_frame_ = false;
|
||||
}
|
||||
|
||||
void Director::applyRestart() {
|
||||
restart_requested_ = false;
|
||||
Audio::get()->stopMusic();
|
||||
Audio::get()->stopAllSounds();
|
||||
initGameContext();
|
||||
Info::ctx.num_piramide = 255;
|
||||
current_scene_.reset();
|
||||
game_state_ = 1;
|
||||
has_frame_ = false;
|
||||
Menu::close();
|
||||
Ji::setInputBlocked(false);
|
||||
}
|
||||
|
||||
void Director::maybeStartTitleCredits() {
|
||||
static bool credits_triggered_ = false;
|
||||
if (credits_triggered_ || Info::ctx.num_piramide != 0) {
|
||||
return;
|
||||
}
|
||||
if (Options::game.show_title_credits) {
|
||||
Overlay::startCredits();
|
||||
}
|
||||
credits_triggered_ = true;
|
||||
}
|
||||
|
||||
auto Director::tickActiveScene() -> bool {
|
||||
if (current_scene_ && (current_scene_->done() || Jg::quitting())) {
|
||||
game_state_ = current_scene_->nextState();
|
||||
current_scene_.reset();
|
||||
}
|
||||
if (!current_scene_) {
|
||||
if (game_state_ == -1 || Jg::quitting()) {
|
||||
return false;
|
||||
}
|
||||
current_scene_ = createNextScene();
|
||||
if (!current_scene_) {
|
||||
return false;
|
||||
}
|
||||
current_scene_->onEnter();
|
||||
last_tick_ms_ = SDL_GetTicks();
|
||||
}
|
||||
|
||||
Ji::update();
|
||||
const Uint32 NOW = SDL_GetTicks();
|
||||
const int DELTA_MS = static_cast<int>(NOW - last_tick_ms_);
|
||||
last_tick_ms_ = NOW;
|
||||
current_scene_->tick(DELTA_MS);
|
||||
|
||||
Jd8::flip();
|
||||
std::memcpy(game_frame_, Jd8::getFramebuffer(), sizeof(game_frame_));
|
||||
has_frame_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Director::iterate() -> bool {
|
||||
if (quit_requested_) {
|
||||
Jg::quitSignal();
|
||||
current_scene_.reset(); // destrueix l'escena actual ordenadament
|
||||
current_scene_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reinici "suau": processat al començament del frame per no manipular
|
||||
// l'escena des d'una lambda del menú mentre encara s'està executant.
|
||||
if (restart_requested_) {
|
||||
restart_requested_ = false;
|
||||
Audio::get()->stopMusic();
|
||||
Audio::get()->stopAllSounds();
|
||||
// Reinicialitza Info::ctx des d'Options (vides, diners, diamants...)
|
||||
// en lloc de ctx.reset() pla que deixaria vida=0 → jugador mort.
|
||||
initGameContext();
|
||||
// Força l'intro independentment de `piramide_inicial` (que pot estar
|
||||
// configurat a una piràmide intermèdia per a proves ràpides).
|
||||
Info::ctx.num_piramide = 255;
|
||||
current_scene_.reset();
|
||||
game_state_ = 1; // 1 = dispatch via SceneRegistry per num_piramide
|
||||
has_frame_ = false;
|
||||
Menu::close();
|
||||
Ji::setInputBlocked(false); // el menú ho havia bloquejat — cal desfer-ho
|
||||
applyRestart();
|
||||
}
|
||||
|
||||
if (!context_initialized_) {
|
||||
initGameContext();
|
||||
context_initialized_ = true;
|
||||
}
|
||||
|
||||
constexpr Uint32 FRAME_MS_VSYNC = 16; // ~60 FPS amb VSync
|
||||
constexpr Uint32 FRAME_MS_NO_VSYNC = 4; // ~250 FPS sense VSync (límit superior)
|
||||
|
||||
constexpr Uint32 FRAME_MS_VSYNC = 16;
|
||||
constexpr Uint32 FRAME_MS_NO_VSYNC = 4;
|
||||
const Uint32 FRAME_START = SDL_GetTicks();
|
||||
|
||||
Gamepad::update();
|
||||
KeyRemap::update();
|
||||
GlobalInputs::handle();
|
||||
Mouse::updateCursorVisibility();
|
||||
|
||||
// Bombeig de l'àudio: reomple l'stream de música i para els canals
|
||||
// drenats. Substituïx el callback de SDL_AddTimer de la versió
|
||||
// antiga — imprescindible per al port a emscripten.
|
||||
Audio::update();
|
||||
|
||||
// Dispara els crèdits cinematogràfics la primera vegada que el joc
|
||||
// arriba al menú del títol (Info::ctx.num_piramide == 0).
|
||||
static bool credits_triggered_ = false;
|
||||
if (!credits_triggered_ && Info::ctx.num_piramide == 0) {
|
||||
if (Options::game.show_title_credits) {
|
||||
Overlay::startCredits();
|
||||
}
|
||||
credits_triggered_ = true;
|
||||
}
|
||||
maybeStartTitleCredits();
|
||||
|
||||
// Si l'overlay ja no bloqueja ESC (timeout), desbloquegem
|
||||
if (esc_blocked_ && !Overlay::isEscConsumed()) {
|
||||
esc_blocked_ = false;
|
||||
}
|
||||
|
||||
// Avança l'escena (si no estem pausats). En pausa, es manté l'escena
|
||||
// congelada i re-presentem l'últim frame amb l'overlay fresc per
|
||||
// damunt.
|
||||
if (!paused_) {
|
||||
// Transicions: si l'escena actual ha acabat (o s'ha senyalat
|
||||
// quit), llegim el seu next state i la destruïm per crear la
|
||||
// següent a continuació.
|
||||
if (current_scene_ && (current_scene_->done() || Jg::quitting())) {
|
||||
game_state_ = current_scene_->nextState();
|
||||
current_scene_.reset();
|
||||
if (!tickActiveScene()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si no hi ha escena activa, construeix la pròxima segons
|
||||
// game_state_ i Info::ctx. Si és impossible (game_state_ == -1,
|
||||
// quit, o state no registrat), eixim del loop.
|
||||
if (!current_scene_) {
|
||||
if (game_state_ == -1 || Jg::quitting()) {
|
||||
return false;
|
||||
}
|
||||
current_scene_ = createNextScene();
|
||||
if (!current_scene_) {
|
||||
return false;
|
||||
}
|
||||
current_scene_->onEnter();
|
||||
last_tick_ms_ = SDL_GetTicks();
|
||||
}
|
||||
|
||||
// Tick de l'escena. Ji::update refresca key_pressed/any_key; el
|
||||
// DELTA_MS és el temps real transcorregut des de l'últim tick.
|
||||
Ji::update();
|
||||
const Uint32 NOW = SDL_GetTicks();
|
||||
const int DELTA_MS = static_cast<int>(NOW - last_tick_ms_);
|
||||
last_tick_ms_ = NOW;
|
||||
current_scene_->tick(DELTA_MS);
|
||||
|
||||
// Converteix `screen` indexat → `pixel_data` ARGB amb la paleta
|
||||
// actual. Jd8::flip ja no fa yield (Phase B.2 eliminà els fibers);
|
||||
// ara només omple el framebuffer perquè el Director l'aprofite.
|
||||
Jd8::flip();
|
||||
std::memcpy(game_frame_, Jd8::getFramebuffer(), sizeof(game_frame_));
|
||||
has_frame_ = true;
|
||||
}
|
||||
|
||||
// Presenta sempre: parteix del frame net del joc, afegeix overlay i envia
|
||||
if (has_frame_) {
|
||||
std::memcpy(presentation_buffer_, game_frame_, sizeof(presentation_buffer_));
|
||||
Screen::get()->present(presentation_buffer_);
|
||||
}
|
||||
|
||||
// Límit de framerate segons VSync.
|
||||
// Nota: quan el runtime posseïx el main loop (SDL_AppIterate /
|
||||
// emscripten), aquest SDL_Delay no és ideal. Fase 7 afegirà un mode
|
||||
// que es basa en el timing intern de SDL en lloc del delay explícit.
|
||||
const Uint32 TARGET_MS = Options::video.vsync ? FRAME_MS_VSYNC : FRAME_MS_NO_VSYNC;
|
||||
const Uint32 ELAPSED = SDL_GetTicks() - FRAME_START;
|
||||
if (ELAPSED < TARGET_MS) {
|
||||
@@ -285,107 +265,97 @@ void Director::pollAllEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
void Director::handleEvent(const SDL_Event& event) {
|
||||
if (event.type == SDL_EVENT_QUIT) {
|
||||
Jg::quitSignal();
|
||||
requestQuit();
|
||||
}
|
||||
// Hot-plug de gamepad (a Emscripten els dispositius web entren com
|
||||
// JOYSTICK_ADDED/REMOVED perquè SDL no reconeix el GUID)
|
||||
if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED ||
|
||||
event.type == SDL_EVENT_JOYSTICK_ADDED || event.type == SDL_EVENT_JOYSTICK_REMOVED) {
|
||||
Gamepad::handleEvent(event);
|
||||
return;
|
||||
}
|
||||
// Empassar-se el KEY_UP de qualsevol tecla que el menú va consumir en KEY_DOWN
|
||||
auto Director::handleMenuEvent(const SDL_Event& event) -> bool {
|
||||
// Empassar-se el KEY_UP d'una 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;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Captura de tecla (remapeig al menú): intercepta KEY_DOWN abans de tot
|
||||
if (Menu::isCapturing() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
|
||||
const bool KEY_DOWN = event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat;
|
||||
// Captura de tecla (remapeig al menú): intercepta KEY_DOWN abans de tot.
|
||||
if (Menu::isCapturing() && KEY_DOWN) {
|
||||
Menu::captureKey(event.key.scancode);
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Pausa: F11 (o tecla configurada) pausa/reprén la simulació.
|
||||
// No mostrem notificació — l'indicador persistent "Pausa" a la cantonada
|
||||
// superior dreta (pintat per Overlay) ja comunica l'estat.
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
|
||||
event.key.scancode == KeyConfig::scancode("pause_toggle")) {
|
||||
// Pausa / menú toggle.
|
||||
if (KEY_DOWN && event.key.scancode == KeyConfig::scancode("pause_toggle")) {
|
||||
togglePause();
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Menú: F12 (o tecla configurada) obre/tanca el menú flotant
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
|
||||
event.key.scancode == KeyConfig::scancode("menu_toggle")) {
|
||||
if (KEY_DOWN && event.key.scancode == KeyConfig::scancode("menu_toggle")) {
|
||||
Menu::toggle();
|
||||
Ji::setInputBlocked(Menu::isOpen());
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Si el menú està obert, consumeix tot l'input de teclat
|
||||
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
|
||||
// Si el menú està obert, consumeix tot l'input de teclat.
|
||||
if (Menu::isOpen() && KEY_DOWN) {
|
||||
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;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_UP) {
|
||||
return; // no deixem passar KEY_UP al joc tampoc
|
||||
return true;
|
||||
}
|
||||
// Salta els crèdits amb qualsevol tecla que arribe al joc. Es fa DESPRÉS
|
||||
// del toggle del menú/pausa i del handling del menú obert — així F12 i
|
||||
// SELECT (gamepad) obrin el menú sense cancel·lar els crèdits, i la
|
||||
// navegació per dins del menú tampoc els anul·la.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Director::handleEscapeEvent(const SDL_Event& event) -> bool {
|
||||
// Salta els crèdits amb qualsevol tecla que arribe al joc.
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && Overlay::creditsActive()) {
|
||||
Overlay::cancelCredits();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Allibera el bloqueig d'ESC quan l'usuari la deixa anar
|
||||
// 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;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// ESC: interceptem KEY_DOWN per bloquejar-la ABANS que el joc la veja per polling
|
||||
// ESC KEY_DOWN: bloqueja per polling i decideix notificació vs eixida.
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_ESCAPE && !event.key.repeat) {
|
||||
esc_blocked_ = true; // Bloqueja ESC per polling immediatament
|
||||
esc_blocked_ = true;
|
||||
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();
|
||||
// Si estem en pausa, la desactivem: el fiber del joc està
|
||||
// congelat i necessita ser reprès per veure la senyal de
|
||||
// quit i poder tornar de forma natural.
|
||||
paused_ = false;
|
||||
}
|
||||
return; // no processa més aquest event
|
||||
return true;
|
||||
}
|
||||
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
|
||||
return;
|
||||
} // Comprova si és una tecla d'UI registrada (no passa al joc).
|
||||
// KeyConfig::isGuiKey cobreix totes les tecles GUI a la vegada,
|
||||
// incloent pause_toggle i menu_toggle (defensa en profunditat:
|
||||
// aquestes ja s'haurien hagut de menjar al swallow d'amunt).
|
||||
return false;
|
||||
}
|
||||
|
||||
void Director::handleEvent(const SDL_Event& event) {
|
||||
if (event.type == SDL_EVENT_QUIT) {
|
||||
Jg::quitSignal();
|
||||
requestQuit();
|
||||
}
|
||||
if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED ||
|
||||
event.type == SDL_EVENT_JOYSTICK_ADDED || event.type == SDL_EVENT_JOYSTICK_REMOVED) {
|
||||
Gamepad::handleEvent(event);
|
||||
return;
|
||||
}
|
||||
if (handleMenuEvent(event)) {
|
||||
return;
|
||||
}
|
||||
if (handleEscapeEvent(event)) {
|
||||
return;
|
||||
}
|
||||
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode != SDL_SCANCODE_ESCAPE) {
|
||||
const auto SC = event.key.scancode;
|
||||
if (!KeyConfig::isGuiKey(SC)) {
|
||||
key_pressed_ = true;
|
||||
|
||||
Reference in New Issue
Block a user