text: #include "Runtime/MP1/CMFGame.hpp"
  
  #include <array>
  
  #include "Runtime/CArchitectureQueue.hpp"
  #include "Runtime/MP1/CSamusHud.hpp"
  #include "Runtime/MP1/MP1.hpp"
  #include "Runtime/Audio/CMidiManager.hpp"
  #include "Runtime/AutoMapper/CAutoMapper.hpp"
  #include "Runtime/Camera/CCinematicCamera.hpp"
  #include "Runtime/World/CPlayer.hpp"
  
  #include "TCastTo.hpp" // Generated file, do not modify include path
  
  namespace metaforce::MP1 {
  
  CMFGame::CMFGame(const std::weak_ptr<CStateManager>& stateMgr, const std::weak_ptr<CInGameGuiManager>& guiMgr,
                   const CArchitectureQueue&)
  : CMFGameBase("CMFGame"), x14_stateManager(stateMgr.lock()), x18_guiManager(guiMgr.lock()) {
    static_cast<CMain&>(*g_Main).SetMFGameBuilt(true);
  }
  
  CMFGame::~CMFGame() {
    auto& main = static_cast<CMain&>(*g_Main);
    main.SetMFGameBuilt(false);
    main.SetScreenFading(false);
    CDecalManager::Reinitialize();
  }
  
  CIOWin::EMessageReturn CMFGame::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {
    switch (msg.GetType()) {
    case EArchMsgType::FrameBegin:
      x14_stateManager->FrameBegin(msg.GetParm<CArchMsgParmInt32>()->x4_parm);
      break;
    case EArchMsgType::TimerTick: {
      bool wasInitialized = x2a_24_initialized;
      x2a_24_initialized = true;
      float dt = MakeMsg::GetParmTimerTick(msg).x4_parm;
  
      /* URDE addition: this is continuously updated for animated UVs even when game paused */
      x14_stateManager->UpdateGraphicsTiming(dt);
  
      switch (x1c_flowState) {
      case EGameFlowState::CinematicSkip: {
        x20_cineSkipTime += dt;
        const CEntity* cam = x14_stateManager->GetCameraManager()->GetCurrentCamera(*x14_stateManager);
        TCastToConstPtr<CCinematicCamera> cineCam = cam;
        if ((x20_cineSkipTime >= 1.f && x14_stateManager->SpecialSkipCinematic()) || !cineCam ||
            (cineCam->GetFlags() & 0x10 && x28_skippedCineCam != cineCam->GetUniqueId())) {
          static_cast<CMain&>(*g_Main).SetScreenFading(false);
          x1c_flowState = EGameFlowState::InGame;
          x18_guiManager->StartFadeIn();
          x28_skippedCineCam = kInvalidUniqueId;
          break;
        }
        [[fallthrough]];
      }
      case EGameFlowState::InGame: {
        x14_stateManager->SetActiveRandomToDefault();
        switch (x14_stateManager->GetDeferredStateTransition()) {
        case EStateManagerTransition::InGame:
          x14_stateManager->Update(dt);
          if (!x14_stateManager->ShouldQuitGame())
            break;
          // CGraphics::SetIsBeginSceneClearFb();
          break;
        case EStateManagerTransition::MapScreen:
          EnterMapScreen();
          break;
        case EStateManagerTransition::PauseGame:
          PauseGame();
          break;
        case EStateManagerTransition::LogBook:
          EnterLogBook();
          break;
        case EStateManagerTransition::SaveGame:
          SaveGame();
          break;
        case EStateManagerTransition::MessageScreen:
          EnterMessageScreen(x14_stateManager->GetHUDMessageTime());
          break;
        }
  
        if (x2a_25_samusAlive && !x14_stateManager->GetPlayerState()->IsPlayerAlive()) {
          PlayerDied();
        }
        x14_stateManager->ClearActiveRandom();
        break;
      }
      case EGameFlowState::Paused: {
        if (x18_guiManager->WasInGame() && x18_guiManager->IsInGame()) {
          x14_stateManager->SetInSaveUI(x18_guiManager->IsInSaveUI());
          UnpauseGame();
          if (x14_stateManager->GetPauseHUDMessage().IsValid())
            x14_stateManager->IncrementHUDMessageFrameCounter();
        }
        break;
      }
      case EGameFlowState::SamusDied: {
        if (x14_stateManager->GetPlayer().IsPlayerDeadEnough()) {
          static_cast<CMain&>(*g_Main).SetFlowState(EClientFlowStates::LoseGame);
          queue.Push(MakeMsg::CreateQuitGameplay(EArchMsgTarget::Game));
        } else {
          x14_stateManager->SetActiveRandomToDefault();
          x14_stateManager->Update(dt);
          x14_stateManager->ClearActiveRandom();
        }
        break;
      }
      default:
        break;
      }
  
      x18_guiManager->Update(*x14_stateManager, dt, queue, IsCameraActiveFlow());
      if (!wasInitialized)
        g_GameState->GetWorldTransitionManager()->EndTransition();
  
      return EMessageReturn::Exit;
    }
    case EArchMsgType::UserInput: {
      if (!x2a_24_initialized)
        break;
      const CFinalInput& input = MakeMsg::GetParmUserInput(msg).x4_parm;
      if (x1c_flowState == EGameFlowState::InGame) {
        if (input.ControllerIdx() == 0) {
          const CEntity* cam = x14_stateManager->GetCameraManager()->GetCurrentCamera(*x14_stateManager);
          TCastToConstPtr<CCinematicCamera> cineCam = cam;
          if (input.PStart() || input.PSpecialKey(boo::ESpecialKey::Esc)) {
            if (cineCam && x14_stateManager->GetSkipCinematicSpecialFunction() != kInvalidUniqueId) {
              CMidiManager::StopAll();
              x28_skippedCineCam = cineCam->GetUniqueId();
              x1c_flowState = EGameFlowState::CinematicSkip;
              x20_cineSkipTime = 0.f;
            } else if (!cineCam) {
              x14_stateManager->DeferStateTransition(EStateManagerTransition::PauseGame);
            }
          } else if ((input.PZ() || input.PKey('\t')) && !cineCam && x14_stateManager->CanShowMapScreen()) {
            x14_stateManager->DeferStateTransition(EStateManagerTransition::MapScreen);
          }
        }
  
        x14_stateManager->SetActiveRandomToDefault();
        x14_stateManager->ProcessInput(input);
        x14_stateManager->ClearActiveRandom();
      }
      x18_guiManager->ProcessControllerInput(*x14_stateManager, input, queue);
      break;
    }
    case EArchMsgType::FrameEnd: {
      x14_stateManager->FrameEnd();
      if (x14_stateManager->ShouldQuitGame())
        queue.Push(MakeMsg::CreateQuitGameplay(EArchMsgTarget::Game));
      break;
    }
    case EArchMsgType::QuitGameplay:
      return EMessageReturn::RemoveIOWin;
    default:
      break;
    }
  
    return EMessageReturn::Normal;
  }
  
  void CMFGame::Touch() {
    x14_stateManager->TouchSky();
    x14_stateManager->TouchPlayerActor();
  
    bool gunVisible = false;
    bool ballVisible = false;
    bool samusVisible = false;
    CPlayer& player = x14_stateManager->GetPlayer();
    switch (player.GetMorphballTransitionState()) {
    case CPlayer::EPlayerMorphBallState::Unmorphed:
      gunVisible = true;
      break;
    case CPlayer::EPlayerMorphBallState::Morphed:
      ballVisible = true;
      break;
    case CPlayer::EPlayerMorphBallState::Morphing:
      ballVisible = true;
      samusVisible = true;
      break;
    case CPlayer::EPlayerMorphBallState::Unmorphing:
      gunVisible = true;
      samusVisible = true;
      break;
    }
  
    if (gunVisible)
      player.GetPlayerGun()->TouchModel(*x14_stateManager);
    if (samusVisible)
      player.GetModelData()->Touch(*x14_stateManager, 0);
    if (ballVisible)
      player.GetMorphBall()->TouchModel(*x14_stateManager);
  }
  
  void CMFGame::Draw() {
    if (!x2a_24_initialized) {
      return;
    }
    SCOPED_GRAPHICS_DEBUG_GROUP("CMFGame::Draw", zeus::skGreen);
  
    Touch();
    if (x18_guiManager->GetIsGameDraw()) {
      static_cast<CMain&>(*g_Main).SetGameFrameDrawn();
      x14_stateManager->PreRender();
      x14_stateManager->DrawWorld();
      x14_stateManager->GetPlayer().IsPlayerDeadEnough();
    }
  
    x18_guiManager->PreDraw(*x14_stateManager, IsCameraActiveFlow());
    x18_guiManager->Draw(*x14_stateManager);
  
    if (x1c_flowState == EGameFlowState::CinematicSkip) {
      const float c = std::min(1.f, 1.f - x20_cineSkipTime);
      m_fadeToBlack.draw(zeus::CColor{c, c, c, c});
    }
  }
  
  void CMFGame::PlayerDied() {
    x1c_flowState = EGameFlowState::SamusDied;
    x2a_25_samusAlive = false;
  }
  
  void CMFGame::UnpauseGame() {
    x1c_flowState = EGameFlowState::InGame;
    CSfxManager::SetChannel(CSfxManager::ESfxChannels::Game);
    x14_stateManager->DeferStateTransition(EStateManagerTransition::InGame);
  }
  
  void CMFGame::EnterMessageScreen(float time) {
    x1c_flowState = EGameFlowState::Paused;
    x18_guiManager->ShowPauseGameHudMessage(*x14_stateManager, x14_stateManager->GetPauseHUDMessage(), time);
  }
  
  void CMFGame::SaveGame() {
    x1c_flowState = EGameFlowState::Paused;
    x18_guiManager->PauseGame(*x14_stateManager, EInGameGuiState::PauseSaveGame);
  }
  
  void CMFGame::EnterLogBook() {
    x1c_flowState = EGameFlowState::Paused;
    x18_guiManager->PauseGame(*x14_stateManager, EInGameGuiState::PauseLogBook);
  }
  
  void CMFGame::PauseGame() {
    x1c_flowState = EGameFlowState::Paused;
    x18_guiManager->PauseGame(*x14_stateManager, EInGameGuiState::PauseGame);
  }
  
  void CMFGame::EnterMapScreen() {
    x1c_flowState = EGameFlowState::Paused;
    x18_guiManager->PauseGame(*x14_stateManager, EInGameGuiState::MapScreen);
    x14_stateManager->SetInMapScreen(true);
  }
  
  CMFGameLoader::CMFGameLoader() : CMFGameLoaderBase("CMFGameLoader") {
    auto* m = static_cast<CMain*>(g_Main);
    switch (m->GetFlowState()) {
    case EClientFlowStates::Default:
    case EClientFlowStates::StateSetter: {
      CAssetId mlvlId = g_GameState->CurrentWorldAssetId();
      if (g_MemoryCardSys->HasSaveWorldMemory(mlvlId)) {
        const CSaveWorldMemory& savwMem = g_MemoryCardSys->GetSaveWorldMemory(mlvlId);
        if (savwMem.GetWorldNameId().IsValid()) {
          g_GameState->GetWorldTransitionManager()->EnableTransition(0xB7BBD0B4, savwMem.GetWorldNameId(), 1, false, 0.1f,
                                                                     16.f, 1.f);
        }
      }
      break;
    }
    default:
      break;
    }
  
    if (g_GameState->CurrentWorldAssetId() == 0x158EFE17 && g_GameState->CurrentWorldState().GetCurrentAreaId() == 0) {
      const SObjectTag* strgTag = g_ResFactory->GetResourceIdByName("STRG_IntroLevelLoad");
      if (strgTag)
        g_GameState->GetWorldTransitionManager()->EnableTransition(-1, strgTag->id, 0, false, 0.1f, 16.f, 1.f);
    }
  }
  
  CMFGameLoader::~CMFGameLoader() = default;
  
  void CMFGameLoader::MakeLoadDependencyList() {
    static constexpr std::array loadDepPAKs{"TestAnim", "SamusGun", "SamGunFx"};
  
    std::vector<SObjectTag> tags;
    for (const auto* const pak : loadDepPAKs) {
      g_ResFactory->GetTagListForFile(pak, tags);
    }
  
    x1c_loadList.reserve(tags.size());
    for (const SObjectTag& tag : tags) {
      x1c_loadList.push_back(g_SimplePool->GetObj(tag));
    }
  }
  
  CIOWin::EMessageReturn CMFGameLoader::OnMessage(const CArchitectureMessage& msg, CArchitectureQueue& queue) {
    std::shared_ptr<CWorldTransManager> wtMgr = g_GameState->GetWorldTransitionManager();
  
    switch (msg.GetType()) {
    case EArchMsgType::TimerTick: {
      const CArchMsgParmReal32& tick = MakeMsg::GetParmTimerTick(msg);
      float dt = tick.x4_parm;
      if (!x2c_24_initialized) {
        if (x1c_loadList.empty()) {
          MakeLoadDependencyList();
          wtMgr->StartTransition();
          return EMessageReturn::Exit;
        }
        u32 loadingCount = 0;
        for (CToken& tok : x1c_loadList) {
          tok.Lock();
          if (!tok.IsLoaded())
            ++loadingCount;
        }
        wtMgr->Update(dt);
        if (loadingCount)
          return EMessageReturn::Exit;
        x2c_24_initialized = true;
      } else {
        wtMgr->Update(dt);
      }
  
      if (!x14_stateMgr) {
        CWorldTransManager::WaitForModelsAndTextures();
        CWorldState& wldState = g_GameState->CurrentWorldState();
        x14_stateMgr = std::make_shared<CStateManager>(wldState.Mailbox(), wldState.MapWorldInfo(),
                                                       g_GameState->GetPlayerState(), wtMgr, wldState.GetLayerState());
      }
  
      if (x14_stateMgr->xb3c_initPhase != CStateManager::EInitPhase::Done) {
        CWorldState& wldState = g_GameState->CurrentWorldState();
        x14_stateMgr->InitializeState(wldState.GetWorldAssetId(), wldState.GetCurrentAreaId(),
                                      wldState.GetDesiredAreaAssetId());
        return EMessageReturn::Exit;
      }
  
      if (!x18_guiMgr) {
        g_GameState->CurrentWorldState().SetDesiredAreaAssetId(CAssetId());
        x18_guiMgr = std::make_shared<CInGameGuiManager>(*x14_stateMgr, queue);
      }
      if (!x18_guiMgr->CheckLoadComplete(*x14_stateMgr))
        return EMessageReturn::Exit;
  
      x1c_loadList.clear();
  
      if (!CGraphics::g_BooFactory->areShadersReady())
        return EMessageReturn::Exit;
  
      wtMgr->StartTextFadeOut();
      x2c_25_transitionFinished = wtMgr->IsTransitionFinished();
      return EMessageReturn::Exit;
    }
    case EArchMsgType::FrameEnd: {
      if (x2c_25_transitionFinished) {
        queue.Push(MakeMsg::CreateCreateIOWin(EArchMsgTarget::IOWinManager, 10, 1000,
                                              std::make_shared<CMFGame>(x14_stateMgr, x18_guiMgr, queue)));
        return EMessageReturn::RemoveIOWinAndExit;
      }
      break;
    }
    default:
      break;
    }
    return EMessageReturn::Exit;
  }
  
  void CMFGameLoader::Draw() {
    SCOPED_GRAPHICS_DEBUG_GROUP("CMFGameLoader::Draw", zeus::skGreen);
    g_GameState->GetWorldTransitionManager()->Draw();
  }
  
  } // namespace metaforce::MP1
  