380 lines
12 KiB
C++
380 lines
12 KiB
C++
#include "core/system/director.hpp"
|
|
|
|
#include <cstring>
|
|
#include <iostream>
|
|
|
|
#include "core/audio/audio.hpp"
|
|
#include "core/input/gamepad.hpp"
|
|
#include "core/input/global_inputs.hpp"
|
|
#include "core/input/key_config.hpp"
|
|
#include "core/input/key_remap.hpp"
|
|
#include "core/input/mouse.hpp"
|
|
#include "core/jail/jdraw8.hpp"
|
|
#include "core/jail/jgame.hpp"
|
|
#include "core/jail/jinput.hpp"
|
|
#include "core/locale/locale.hpp"
|
|
#include "core/rendering/menu.hpp"
|
|
#include "core/rendering/overlay.hpp"
|
|
#include "core/rendering/screen.hpp"
|
|
#include "core/resources/resource_cache.hpp"
|
|
#include "game/info.hpp"
|
|
#include "game/modulegame.hpp"
|
|
#include "game/options.hpp"
|
|
#include "game/scenes/banner_scene.hpp"
|
|
#include "game/scenes/boot_loader_scene.hpp"
|
|
#include "game/scenes/credits_scene.hpp"
|
|
#include "game/scenes/intro_new_logo_scene.hpp"
|
|
#include "game/scenes/intro_scene.hpp"
|
|
#include "game/scenes/menu_scene.hpp"
|
|
#include "game/scenes/mort_scene.hpp"
|
|
#include "game/scenes/scene.hpp"
|
|
#include "game/scenes/scene_registry.hpp"
|
|
#include "game/scenes/secreta_scene.hpp"
|
|
#include "game/scenes/slides_scene.hpp"
|
|
|
|
std::unique_ptr<Director> Director::instance;
|
|
|
|
Director::~Director() = default;
|
|
|
|
void Director::initGameContext() {
|
|
Info::ctx.num_habitacio = Options::game.habitacio_inicial;
|
|
Info::ctx.num_piramide = Options::game.piramide_inicial;
|
|
Info::ctx.diners = Options::game.diners_inicial;
|
|
Info::ctx.diamants = Options::game.diamants_inicial;
|
|
Info::ctx.vida = Options::game.vides;
|
|
Info::ctx.momies = 0;
|
|
Info::ctx.nou_personatge = false;
|
|
Info::ctx.pepe_activat = false;
|
|
|
|
FILE* ini = fopen("trick.ini", "rb");
|
|
if (ini != nullptr) {
|
|
Info::ctx.nou_personatge = true;
|
|
fclose(ini);
|
|
}
|
|
}
|
|
|
|
auto Director::createNextScene() const -> std::unique_ptr<Scenes::Scene> {
|
|
// Mentre el Resource::Cache no haja acabat de precarregar, executem
|
|
// el BootLoaderScene — pinta una barra de progrés i avança la
|
|
// càrrega per pressupost de temps. Quan acaba, retorna i tornem ací
|
|
// amb el cache plenament disponible per a la resta d'escenes.
|
|
if (Resource::Cache::get() != nullptr && !Resource::Cache::get()->isLoadDone()) {
|
|
return std::make_unique<Scenes::BootLoaderScene>();
|
|
}
|
|
if (game_state_ == 0) {
|
|
// Gameplay. ModuleGame és una Scenes::Scene des de la Phase A.
|
|
return std::make_unique<ModuleGame>();
|
|
}
|
|
// game_state_ == 1: dispatch al registry per num_piramide. Replica
|
|
// del redirect que el vell ModuleSequence::Go() feia: si el jugador
|
|
// arriba a la Secreta (6) sense prou diners, salta als slides de
|
|
// fracàs (7) abans de buscar l'escena al registry.
|
|
if (Info::ctx.num_piramide == 6 && Info::ctx.diners < 200) {
|
|
Info::ctx.num_piramide = 7;
|
|
}
|
|
return Scenes::SceneRegistry::instance().tryCreate(Info::ctx.num_piramide);
|
|
}
|
|
|
|
void Director::init() {
|
|
instance = std::unique_ptr<Director>(new Director());
|
|
Gamepad::init();
|
|
|
|
// Registre d'escenes. Cada entrada = un state_key (`num_piramide`)
|
|
// amb una factory de `Scenes::Scene`. iterate() consulta aquest
|
|
// registry per a tots els states de seqüència (game_state_ == 1); si
|
|
// una clau no apareix ací, Director surt ordenadament.
|
|
auto& registry = Scenes::SceneRegistry::instance();
|
|
registry.registerScene(0, [] { return std::make_unique<Scenes::MenuScene>(); });
|
|
registry.registerScene(100, [] { return std::make_unique<Scenes::MortScene>(); });
|
|
// BannerScene cobreix les piràmides 2..5 (el vell doBanner decideix
|
|
// pel switch intern llegint Info::ctx.num_piramide).
|
|
for (int p = 2; p <= 5; ++p) {
|
|
registry.registerScene(p, [] { return std::make_unique<Scenes::BannerScene>(); });
|
|
}
|
|
// SlidesScene cobreix els dos states on el vell `doSlides` s'invocava:
|
|
// - num_piramide == 1: slides narratius inicials (entrada al joc)
|
|
// - num_piramide == 7: slides de fracàs (ve del redirect 6→7 quan
|
|
// l'usuari no té prou diners per a la Secreta)
|
|
registry.registerScene(1, [] { return std::make_unique<Scenes::SlidesScene>(); });
|
|
registry.registerScene(7, [] { return std::make_unique<Scenes::SlidesScene>(); });
|
|
registry.registerScene(6, [] { return std::make_unique<Scenes::SecretaScene>(); });
|
|
registry.registerScene(8, [] { return std::make_unique<Scenes::CreditsScene>(); });
|
|
// State 255 (intro): dues variants segons `Options::game.use_new_logo`.
|
|
// La factory tria a runtime — així es pot togglar des del menú sense
|
|
// re-registrar. Les dues escenes construeixen una IntroSpritesScene
|
|
// com a sub-escena per a la part d'animacions de sprites.
|
|
registry.registerScene(255, []() -> std::unique_ptr<Scenes::Scene> {
|
|
if (Options::game.use_new_logo) {
|
|
return std::make_unique<Scenes::IntroNewLogoScene>();
|
|
}
|
|
return std::make_unique<Scenes::IntroScene>();
|
|
});
|
|
}
|
|
|
|
void Director::destroy() {
|
|
Gamepad::destroy();
|
|
instance.reset();
|
|
}
|
|
|
|
auto Director::get() -> Director* {
|
|
return instance.get();
|
|
}
|
|
|
|
void Director::togglePause() {
|
|
paused_ = !paused_;
|
|
if (paused_) {
|
|
Audio::get()->pauseMusic();
|
|
} else {
|
|
Audio::get()->resumeMusic();
|
|
}
|
|
}
|
|
|
|
void Director::setup() {
|
|
// Els buffers són membres (director.hpp); només els inicialitzem.
|
|
std::memset(game_frame_, 0, sizeof(game_frame_));
|
|
std::memset(presentation_buffer_, 0, sizeof(presentation_buffer_));
|
|
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();
|
|
return false;
|
|
}
|
|
if (restart_requested_) {
|
|
applyRestart();
|
|
}
|
|
if (!context_initialized_) {
|
|
initGameContext();
|
|
context_initialized_ = true;
|
|
}
|
|
|
|
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();
|
|
Audio::update();
|
|
|
|
maybeStartTitleCredits();
|
|
|
|
if (esc_blocked_ && !Overlay::isEscConsumed()) {
|
|
esc_blocked_ = false;
|
|
}
|
|
|
|
if (!paused_) {
|
|
if (!tickActiveScene()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (has_frame_) {
|
|
std::memcpy(presentation_buffer_, game_frame_, sizeof(presentation_buffer_));
|
|
Screen::get()->present(presentation_buffer_);
|
|
}
|
|
|
|
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) {
|
|
SDL_Delay(TARGET_MS - ELAPSED);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Director::teardown() {
|
|
// Senyal de quit i descàrrega ordenada de l'escena en curs. Els
|
|
// destructors de cada escena són no-bloquejants — ja no fan fades
|
|
// bloquejants. La resta de cleanup la gestiona `destroy()`.
|
|
Jg::quitSignal();
|
|
current_scene_.reset();
|
|
}
|
|
|
|
void Director::run() {
|
|
setup();
|
|
while (true) {
|
|
pollAllEvents();
|
|
if (!iterate()) {
|
|
break;
|
|
}
|
|
}
|
|
teardown();
|
|
}
|
|
|
|
void Director::pollAllEvents() {
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event)) {
|
|
handleEvent(event);
|
|
}
|
|
}
|
|
|
|
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 true;
|
|
}
|
|
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 true;
|
|
}
|
|
// Pausa / menú toggle.
|
|
if (KEY_DOWN && event.key.scancode == KeyConfig::scancode("pause_toggle")) {
|
|
togglePause();
|
|
menu_keys_held_[event.key.scancode] = true;
|
|
return true;
|
|
}
|
|
if (KEY_DOWN && event.key.scancode == KeyConfig::scancode("menu_toggle")) {
|
|
Menu::toggle();
|
|
Ji::setInputBlocked(Menu::isOpen());
|
|
menu_keys_held_[event.key.scancode] = true;
|
|
return true;
|
|
}
|
|
// 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);
|
|
esc_swallow_until_release_ = true;
|
|
} else {
|
|
Menu::handleKey(event.key.scancode);
|
|
if (!Menu::isOpen()) {
|
|
Ji::setInputBlocked(false);
|
|
}
|
|
}
|
|
menu_keys_held_[event.key.scancode] = true;
|
|
return true;
|
|
}
|
|
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_UP) {
|
|
return true;
|
|
}
|
|
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 true;
|
|
}
|
|
// 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 true;
|
|
}
|
|
// 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;
|
|
if (!Overlay::isEscConsumed()) {
|
|
Overlay::handleEscape();
|
|
} else {
|
|
esc_blocked_ = false;
|
|
key_pressed_ = true;
|
|
Jg::quitSignal();
|
|
paused_ = false;
|
|
}
|
|
return true;
|
|
}
|
|
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;
|
|
Ji::moveCheats(SC);
|
|
}
|
|
}
|
|
Mouse::handleEvent(event);
|
|
}
|
|
|
|
void Director::requestQuit() {
|
|
quit_requested_ = true;
|
|
Jg::quitSignal();
|
|
}
|
|
|
|
void Director::requestRestart() {
|
|
restart_requested_ = true;
|
|
}
|
|
|
|
auto Director::consumeKeyPressed() -> bool {
|
|
return key_pressed_.exchange(false);
|
|
}
|