#include "CGameOptions.hpp" #include "GameGlobalObjects.hpp" #include "CMemoryCardSys.hpp" #include "CSimplePool.hpp" #include "CSaveWorld.hpp" #include "CGameHintInfo.hpp" #include "GameGlobalObjects.hpp" #include "CGameState.hpp" #include "Input/CFinalInput.hpp" #include "Audio/CSfxManager.hpp" #include "Audio/CStreamAudioManager.hpp" #include "Graphics/CMoviePlayer.hpp" #include "CStateManager.hpp" #include "hecl/CVarManager.hpp" namespace urde { static const SGameOption VisorOpts[] = { {EGameOption::VisorOpacity, 21, 0.f, 255.f, 1.f, EOptionType::Float}, {EGameOption::HelmetOpacity, 22, 0.f, 255.f, 1.f, EOptionType::Float}, {EGameOption::HUDLag, 23, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::HintSystem, 24, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults} }; static const SGameOption DisplayOpts[] = { //{EGameOption::ScreenBrightness, 25, 0.f, 8.f, 1.f, EOptionType::Float}, {EGameOption::ScreenBrightness, 25, -100.f, 100.f, 1.f, EOptionType::Float}, {EGameOption::ScreenOffsetX, 26, -30.f, 30.f, 1.f, EOptionType::Float}, {EGameOption::ScreenOffsetY, 27, -30.f, 30.f, 1.f, EOptionType::Float}, {EGameOption::ScreenStretch, 28, -10.f, 10.f, 1.f, EOptionType::Float}, {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults} }; static const SGameOption SoundOpts[] = { {EGameOption::SFXVolume, 29, 0.f, 127.f, 1.f, EOptionType::Float}, {EGameOption::MusicVolume, 30, 0.f, 127.f, 1.f, EOptionType::Float}, {EGameOption::SoundMode, 31, 0.f, 1.f, 1.f, EOptionType::TripleEnum}, {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults} }; static const SGameOption ControllerOpts[] = { {EGameOption::ReverseYAxis, 32, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::Rumble, 33, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::SwapBeamControls, 34, 0.f, 1.f, 1.f, EOptionType::DoubleEnum}, {EGameOption::RestoreDefaults, 35, 0.f, 1.f, 1.f, EOptionType::RestoreDefaults} }; const std::pair GameOptionsRegistry[] = { {5, VisorOpts}, {5, DisplayOpts}, {4, SoundOpts}, {4, ControllerOpts}, {0, nullptr} }; CPersistentOptions::CPersistentOptions(CBitStreamReader& stream) { for (int b=0 ; b<98 ; ++b) x0_nesState[b] = stream.ReadEncoded(8); for (int b=0 ; b<64 ; ++b) x68_[b] = stream.ReadEncoded(8); xc0_frozenFpsCount = stream.ReadEncoded(2); xc4_frozenBallCount = stream.ReadEncoded(2); xc8_powerBombAmmoCount = stream.ReadEncoded(1); xcc_logScanPercent = stream.ReadEncoded(7); xd0_24_fusionLinked = stream.ReadEncoded(1); xd0_25_normalModeBeat = stream.ReadEncoded(1); xd0_26_hardModeBeat = stream.ReadEncoded(1); xd0_27_fusionBeat = stream.ReadEncoded(1); xd0_28_fusionSuitActive = false; xd0_29_allItemsCollected = stream.ReadEncoded(1); xbc_autoMapperKeyState = stream.ReadEncoded(2); auto& memWorlds = g_MemoryCardSys->GetMemoryWorlds(); size_t cinematicCount = 0; for (const auto& world : memWorlds) { TLockedToken saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), world.second.GetSaveWorldAssetId()}); cinematicCount += saveWorld->GetCinematicCount(); } std::vector cinematicStates; cinematicStates.reserve(cinematicCount); for (size_t i=0 ; i saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), world.second.GetSaveWorldAssetId()}); auto stateIt = cinematicStates.cbegin(); for (TEditorId cineId : saveWorld->GetCinematics()) if (*stateIt++) SetCinematicState(world.first, cineId, true); } } void CPersistentOptions::PutTo(CBitStreamWriter& w) const { for (int b=0 ; b<98 ; ++b) w.WriteEncoded(x0_nesState[b], 8); for (int b=0 ; b<64 ; ++b) w.WriteEncoded(x68_[b], 8); w.WriteEncoded(xc0_frozenFpsCount, 2); w.WriteEncoded(xc4_frozenBallCount, 2); w.WriteEncoded(xc8_powerBombAmmoCount, 1); w.WriteEncoded(xcc_logScanPercent, 7); w.WriteEncoded(xd0_24_fusionLinked, 1); w.WriteEncoded(xd0_25_normalModeBeat, 1); w.WriteEncoded(xd0_26_hardModeBeat, 1); w.WriteEncoded(xd0_27_fusionBeat, 1); w.WriteEncoded(xd0_29_allItemsCollected, 1); w.WriteEncoded(xbc_autoMapperKeyState, 2); auto& memWorlds = g_MemoryCardSys->GetMemoryWorlds(); for (const auto& world : memWorlds) { TLockedToken saveWorld = g_SimplePool->GetObj(SObjectTag{FOURCC('SAVW'), world.second.GetSaveWorldAssetId()}); for (TEditorId cineId : saveWorld->GetCinematics()) w.WriteEncoded(GetCinematicState(world.first, cineId), 1); } } bool CPersistentOptions::GetCinematicState(CAssetId mlvlId, TEditorId cineId) const { auto existing = std::find_if(xac_cinematicStates.cbegin(), xac_cinematicStates.cend(), [&](const std::pair& pair) -> bool { return pair.first == mlvlId && pair.second == cineId; }); return existing != xac_cinematicStates.cend(); } void CPersistentOptions::SetCinematicState(CAssetId mlvlId, TEditorId cineId, bool state) { auto existing = std::find_if(xac_cinematicStates.cbegin(), xac_cinematicStates.cend(), [&](const std::pair& pair) -> bool { return pair.first == mlvlId && pair.second == cineId; }); if (state && existing == xac_cinematicStates.cend()) xac_cinematicStates.emplace_back(mlvlId, cineId); else if (!state && existing != xac_cinematicStates.cend()) xac_cinematicStates.erase(existing); } CGameOptions::CGameOptions(CBitStreamReader& stream) { for (int b=0 ; b<64 ; ++b) x0_[b] = stream.ReadEncoded(8); x44_soundMode = CAudioSys::ESurroundModes(stream.ReadEncoded(2)); x48_screenBrightness = stream.ReadEncoded(4); x4c_screenXOffset = stream.ReadEncoded(6) - 30; x50_screenYOffset = stream.ReadEncoded(6) - 30; x54_screenStretch = stream.ReadEncoded(5) - 10; x58_sfxVol = stream.ReadEncoded(7); x5c_musicVol = stream.ReadEncoded(7); x60_hudAlpha = stream.ReadEncoded(8); x64_helmetAlpha = stream.ReadEncoded(8); x68_24_hudLag = stream.ReadEncoded(1); x68_28_hintSystem = stream.ReadEncoded(1); x68_25_invertY = stream.ReadEncoded(1); x68_26_rumble = stream.ReadEncoded(1); x68_27_swapBeamsControls = stream.ReadEncoded(1); } void CGameOptions::ResetToDefaults() { x48_screenBrightness = 4; x4c_screenXOffset = 0; x50_screenYOffset = 0; x54_screenStretch = 0; x58_sfxVol = 0x7f; x5c_musicVol = 0x7f; x44_soundMode = CAudioSys::ESurroundModes::Stereo; x60_hudAlpha = 0xFF; x64_helmetAlpha = 0xFF; x68_24_hudLag = true; x68_25_invertY = false; x68_26_rumble = true; x68_27_swapBeamsControls = false; x68_28_hintSystem = true; InitSoundMode(); EnsureSettings(); } void CGameOptions::PutTo(CBitStreamWriter& writer) const { for (int b=0 ; b<64 ; ++b) writer.WriteEncoded(x0_[b], 8); writer.WriteEncoded(u32(x44_soundMode), 2); writer.WriteEncoded(x48_screenBrightness, 4); writer.WriteEncoded(x4c_screenXOffset + 30, 6); writer.WriteEncoded(x50_screenYOffset + 30, 6); writer.WriteEncoded(x54_screenStretch + 10, 5); writer.WriteEncoded(x58_sfxVol, 7); writer.WriteEncoded(x5c_musicVol, 7); writer.WriteEncoded(x60_hudAlpha, 8); writer.WriteEncoded(x64_helmetAlpha, 8); writer.WriteEncoded(x68_24_hudLag, 1); writer.WriteEncoded(x68_28_hintSystem, 1); writer.WriteEncoded(x68_25_invertY, 1); writer.WriteEncoded(x68_26_rumble, 1); writer.WriteEncoded(x68_27_swapBeamsControls, 1); } CGameOptions::CGameOptions() { x68_24_hudLag = true; x68_26_rumble = true; x68_28_hintSystem = true; InitSoundMode(); } float CGameOptions::TuneScreenBrightness() { return (0.375f * 1.f) + (float(x48_screenBrightness) * 0.25f); } void CGameOptions::InitSoundMode() { /* If system is mono, force x44 to mono, otherwise honor user preference */ } static float BrightnessCopyFilter = 0.f; void CGameOptions::SetScreenBrightness(s32 val, bool apply) { x48_screenBrightness = zeus::clamp(0, val, 8); if (apply) BrightnessCopyFilter = TuneScreenBrightness(); } void CGameOptions::ApplyGamma() { float gammaT = -m_gamma / 100.f + 1.f; if (gammaT < 1.f) gammaT = gammaT * 0.5f + 0.5f; if (zeus::close_enough(gammaT, 1.f, 0.05f)) gammaT = 1.f; CGraphics::g_BooFactory->setDisplayGamma(gammaT); } void CGameOptions::SetGamma(s32 val, bool apply) { m_gamma = zeus::clamp(-100, val, 100); if (apply) ApplyGamma(); } void CGameOptions::SetScreenPositionX(s32 pos, bool apply) { x4c_screenXOffset = zeus::clamp(-30, pos, 30); if (apply) { /* TOOD: CGraphics related funcs */ } } void CGameOptions::SetScreenPositionY(s32 pos, bool apply) { x50_screenYOffset = zeus::clamp(-30, pos, 30); if (apply) { /* TOOD: CGraphics related funcs */ } } void CGameOptions::SetScreenStretch(s32 st, bool apply) { x54_screenStretch = zeus::clamp(-10, st, 10); if (apply) { /* TOOD: CGraphics related funcs */ } } void CGameOptions::SetSfxVolume(s32 vol, bool apply) { x58_sfxVol = zeus::clamp(0, vol, 0x7f); if (apply) { CAudioSys::SysSetSfxVolume(x58_sfxVol, 1, 1, 1); CStreamAudioManager::SetSfxVolume(x58_sfxVol); CMoviePlayer::SetSfxVolume(x58_sfxVol); } } void CGameOptions::SetMusicVolume(s32 vol, bool apply) { x5c_musicVol = zeus::clamp(0, vol, 0x7f); if (apply) CStreamAudioManager::SetMusicVolume(x5c_musicVol); } void CGameOptions::SetHUDAlpha(u32 alpha) { x60_hudAlpha = alpha; } void CGameOptions::SetHelmetAlpha(u32 alpha) { x64_helmetAlpha = alpha; } void CGameOptions::SetHUDLag(bool lag) { x68_24_hudLag = lag; } void CGameOptions::SetSurroundMode(int mode, bool apply) { x44_soundMode = CAudioSys::ESurroundModes(zeus::clamp(0, mode, 2)); if (apply) CAudioSys::SetSurroundMode(x44_soundMode); } CAudioSys::ESurroundModes CGameOptions::GetSurroundMode() const { return x44_soundMode; } void CGameOptions::SetInvertYAxis(bool invert) { x68_25_invertY = invert; } void CGameOptions::SetIsRumbleEnabled(bool rumble) { x68_26_rumble = rumble; } void CGameOptions::SetSwapBeamControls(bool swap) { x68_27_swapBeamsControls = swap; if (!swap) SetControls(0); else SetControls(1); } void CGameOptions::SetIsHintSystemEnabled(bool hints) { x68_28_hintSystem = hints; } void CGameOptions::SetControls(int controls) { if (controls == 0) g_currentPlayerControl = g_tweakPlayerControl; else g_currentPlayerControl = g_tweakPlayerControlAlt; ResetControllerAssets(controls); } static const std::pair CStickToDPadRemap[] = { {0x2A13C23E, 0xF13452F8}, {0xA91A7703, 0xC042EC91}, {0x12A12131, 0x5F556002}, {0xA9798329, 0xB306E26F}, {0xCD7B1ACA, 0x8ADA8184}, }; static const std::pair CStickOutlineToDPadRemap[] = { {0x1A29C0E6, 0xF13452F8}, {0x5D9F9796, 0xC042EC91}, {0x951546A8, 0x5F556002}, {0x7946C4C5, 0xB306E26F}, {0x409AA72E, 0x8ADA8184}, }; static std::pair TranslatePairToNew(const std::pair& p) { return {g_ResFactory->TranslateOriginalToNew(p.first), g_ResFactory->TranslateOriginalToNew(p.second)}; } void CGameOptions::ResetControllerAssets(int controls) { if (controls != 1) { x6c_controlTxtrMap.clear(); } else if (x6c_controlTxtrMap.empty()) { x6c_controlTxtrMap.reserve(15); for (int i=0 ; i<5 ; ++i) { x6c_controlTxtrMap.push_back(TranslatePairToNew(CStickToDPadRemap[i])); x6c_controlTxtrMap.push_back({x6c_controlTxtrMap.back().second, x6c_controlTxtrMap.back().first}); } for (int i=0 ; i<5 ; ++i) x6c_controlTxtrMap.push_back(TranslatePairToNew(CStickOutlineToDPadRemap[i])); std::sort(x6c_controlTxtrMap.begin(), x6c_controlTxtrMap.end(), [](const std::pair& a, const std::pair& b) { return a.first < b.first; }); } } void CGameOptions::EnsureSettings() { SetScreenBrightness(x48_screenBrightness, true); SetGamma(m_gamma, true); SetScreenPositionX(x4c_screenXOffset, true); SetScreenPositionY(x50_screenYOffset, true); SetScreenStretch(x54_screenStretch, true); SetSfxVolume(x58_sfxVol, true); SetMusicVolume(x5c_musicVol, true); SetSurroundMode(int(x44_soundMode), true); SetHelmetAlpha(x64_helmetAlpha); SetHUDLag(x68_24_hudLag); SetInvertYAxis(x68_25_invertY); SetIsRumbleEnabled(x68_26_rumble); SetIsHintSystemEnabled(x68_28_hintSystem); SetSwapBeamControls(x68_27_swapBeamsControls); } void CGameOptions::TryRestoreDefaults(const CFinalInput& input, int category, int option, bool frontend, bool forceRestore) { const std::pair& options = GameOptionsRegistry[category]; if (!options.first) return; if (options.second[option].option != EGameOption::RestoreDefaults) return; if (!forceRestore && !input.PA()) return; if (frontend) { CSfxManager::SfxStart(1096, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); CSfxManager::SfxStart(1091, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } else { CSfxManager::SfxStart(1432, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); } CGameOptions& gameOptions = g_GameState->GameOptions(); switch (category) { case 0: gameOptions.SetHelmetAlpha(0xff); gameOptions.SetHUDLag(true); gameOptions.SetIsHintSystemEnabled(true); break; case 1: gameOptions.SetScreenBrightness(4, true); gameOptions.SetGamma(0, true); gameOptions.SetScreenPositionX(0, true); gameOptions.SetScreenPositionY(0, true); gameOptions.SetScreenStretch(0, true); break; case 2: gameOptions.SetSfxVolume(0x7f, true); gameOptions.SetMusicVolume(0x7f, true); gameOptions.SetSurroundMode(1, true); break; case 3: gameOptions.SetInvertYAxis(false); gameOptions.SetIsRumbleEnabled(true); gameOptions.SetSwapBeamControls(false); break; default: break; } } void CGameOptions::SetOption(EGameOption option, int value) { CGameOptions& options = g_GameState->GameOptions(); switch (option) { case EGameOption::VisorOpacity: options.SetHUDAlpha(value); break; case EGameOption::HelmetOpacity: options.SetHelmetAlpha(value); break; case EGameOption::HUDLag: options.SetHUDLag(value); break; case EGameOption::HintSystem: options.SetIsHintSystemEnabled(value); break; case EGameOption::ScreenBrightness: options.SetGamma(value, true); break; case EGameOption::ScreenOffsetX: options.SetScreenPositionX(value, true); break; case EGameOption::ScreenOffsetY: options.SetScreenPositionY(value, true); break; case EGameOption::ScreenStretch: options.SetScreenStretch(value, true); break; case EGameOption::SFXVolume: options.SetSfxVolume(value, true); break; case EGameOption::MusicVolume: options.SetMusicVolume(value, true); break; case EGameOption::SoundMode: options.SetSurroundMode(value, true); break; case EGameOption::ReverseYAxis: options.SetInvertYAxis(value); break; case EGameOption::Rumble: options.SetIsRumbleEnabled(value); break; case EGameOption::SwapBeamControls: options.SetSwapBeamControls(value); break; default: break; } } int CGameOptions::GetOption(EGameOption option) { const CGameOptions& options = g_GameState->GameOptions(); switch (option) { case EGameOption::VisorOpacity: return options.GetHUDAlpha(); case EGameOption::HelmetOpacity: return options.GetHelmetAlpha(); case EGameOption::HUDLag: return options.GetHUDLag(); case EGameOption::HintSystem: return options.GetIsHintSystemEnabled(); case EGameOption::ScreenBrightness: return options.GetGamma(); case EGameOption::ScreenOffsetX: return options.GetScreenPositionX(); case EGameOption::ScreenOffsetY: return options.GetScreenPositionY(); case EGameOption::ScreenStretch: return options.GetScreenStretch(); case EGameOption::SFXVolume: return options.GetSfxVolume(); case EGameOption::MusicVolume: return options.GetMusicVolume(); case EGameOption::SoundMode: return int(options.GetSurroundMode()); case EGameOption::ReverseYAxis: return options.GetInvertYAxis(); case EGameOption::Rumble: return options.GetIsRumbleEnabled(); case EGameOption::SwapBeamControls: return options.GetSwapBeamControls(); default: break; } return 0; } CHintOptions::CHintOptions(CBitStreamReader& stream) { const auto& hints = g_MemoryCardSys->GetHints(); x0_hintStates.reserve(hints.size()); u32 hintIdx = 0; for (const auto& hint : hints) { (void)hint; EHintState state = EHintState(stream.ReadEncoded(2)); u32 timeBits = stream.ReadEncoded(32); float time = reinterpret_cast(timeBits); if (state == EHintState::Zero) time = 0.f; x0_hintStates.emplace_back(state, time, false); if (x10_nextHintIdx == -1 && state == EHintState::Displaying) x10_nextHintIdx = hintIdx; ++hintIdx; } } void CHintOptions::PutTo(CBitStreamWriter& writer) const { for (const SHintState& hint : x0_hintStates) { writer.WriteEncoded(u32(hint.x0_state), 2); writer.WriteEncoded(reinterpret_cast(hint.x4_time), 32); } } void CHintOptions::SetNextHintTime() { if (x10_nextHintIdx == -1) return; x0_hintStates[x10_nextHintIdx].x4_time = g_MemoryCardSys->GetHints()[x10_nextHintIdx].GetTextTime() + 5.f; } void CHintOptions::InitializeMemoryState() { const auto& hints = g_MemoryCardSys->GetHints(); x0_hintStates.resize(hints.size()); } const CHintOptions::SHintState* CHintOptions::GetCurrentDisplayedHint() const { if (!g_GameState->GameOptions().GetIsHintSystemEnabled()) return nullptr; if (x10_nextHintIdx == -1) return nullptr; const SHintState& hintState = x0_hintStates[x10_nextHintIdx]; const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[x10_nextHintIdx]; if (hintState.x4_time >= hint.GetTextTime()) return nullptr; if (hintState.x4_time < 3.f) return &hintState; if (!hintState.x8_dismissed) return &hintState; return nullptr; } void CHintOptions::DelayHint(const char* name) { int idx = CGameHintInfo::FindHintIndex(name); if (idx == -1) return; if (x10_nextHintIdx == idx) for (SHintState& state : x0_hintStates) state.x4_time += 60.f; x0_hintStates[idx].x0_state = EHintState::Delayed; } void CHintOptions::ActivateImmediateHintTimer(const char* name) { int idx = CGameHintInfo::FindHintIndex(name); if (idx == -1) return; SHintState& hintState = x0_hintStates[idx]; const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[idx]; if (hintState.x0_state != EHintState::Zero) return; hintState.x0_state = EHintState::Waiting; hintState.x4_time = hint.GetImmediateTime(); } void CHintOptions::ActivateContinueDelayHintTimer(const char* name) { int idx = x10_nextHintIdx; if (idx != 0) idx = CGameHintInfo::FindHintIndex(name); if (idx == -1) return; SHintState& hintState = x0_hintStates[idx]; const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[idx]; if (hintState.x0_state != EHintState::Displaying) return; hintState.x4_time = hint.GetTextTime(); } void CHintOptions::DismissDisplayedHint() { if (x10_nextHintIdx == -1) return; const CGameHintInfo::CGameHint& hint = g_MemoryCardSys->GetHints()[x10_nextHintIdx]; SHintState& hintState = x0_hintStates[x10_nextHintIdx]; if (hintState.x4_time >= hint.GetTextTime()) return; hintState.x4_time = hint.GetNormalTime(); hintState.x8_dismissed = true; } u32 CHintOptions::GetNextHintIdx() const { if (g_GameState->GameOptions().GetIsHintSystemEnabled()) return x10_nextHintIdx; return -1; } void CHintOptions::Update(float dt, const CStateManager& stateMgr) { x10_nextHintIdx = -1; int idx = 0; auto memIt = g_MemoryCardSys->GetHints().begin(); for (SHintState& state : x0_hintStates) { switch (state.x0_state) { case EHintState::Waiting: state.x4_time -= dt; if (state.x4_time <= 0.f) { state.x0_state = EHintState::Displaying; state.x4_time = memIt->GetTextTime(); } break; case EHintState::Displaying: if (x10_nextHintIdx == -1) x10_nextHintIdx = idx; default: break; } ++memIt; ++idx; } if (x10_nextHintIdx == -1) return; SHintState& state = x0_hintStates[x10_nextHintIdx]; const CGameHintInfo::CGameHint& data = g_MemoryCardSys->GetHints()[x10_nextHintIdx]; state.x4_time = std::max(0.f, state.x4_time - dt); if (state.x4_time < data.GetTextTime()) { for (const CGameHintInfo::SHintLocation& loc : data.GetLocations()) { if (loc.x0_mlvlId == stateMgr.GetWorld()->IGetWorldAssetId() && loc.x8_areaId == stateMgr.GetNextAreaId()) { state.x4_time = data.GetNormalTime(); state.x8_dismissed = true; } } } } }