diff --git a/src/animation/CutsceneMgr.cpp b/src/animation/CutsceneMgr.cpp index 633618b..4cab79e 100644 --- a/src/animation/CutsceneMgr.cpp +++ b/src/animation/CutsceneMgr.cpp @@ -1,7 +1,9 @@ #include "common.h" +#include "crossplatform.h" #include "General.h" #include "CutsceneMgr.h" +#include "Game.h" #include "Directory.h" #include "Camera.h" #include "Streaming.h" @@ -22,6 +24,7 @@ #include "ColStore.h" #include "Radar.h" #include "Pools.h" +#include "Timer.h" const struct { const char *szTrackName; @@ -129,6 +132,12 @@ float CCutsceneMgr::ms_cutsceneTimer; bool CCutsceneMgr::ms_wasCutsceneSkipped; uint32 CCutsceneMgr::ms_cutsceneLoadStatus; bool CCutsceneMgr::ms_useCutsceneShadows = true; +bool CCutsceneMgr::ms_cutsceneHasMusic; +bool CCutsceneMgr::ms_cutsceneMusicNeedsResume; +bool CCutsceneMgr::ms_wasWindowPaused; +uint32 CCutsceneMgr::ms_cutsceneMusicResumeFrames; + +static const uint32 CUTSCENE_MUSIC_RESUME_RETRY_FRAMES = 60; bool bCamLoaded; bool bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver; // pls don't shrink the name :P @@ -164,6 +173,10 @@ CCutsceneMgr::Initialise(void) ms_useLodMultiplier = false; ms_animLoaded = false; ms_cutsceneProcessing = false; + ms_cutsceneHasMusic = false; + ms_cutsceneMusicNeedsResume = false; + ms_wasWindowPaused = false; + ms_cutsceneMusicResumeFrames = 0; ms_pCutsceneDir = new CDirectory(CUTSCENEDIRSIZE); ms_pCutsceneDir->ReadDirFile("ANIM\\CUTS.DIR"); @@ -188,6 +201,10 @@ CCutsceneMgr::LoadCutsceneData(const char *szCutsceneName) ms_cutsceneProcessing = true; ms_wasCutsceneSkipped = false; + ms_cutsceneHasMusic = false; + ms_cutsceneMusicNeedsResume = false; + ms_wasWindowPaused = false; + ms_cutsceneMusicResumeFrames = 0; CTimer::Suspend(); if (!bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver) CStreaming::RemoveCurrentZonesModels(); @@ -241,6 +258,7 @@ CCutsceneMgr::LoadCutsceneData(const char *szCutsceneName) printf("Start preload audio %s\n", szCutsceneName); DMAudio.PreloadCutSceneMusic(trackId); printf("End preload audio %s\n", szCutsceneName); + ms_cutsceneHasMusic = true; } } @@ -271,6 +289,37 @@ CCutsceneMgr::FinishCutscene() CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(false); } +void +CCutsceneMgr::RecordWindowPause(void) +{ + if (IsActiveOrLoadingForPause()) { + ms_wasWindowPaused = true; + ms_cutsceneMusicNeedsResume = ms_cutsceneMusicNeedsResume || + (ms_cutsceneHasMusic && (ms_cutsceneLoadStatus != 0 || DMAudio.IsCutSceneMusicPlaying())); + ms_cutsceneMusicResumeFrames = CUTSCENE_MUSIC_RESUME_RETRY_FRAMES; + } +} + +bool +CCutsceneMgr::SyncCutsceneToMusic(void) +{ + if (!ms_cutsceneHasMusic || !bCamLoaded) + return false; + + uint32 finishTime = TheCamera.GetCutSceneFinishTime(); + if (finishTime == 0) + return false; + + int32 musicPosition = DMAudio.GetCutSceneMusicPosition(); + if (musicPosition < 0) + return false; + + uint32 clampedPosition = musicPosition > (int32)finishTime ? finishTime : musicPosition; + TheCamera.SetPercentAlongCutScene(100.0f * (float)clampedPosition / (float)finishTime); + ms_cutsceneTimer = clampedPosition * 0.001f; + return true; +} + void CCutsceneMgr::SetupCutsceneToStart(void) { @@ -452,6 +501,10 @@ CCutsceneMgr::DeleteCutsceneData(void) } ms_running = false; ms_loaded = false; + ms_cutsceneHasMusic = false; + ms_cutsceneMusicNeedsResume = false; + ms_wasWindowPaused = false; + ms_cutsceneMusicResumeFrames = 0; FindPlayerPed()->bIsVisible = true; CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_CUTSCENE); @@ -506,10 +559,17 @@ CCutsceneMgr::Update(void) CUTSCENE_LOADING_4 }; + if (CGame::IsWindowPauseMenuActive() || CTimer::GetIsWindowMinimizedPaused() || psRefreshAndGetWindowMinimizedPause()) { + RecordWindowPause(); + CTimer::SetTimeStep(0.0f); + CTimer::SetTimeStepNonClipped(0.0f); + return; + } + switch (ms_cutsceneLoadStatus) { case CUTSCENE_LOADING_AUDIO: SetupCutsceneToStart(); - if (CGeneral::faststricmp(ms_cutsceneName, "finale")) + if (ms_cutsceneHasMusic) DMAudio.PlayPreloadedCutSceneMusic(); ms_cutsceneLoadStatus++; break; @@ -526,7 +586,28 @@ CCutsceneMgr::Update(void) if (!ms_running) return; - ms_cutsceneTimer += CTimer::GetTimeStepNonClippedInSeconds(); + bool syncedToMusic = false; + if (ms_wasWindowPaused) { + if (ms_cutsceneMusicNeedsResume) { + DMAudio.ResumeCutSceneMusicAfterPause(); + syncedToMusic = SyncCutsceneToMusic(); + if (!syncedToMusic && ms_cutsceneMusicResumeFrames != 0 && TheCamera.GetCutSceneFinishTime() != 0) { + --ms_cutsceneMusicResumeFrames; + CTimer::SetTimeStep(0.0f); + CTimer::SetTimeStepNonClipped(0.0f); + return; + } + } + ms_wasWindowPaused = false; + ms_cutsceneMusicNeedsResume = false; + ms_cutsceneMusicResumeFrames = 0; + } + + if (!syncedToMusic) + syncedToMusic = SyncCutsceneToMusic(); + + if (!syncedToMusic) + ms_cutsceneTimer += CTimer::GetTimeStepNonClippedInSeconds(); for (int i = 0; i < ms_numCutsceneObjs; i++) { int modelId = ms_pCutsceneObjects[i]->GetModelIndex(); @@ -548,7 +629,7 @@ CCutsceneMgr::Update(void) } } -bool CCutsceneMgr::HasCutsceneFinished(void) { return !bCamLoaded || TheCamera.GetPositionAlongSpline() == 1.0f; } +bool CCutsceneMgr::HasCutsceneFinished(void) { return !bCamLoaded || TheCamera.GetPositionAlongSpline() >= 1.0f; } void CCutsceneMgr::LoadAnimationUncompressed(char const* name) @@ -680,4 +761,4 @@ CCutsceneMgr::RemoveEverythingFromTheWorldForTheBiggestFuckoffCutsceneEver() pPlayerPed->ClearWeapons(); CGame::DrasticTidyUpMemory(true); -} \ No newline at end of file +} diff --git a/src/animation/CutsceneMgr.h b/src/animation/CutsceneMgr.h index 51ef6c0..254dc12 100644 --- a/src/animation/CutsceneMgr.h +++ b/src/animation/CutsceneMgr.h @@ -24,6 +24,11 @@ class CCutsceneMgr static bool ms_wasCutsceneSkipped; static bool ms_cutsceneProcessing; static bool ms_useCutsceneShadows; + static bool ms_cutsceneHasMusic; + static bool ms_cutsceneMusicNeedsResume; + static bool ms_wasWindowPaused; + static uint32 ms_cutsceneMusicResumeFrames; + static bool SyncCutsceneToMusic(void); public: static CDirectory *ms_pCutsceneDir; static uint32 ms_cutsceneLoadStatus; @@ -32,6 +37,7 @@ class CCutsceneMgr static bool IsRunning(void) { return ms_running; } static bool HasLoaded(void) { return ms_loaded; } static bool IsCutsceneProcessing(void) { return ms_cutsceneProcessing; } + static bool IsActiveOrLoadingForPause(void) { return ms_running || ms_cutsceneProcessing || ms_loaded || ms_cutsceneLoadStatus != 0; } static bool WasCutsceneSkipped(void) { return ms_wasCutsceneSkipped; } static bool UseLodMultiplier(void) { return ms_useLodMultiplier; } static CCutsceneObject* GetCutsceneObject(int id) { return ms_pCutsceneObjects[id]; } @@ -44,6 +50,7 @@ class CCutsceneMgr static void Shutdown(void); static void LoadCutsceneData(const char *szCutsceneName); static void FinishCutscene(void); + static void RecordWindowPause(void); static void SetupCutsceneToStart(void); static void SetCutsceneAnim(const char *animName, CObject *pObject); static void SetCutsceneAnimToLoop(const char *animName); diff --git a/src/audio/AudioLogic.cpp b/src/audio/AudioLogic.cpp index 87521f0..4d13ed2 100644 --- a/src/audio/AudioLogic.cpp +++ b/src/audio/AudioLogic.cpp @@ -9841,8 +9841,16 @@ cAudioManager::ProcessMissionAudioSlot(uint8 slot) static uint8 nCheckPlayingDelay[MISSION_AUDIO_SLOTS] = { 0, 0 }; static uint8 nFramesUntilFailedLoad[MISSION_AUDIO_SLOTS] = { 0, 0 }; static uint8 nFramesForPretendPlaying[MISSION_AUDIO_SLOTS] = { 0, 0 }; + static uint8 nFramesUntilFailedResume[MISSION_AUDIO_SLOTS] = { 0, 0 }; + const uint8 RESUME_RETRY_FRAMES = 60; - if (m_sMissionAudio.m_nSampleIndex[slot] == NO_SAMPLE) return; + if (m_sMissionAudio.m_nSampleIndex[slot] == NO_SAMPLE) { + nCheckPlayingDelay[slot] = 0; + nFramesUntilFailedLoad[slot] = 0; + nFramesForPretendPlaying[slot] = 0; + nFramesUntilFailedResume[slot] = 0; + return; + } switch (m_sMissionAudio.m_nLoadingStatus[slot]) { case LOADING_STATUS_NOT_LOADED: @@ -9860,6 +9868,7 @@ cAudioManager::ProcessMissionAudioSlot(uint8 slot) nFramesForPretendPlaying[slot] = 0; nCheckPlayingDelay[slot] = 0; nFramesUntilFailedLoad[slot] = 0; + nFramesUntilFailedResume[slot] = 0; } else if (!m_nUserPause) { if (++nFramesForPretendPlaying[slot] < 90) { m_sMissionAudio.m_nPlayStatus[slot] = PLAY_STATUS_PLAYING; @@ -9872,11 +9881,12 @@ cAudioManager::ProcessMissionAudioSlot(uint8 slot) } switch (m_sMissionAudio.m_nPlayStatus[slot]) { case PLAY_STATUS_STOPPED: + if (m_nUserPause) + break; + if (MissionScriptAudioUsesPoliceChannel(m_sMissionAudio.m_nSampleIndex[slot])) { SetMissionScriptPoliceAudio(m_sMissionAudio.m_nSampleIndex[slot]); } else { - if (m_nUserPause) - SampleManager.PauseStream(1, slot + 1); if (m_sMissionAudio.m_bPredefinedProperties[slot]) { if (m_sMissionAudio.m_nSampleIndex[slot] == STREAMED_SOUND_MISSION_CAMERAL) SampleManager.SetStreamedVolumeAndPan(80, 0, 1, slot + 1); @@ -9904,6 +9914,7 @@ cAudioManager::ProcessMissionAudioSlot(uint8 slot) } m_sMissionAudio.m_nPlayStatus[slot] = PLAY_STATUS_PLAYING; nCheckPlayingDelay[slot] = 30; + nFramesUntilFailedResume[slot] = 0; if (m_sMissionAudio.m_nSampleIndex[slot] >= STREAMED_SOUND_MISSION_MOB_01A && m_sMissionAudio.m_nSampleIndex[slot] <= STREAMED_SOUND_MISSION_MOB_99A) m_sMissionAudio.m_bIsMobile[slot] = true; break; @@ -9911,10 +9922,16 @@ cAudioManager::ProcessMissionAudioSlot(uint8 slot) if (m_bTimerJustReset) { ClearMissionAudio(slot); SampleManager.StopStreamedFile(slot + 1); + nFramesUntilFailedResume[slot] = 0; break; } if (MissionScriptAudioUsesPoliceChannel(m_sMissionAudio.m_nSampleIndex[slot])) { - if (!m_nUserPause) { + if (m_nUserPause) { + SampleManager.PauseStream(1, 1); + nFramesUntilFailedResume[slot] = 0; + } else { + if (m_nPreviousUserPause) + SampleManager.PauseStream(0, 1); if (nCheckPlayingDelay[slot]) { --nCheckPlayingDelay[slot]; } else if (GetMissionScriptPoliceAudioPlayingStatus() == PLAY_STATUS_FINISHED || m_sMissionAudio.m_nMissionAudioCounter[slot]-- == 0) { @@ -9924,30 +9941,37 @@ cAudioManager::ProcessMissionAudioSlot(uint8 slot) m_sMissionAudio.m_nMissionAudioCounter[slot] = 0; } } + } else if (m_nUserPause) { + SampleManager.PauseStream(1, slot + 1); + nFramesUntilFailedResume[slot] = 0; } else if (m_sMissionAudio.m_bIsPlaying[slot]) { + if (m_nPreviousUserPause) { + SampleManager.PauseStream(0, slot + 1); + nFramesUntilFailedResume[slot] = RESUME_RETRY_FRAMES; + } if (SampleManager.IsStreamPlaying(slot + 1) || m_nUserPause || m_nPreviousUserPause) { - if (m_nUserPause) - SampleManager.PauseStream(1, slot + 1); - else - { - SampleManager.PauseStream(0, slot + 1); - if (!m_sMissionAudio.m_bPredefinedProperties[slot]) { - distSquared = GetDistanceSquared(m_sMissionAudio.m_vecPos[slot]); - if (distSquared >= SQR(80.0f)) { - emittingVol = 0; - pan = 63; - } else { - emittingVol = 127; - if (distSquared > 0.0f) { - dist = Sqrt(distSquared); - emittingVol = ComputeVolume(127, 80.0f, dist); - } - TranslateEntity(&m_sMissionAudio.m_vecPos[slot], &vec); - pan = ComputePan(80.f, &vec); + if (SampleManager.IsStreamPlaying(slot + 1)) + nFramesUntilFailedResume[slot] = 0; + if (!m_sMissionAudio.m_bPredefinedProperties[slot]) { + distSquared = GetDistanceSquared(m_sMissionAudio.m_vecPos[slot]); + if (distSquared >= SQR(80.0f)) { + emittingVol = 0; + pan = 63; + } else { + emittingVol = 127; + if (distSquared > 0.0f) { + dist = Sqrt(distSquared); + emittingVol = ComputeVolume(127, 80.0f, dist); } - SampleManager.SetStreamedVolumeAndPan(emittingVol, pan, 1, slot + 1); + TranslateEntity(&m_sMissionAudio.m_vecPos[slot], &vec); + pan = ComputePan(80.f, &vec); } + SampleManager.SetStreamedVolumeAndPan(emittingVol, pan, 1, slot + 1); } + } else if (nFramesUntilFailedResume[slot]) { + SampleManager.PauseStream(0, slot + 1); + SampleManager.StartPreloadedStreamedFile(slot + 1); + --nFramesUntilFailedResume[slot]; } else if (m_sMissionAudio.m_nSampleIndex[slot] == STREAMED_SOUND_MISSION_ROK2_01) { m_sMissionAudio.m_nPlayStatus[slot] = PLAY_STATUS_STOPPED; } else { @@ -9957,15 +9981,25 @@ cAudioManager::ProcessMissionAudioSlot(uint8 slot) m_sMissionAudio.m_nSampleIndex[slot] = NO_SAMPLE; SampleManager.StopStreamedFile(slot + 1); m_sMissionAudio.m_nMissionAudioCounter[slot] = 0; + nFramesUntilFailedResume[slot] = 0; } } else { if (m_nUserPause) break; - if (nCheckPlayingDelay[slot]--) { + if (m_nPreviousUserPause) { + SampleManager.PauseStream(0, slot + 1); + if (!SampleManager.IsStreamPlaying(slot + 1)) + SampleManager.StartPreloadedStreamedFile(slot + 1); + nFramesUntilFailedResume[slot] = RESUME_RETRY_FRAMES; + } + if (nCheckPlayingDelay[slot]) { + --nCheckPlayingDelay[slot]; if (!SampleManager.IsStreamPlaying(slot + 1)) break; nCheckPlayingDelay[slot] = 0; } + if (SampleManager.IsStreamPlaying(slot + 1)) + nFramesUntilFailedResume[slot] = 0; m_sMissionAudio.m_bIsPlaying[slot] = true; } break; diff --git a/src/audio/DMAudio.cpp b/src/audio/DMAudio.cpp index 7c99e89..94fe317 100644 --- a/src/audio/DMAudio.cpp +++ b/src/audio/DMAudio.cpp @@ -26,6 +26,36 @@ cDMAudio::Service(void) AudioManager.Service(); } +void +cDMAudio::SetStreamsPausedForWindowPause(bool paused) +{ + static bool windowPauseActive = false; + static bool wasPlaying[MAX_STREAMS] = {}; + + if (paused) { + if (windowPauseActive) + return; + + windowPauseActive = true; + for (uint8 i = 0; i < MAX_STREAMS; i++) { + wasPlaying[i] = SampleManager.IsStreamPlaying(i); + if (wasPlaying[i]) + SampleManager.PauseStream(1, i); + } + return; + } + + if (!windowPauseActive) + return; + + for (uint8 i = 0; i < MAX_STREAMS; i++) { + if (wasPlaying[i]) + SampleManager.PauseStream(0, i); + wasPlaying[i] = false; + } + windowPauseActive = false; +} + int32 cDMAudio::CreateEntity(eAudioType type, void *UID) { @@ -279,6 +309,24 @@ cDMAudio::PlayPreloadedCutSceneMusic(void) MusicManager.PlayPreloadedCutSceneMusic(); } +bool +cDMAudio::ResumeCutSceneMusicAfterPause(void) +{ + return MusicManager.ResumeCutSceneMusicAfterPause(); +} + +bool +cDMAudio::IsCutSceneMusicPlaying(void) +{ + return MusicManager.IsCutSceneMusicPlaying(); +} + +int32 +cDMAudio::GetCutSceneMusicPosition(void) +{ + return MusicManager.GetCutSceneMusicPosition(); +} + void cDMAudio::StopCutSceneMusic(void) { @@ -379,4 +427,4 @@ void cDMAudio::ShutUpPlayerTalking(uint8 state) { AudioManager.m_bIsPlayerShutUp = state; -} \ No newline at end of file +} diff --git a/src/audio/DMAudio.h b/src/audio/DMAudio.h index 2c10043..d541f6e 100644 --- a/src/audio/DMAudio.h +++ b/src/audio/DMAudio.h @@ -22,6 +22,7 @@ class cDMAudio void Initialise(void); void Terminate(void); void Service(void); + void SetStreamsPausedForWindowPause(bool paused); int32 CreateEntity(eAudioType type, void *UID); void DestroyEntity(int32 audioEntity); @@ -79,6 +80,9 @@ class cDMAudio void PreloadCutSceneMusic(uint32 track); void PlayPreloadedCutSceneMusic(void); + bool ResumeCutSceneMusicAfterPause(void); + bool IsCutSceneMusicPlaying(void); + int32 GetCutSceneMusicPosition(void); void StopCutSceneMusic(void); void PreloadMissionAudio(uint8 slot, Const char *missionAudio); diff --git a/src/audio/MusicManager.cpp b/src/audio/MusicManager.cpp index f2e3cfd..c6884a2 100644 --- a/src/audio/MusicManager.cpp +++ b/src/audio/MusicManager.cpp @@ -369,7 +369,15 @@ cMusicManager::Service() { case MUSICMODE_FRONTEND: ServiceFrontEndMode(); break; case MUSICMODE_GAME: ServiceGameMode(); break; - case MUSICMODE_CUTSCENE: SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, 1, 0); break; + case MUSICMODE_CUTSCENE: + if (AudioManager.m_nUserPause) + SampleManager.PauseStream(1, 0); + else { + if (AudioManager.m_nPreviousUserPause) + SampleManager.PauseStream(0, 0); + SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, 1, 0); + } + break; } } else @@ -1086,6 +1094,37 @@ cMusicManager::PlayPreloadedCutSceneMusic(void) SampleManager.StartPreloadedStreamedFile(0); } +bool +cMusicManager::ResumeCutSceneMusicAfterPause(void) +{ + if (!IsInitialised() || m_bDisabled || m_nMusicMode != MUSICMODE_CUTSCENE || m_nPlayingTrack == NO_TRACK) + return true; + + SampleManager.PauseStream(0, 0); + SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, 1, 0); + if (SampleManager.IsStreamPlaying(0)) + return true; + + SampleManager.StartPreloadedStreamedFile(0); + return SampleManager.IsStreamPlaying(0); +} + +bool +cMusicManager::IsCutSceneMusicPlaying(void) +{ + return IsInitialised() && !m_bDisabled && m_nMusicMode == MUSICMODE_CUTSCENE && + m_nPlayingTrack != NO_TRACK && SampleManager.IsStreamPlaying(0); +} + +int32 +cMusicManager::GetCutSceneMusicPosition(void) +{ + if (!IsCutSceneMusicPlaying()) + return -1; + + return SampleManager.GetStreamedFilePosition(0); +} + void cMusicManager::StopCutSceneMusic(void) { diff --git a/src/audio/MusicManager.h b/src/audio/MusicManager.h index 27456d7..8145a9a 100644 --- a/src/audio/MusicManager.h +++ b/src/audio/MusicManager.h @@ -71,6 +71,9 @@ class cMusicManager void PlayFrontEndTrack(uint32, uint8); void PreloadCutSceneMusic(uint32); void PlayPreloadedCutSceneMusic(void); + bool ResumeCutSceneMusicAfterPause(void); + bool IsCutSceneMusicPlaying(void); + int32 GetCutSceneMusicPosition(void); void StopCutSceneMusic(void); uint32 GetRadioInCar(void); void SetRadioInCar(uint32); diff --git a/src/audio/PoliceRadio.cpp b/src/audio/PoliceRadio.cpp index 227d27d..5521499 100644 --- a/src/audio/PoliceRadio.cpp +++ b/src/audio/PoliceRadio.cpp @@ -169,13 +169,12 @@ cAudioManager::ServicePoliceRadioChannel(uint8 wantedLevel) if (m_nUserPause != 0) { if (SampleManager.GetChannelUsedFlag(policeChannel)) SampleManager.StopChannel(policeChannel); - if (g_nMissionAudioSfx != NO_SAMPLE && bMissionAudioPhysicalPlayingStatus == 1 && - SampleManager.IsStreamPlaying(1)) { + if (g_nMissionAudioSfx != NO_SAMPLE && g_nMissionAudioPlayingStatus == 1) { SampleManager.PauseStream(1, 1); } } else { if (m_nPreviousUserPause && g_nMissionAudioSfx != NO_SAMPLE && - bMissionAudioPhysicalPlayingStatus == 1) { + g_nMissionAudioPlayingStatus == 1) { SampleManager.PauseStream(0, 1); } if (m_sPoliceRadioQueue.policeChannelTimer == 0) bChannelOpen = false; diff --git a/src/audio/oal/stream.cpp b/src/audio/oal/stream.cpp index 426d0e0..bee82b9 100644 --- a/src/audio/oal/stream.cpp +++ b/src/audio/oal/stream.cpp @@ -1168,14 +1168,21 @@ uint32 CStream::GetPosMS() { if ( !HasSource() ) return 0; if ( !IsOpened() ) return 0; - - ALint offset; - //alGetSourcei(m_alSource, AL_SAMPLE_OFFSET, &offset); - alGetSourcei(m_pAlSources[0], AL_BYTE_OFFSET, &offset); - return m_pSoundFile->Tell() - - m_pSoundFile->samples2ms(m_pSoundFile->GetBufferSamples() * (NUM_STREAMBUFFERS/2-1)) / m_pSoundFile->GetChannels() - + m_pSoundFile->samples2ms(offset/m_pSoundFile->GetSampleSize()) / m_pSoundFile->GetChannels(); + ALint offset = 0; + ALint buffersQueued = 0; + ALint buffersProcessed = 0; + alGetSourcei(m_pAlSources[0], AL_BYTE_OFFSET, &offset); + alGetSourcei(m_pAlSources[0], AL_BUFFERS_QUEUED, &buffersQueued); + alGetSourcei(m_pAlSources[0], AL_BUFFERS_PROCESSED, &buffersProcessed); + + // Tell() is at the end of the decoder queue, while the source is playing + // the first pending buffer. Account for every pending buffer, not just its tail. + uint32 bufferDuration = m_pSoundFile->samples2ms(m_pSoundFile->GetBufferSamples()) / m_pSoundFile->GetChannels(); + int64 position = (int64)m_pSoundFile->Tell() + - (int64)bufferDuration * Max(buffersQueued - buffersProcessed, 0) + + m_pSoundFile->samples2ms(offset / m_pSoundFile->GetSampleSize()); + return position > 0 ? (uint32)position : 0; } uint32 CStream::GetLengthMS() @@ -1301,8 +1308,27 @@ void CStream::SetPlay(bool state) void CStream::Start() { - if ( !HasSource() ) return; - if ( FillBuffers() != 0 ) + if ( !HasSource() || !IsOpened() ) return; + + if (m_bPaused) + SetPause(false); + if (IsPlaying()) + return; + + ALint buffersQueued = 0; + ALint buffersProcessed = 0; + alGetSourcei(m_pAlSources[0], AL_BUFFERS_QUEUED, &buffersQueued); + alGetSourcei(m_pAlSources[0], AL_BUFFERS_PROCESSED, &buffersProcessed); + if (buffersQueued > buffersProcessed) { + SetPlay(true); + return; + } + + if (buffersQueued != 0) { + SetPlay(false); + ClearBuffers(); + } + if (FillBuffers() != 0) SetPlay(true); } diff --git a/src/audio/sampman_miles.cpp b/src/audio/sampman_miles.cpp index 42ca433..f3529b7 100644 --- a/src/audio/sampman_miles.cpp +++ b/src/audio/sampman_miles.cpp @@ -2074,7 +2074,7 @@ cSampleManager::StartPreloadedStreamedFile(uint8 nStream) { if ( m_bInitialised ) { - if ( mp3Stream[nStream] ) + if ( mp3Stream[nStream] && AIL_stream_status(mp3Stream[nStream]) != SMP_PLAYING ) AIL_start_stream(mp3Stream[nStream]); } } diff --git a/src/audio/sampman_oal.cpp b/src/audio/sampman_oal.cpp index cc40dae..d587642 100644 --- a/src/audio/sampman_oal.cpp +++ b/src/audio/sampman_oal.cpp @@ -1724,10 +1724,7 @@ cSampleManager::StartPreloadedStreamedFile(uint8 nStream) if ( stream ) { - if ( stream->IsOpened() ) - { - stream->Start(); - } + stream->Start(); } } diff --git a/src/control/Script.cpp b/src/control/Script.cpp index f1a9f35..90fbc9d 100644 --- a/src/control/Script.cpp +++ b/src/control/Script.cpp @@ -1,5 +1,6 @@ #include "common.h" +#include "crossplatform.h" #include "Script.h" #include "ScriptCommands.h" @@ -41,6 +42,7 @@ #include "Ropes.h" #include "ColStore.h" #include "Fluff.h" +#include "Game.h" #include "GameLogic.h" #include "MBlur.h" #include "PedRoutes.h" @@ -137,6 +139,17 @@ int MissionSkipLevel; #endif +static bool +PauseScriptProcessingForWindowMinimized(void) +{ + if (!CGame::IsWindowPauseMenuActive() && !CTimer::GetIsWindowMinimizedPaused() && !psRefreshAndGetWindowMinimizedPause()) + return false; + + DMAudio.SetStreamsPausedForWindowPause(true); + DMAudio.Service(); + return true; +} + #ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT #define REGISTER_COMMAND(command, in, out, cond, ovrd, visual) { command, #command, in, out, cond, ovrd, visual } @@ -2365,6 +2378,9 @@ CRunningScript* CTheScripts::StartNewScript(uint32 ip) void CTheScripts::Process() { + if (PauseScriptProcessingForWindowMinimized()) + return; + if (CReplay::IsPlayingBack()) return; CommandsExecuted = 0; @@ -2469,6 +2485,9 @@ bool CTheScripts::IsPlayerOnAMission() void CRunningScript::Process() { + if (PauseScriptProcessingForWindowMinimized()) + return; + #ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT PrintToLog("\n\nProcessing script %s (id %d)\n\n", m_abScriptName, this - CTheScripts::ScriptsArray); #endif @@ -2496,6 +2515,9 @@ void CRunningScript::Process() int8 CRunningScript::ProcessOneCommand() { + if (PauseScriptProcessingForWindowMinimized()) + return 1; + int8 retval = -1; ++CTheScripts::CommandsExecuted; int32 command = (uint16)CTheScripts::Read2BytesFromScript(&m_nIp); @@ -4843,7 +4865,10 @@ CTheScripts::SwitchToMission(int32 mission) pScript->m_nWakeTime = 0; pScript->m_bDeatharrestExecuted = true; - while (!pScript->ProcessOneCommand()); + if (PauseScriptProcessingForWindowMinimized()) + return; + while (!pScript->ProcessOneCommand()) + ; CMessages::ClearMessages(); } diff --git a/src/core/Cam.cpp b/src/core/Cam.cpp index 24ce497..dda97cd 100644 --- a/src/core/Cam.cpp +++ b/src/core/Cam.cpp @@ -15,6 +15,7 @@ #include "Pad.h" #include "Frontend.h" #include "General.h" +#include "Game.h" #include "Timecycle.h" #include "Renderer.h" #include "Shadows.h" @@ -3518,6 +3519,9 @@ FindSplinePathPositionVector(CVector *out, float *spline, uint32 time, uint32 &m void CCam::Process_FlyBy(const CVector&, float, float, float) { + if (CGame::IsWindowPauseMenuActive() || CTimer::GetIsWindowMinimizedPaused()) + return; + float UpAngle = 0.0f; static float FirstFOVValue = 0.0f; static float PsuedoFOV; diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp index 41d80ed..5b057b8 100644 --- a/src/core/Camera.cpp +++ b/src/core/Camera.cpp @@ -1,7 +1,9 @@ #include "common.h" +#include "crossplatform.h" #include "main.h" #include "Draw.h" +#include "Game.h" #include "World.h" #include "Vehicle.h" #include "Train.h" @@ -253,6 +255,9 @@ CCamera::Init(void) void CCamera::Process(void) { + if (CGame::IsWindowPauseMenuActive() || CTimer::GetIsWindowMinimizedPaused() || psRefreshAndGetWindowMinimizedPause()) + return; + // static bool InterpolatorNotInitialised = true; // unused static float PlayerMinDist = 1.3f; static bool WasPreviouslyInterSyhonFollowPed = false; // only written diff --git a/src/core/Frontend.cpp b/src/core/Frontend.cpp index f30811b..9963268 100644 --- a/src/core/Frontend.cpp +++ b/src/core/Frontend.cpp @@ -573,7 +573,8 @@ CMenuManager::Initialise(void) CPad::StopPadsShaking(); if (!m_OnlySaveMenu) m_nCurrScreen = MENUPAGE_NONE; - DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + if (!CGame::IsWindowPauseMenuActive()) + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0); DMAudio.Service(); DMAudio.SetMusicMasterVolume(m_PrefsMusicVolume); @@ -3439,7 +3440,7 @@ CMenuManager::Process(void) ProcessDialogTimer(); #endif - if (TheCamera.GetScreenFadeStatus() != FADE_0) + if (TheCamera.GetScreenFadeStatus() != FADE_0 && !CGame::IsWindowPauseMenuActive()) return; InitialiseChangedLanguageSettings(); @@ -5462,7 +5463,7 @@ CMenuManager::ProcessFileActions() void CMenuManager::SwitchMenuOnAndOff() { - if (!TheCamera.m_WideScreenOn) { + if (!TheCamera.m_WideScreenOn || CGame::IsWindowPauseMenuActive()) { // Reminder: You need REGISTER_START_BUTTON defined to make it work. if ((CPad::GetPad(0)->GetStartJustDown() || CPad::GetPad(0)->GetEscapeJustDown()) @@ -5502,6 +5503,8 @@ CMenuManager::SwitchMenuOnAndOff() CPad::StopPadsShaking(); #endif } else { + bool wasWindowPauseMenu = CGame::IsWindowPauseMenuActive(); + psRestoreFocusAfterPause(); #ifdef EXTENDED_COLOURFILTER // we always expect CPostFX to be open CMBlur::BlurOn = true; @@ -5514,7 +5517,7 @@ CMenuManager::SwitchMenuOnAndOff() DoRWStuffEndOfFrame(); DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); DoRWStuffEndOfFrame(); - if (_InputMouseNeedsExclusive()) { + if (IsForegroundApp() && _InputMouseNeedsExclusive()) { _InputShutdownMouse(); _InputInitialiseMouse(true); } @@ -5548,6 +5551,8 @@ CMenuManager::SwitchMenuOnAndOff() #endif UnloadTextures(); CTimer::EndUserPause(); + if (wasWindowPauseMenu) + CGame::FinishWindowPauseMenu(); CTimer::Update(); m_OnlySaveMenu = false; } @@ -5600,7 +5605,10 @@ CMenuManager::UnloadTextures() DMAudio.StopFrontEndTrack(); DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0); - DMAudio.ChangeMusicMode(MUSICMODE_GAME); + // A forced focus-loss menu can overlay a cutscene. Keep its music mode so + // stream 0 remains preloaded and can resume when Continue closes the menu. + if (!CGame::IsWindowPauseMenuActive()) + DMAudio.ChangeMusicMode(MUSICMODE_GAME); if (m_bSpritesLoaded) { printf("REMOVE frontend\n"); int frontend = CTxdStore::FindTxdSlot("frontend1"); diff --git a/src/core/Game.cpp b/src/core/Game.cpp index 3915b61..70ae01d 100644 --- a/src/core/Game.cpp +++ b/src/core/Game.cpp @@ -1,6 +1,7 @@ #include "common.h" #include "platform.h" +#include "crossplatform.h" #include "Game.h" #include "main.h" #include "RwHelper.h" @@ -9,6 +10,7 @@ #include "Bridge.h" #include "CarCtrl.h" #include "CarGen.h" +#include "Camera.h" #include "CdStream.h" #include "Clock.h" #include "Clouds.h" @@ -69,6 +71,7 @@ #include "Streaming.h" #include "SurfaceTable.h" #include "TempColModels.h" +#include "Timer.h" #include "Timecycle.h" #include "TrafficLights.h" #include "Train.h" @@ -76,6 +79,7 @@ #include "User.h" #include "VisibilityPlugins.h" #include "WaterCannon.h" +#include "World.h" #include "WaterLevel.h" #include "Weapon.h" #include "WeaponEffects.h" @@ -120,6 +124,12 @@ bool8 CGame::VarUpdatePlayerCoords; int gameTxdSlot; +// This survives a transient fullscreen focus report until the player explicitly +// leaves the pause menu. It must not live in CMenuManager: that class has a +// fixed binary layout on the original PC menu path. +static bool WindowPauseMenuActive = false; +static bool WindowPauseStreamsNeedResume = false; + #ifdef SECUROM uint8 gameProcessPirateCheck = 0; #endif @@ -341,7 +351,9 @@ bool CGame::InitialiseOnceAfterRW(void) void CGame::FinalShutdown(void) -{ +{ + WindowPauseMenuActive = false; + WindowPauseStreamsNeedResume = false; CTxdStore::Shutdown(); CPedStats::Shutdown(); CdStreamShutdown(); @@ -349,6 +361,8 @@ CGame::FinalShutdown(void) bool CGame::Initialise(const char* datFile) { + WindowPauseMenuActive = false; + WindowPauseStreamsNeedResume = false; ResetLoadingScreenBar(); strcpy(aDatFile, datFile); @@ -536,8 +550,10 @@ bool CGame::Initialise(const char* datFile) #endif { CTheScripts::StartTestScript(); - CTheScripts::Process(); - TheCamera.Process(); + if (!IsWindowPauseMenuActive() && !psRefreshAndGetWindowMinimizedPause()) { + CTheScripts::Process(); + TheCamera.Process(); + } } LoadingScreen("Loading the Game", "Load scene", nil); @@ -672,6 +688,7 @@ void CGame::ReInitGameObjectVariables(void) POP_MEMID(); CTimer::Initialise(); + psRefreshFocusAfterPause(); CClock::Initialise(1000); CTheCarGenerators::Init(); CHeli::InitHelis(); @@ -695,8 +712,10 @@ void CGame::ReInitGameObjectVariables(void) { CCranes::InitCranes(); CTheScripts::StartTestScript(); - CTheScripts::Process(); - TheCamera.Process(); + if (!IsWindowPauseMenuActive() && !psRefreshAndGetWindowMinimizedPause()) { + CTheScripts::Process(); + TheCamera.Process(); + } CTrain::InitTrains(); CPlane::InitPlanes(); } @@ -741,10 +760,13 @@ void CGame::ShutDownForRestart(void) void CGame::InitialiseWhenRestarting(void) { + WindowPauseMenuActive = false; + WindowPauseStreamsNeedResume = false; CRect rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); CRGBA color(255, 255, 255, 255); CTimer::Initialise(); + psRefreshFocusAfterPause(); CSprite2d::SetRecipNearClip(); if (b_FoundRecentSavedGameWantToLoad || FrontEndMenuManager.m_bWantToLoad) @@ -797,6 +819,7 @@ void CGame::InitialiseWhenRestarting(void) ShutDownForRestart(); CTimer::Stop(); CTimer::Initialise(); + psRefreshFocusAfterPause(); FrontEndMenuManager.m_bWantToLoad = false; ReInitGameObjectVariables(); currLevel = LEVEL_GENERIC; @@ -817,7 +840,21 @@ void CGame::InitialiseWhenRestarting(void) void CGame::Process(void) { + if (CTimer::GetIsWindowMinimizedPaused() || psRefreshAndGetWindowMinimizedPause()) { + DMAudio.SetStreamsPausedForWindowPause(true); + DMAudio.Service(); + return; + } + CPad::UpdatePads(); + if (IsWindowPauseMenuActive()) { + CTimer::StartUserPause(); + DMAudio.SetStreamsPausedForWindowPause(true); + DMAudio.Service(); + FrontEndMenuManager.Process(); + return; + } + #ifdef USE_CUSTOM_ALLOCATOR ProcessTidyUpMemory(); #endif @@ -1255,11 +1292,49 @@ void CGame::ProcessTidyUpMemory(void) void CGame::InitAfterFocusLoss() { + if (WindowPauseMenuActive || FrontEndMenuManager.m_bGameNotLoaded || FrontEndMenuManager.m_bMenuActive) + return; + + WindowPauseMenuActive = true; FrontEndMenuManager.m_nPrefsAudio3DProviderIndex = FrontEndMenuManager.m_lastWorking3DAudioProvider; - DMAudio.SetCurrent3DProvider(FrontEndMenuManager.m_lastWorking3DAudioProvider); + if (DMAudio.GetCurrent3DProviderIndex() != FrontEndMenuManager.m_lastWorking3DAudioProvider) + DMAudio.SetCurrent3DProvider(FrontEndMenuManager.m_lastWorking3DAudioProvider); + CTimer::StartUserPause(); + FrontEndMenuManager.m_bStartUpFrontEndRequested = true; +} + +bool +CGame::IsWindowPauseMenuActive() +{ + return WindowPauseMenuActive; +} + +void +CGame::FinishWindowPauseMenu() +{ + if (!WindowPauseMenuActive) + return; - if (!FrontEndMenuManager.m_bGameNotLoaded && !FrontEndMenuManager.m_bMenuActive) - FrontEndMenuManager.m_bStartUpFrontEndRequested = true; + WindowPauseMenuActive = false; + WindowPauseStreamsNeedResume = true; + CTimer::StartUserPause(); + ResumeWindowPauseMenuAfterFocusRestore(); +} + +void +CGame::ResumeWindowPauseMenuAfterFocusRestore() +{ + if (WindowPauseMenuActive || !WindowPauseStreamsNeedResume || CTimer::GetIsWindowMinimizedPaused() || psRefreshAndGetWindowMinimizedPause()) + return; + + // A focus-loss pause mutes the backend. Restore the user-selected masters + // here as well, since Continue is the only point that releases its streams. + DMAudio.SetMusicMasterVolume(FrontEndMenuManager.m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(FrontEndMenuManager.m_PrefsSfxVolume); + CTimer::EndUserPause(); + DMAudio.SetStreamsPausedForWindowPause(false); + DMAudio.Service(); + WindowPauseStreamsNeedResume = false; } bool diff --git a/src/core/Game.h b/src/core/Game.h index 9c4bdf1..53d8a24 100644 --- a/src/core/Game.h +++ b/src/core/Game.h @@ -68,6 +68,9 @@ class CGame static void Process(void); static void InitAfterFocusLoss(void); + static bool IsWindowPauseMenuActive(void); + static void FinishWindowPauseMenu(void); + static void ResumeWindowPauseMenuAfterFocusRestore(void); static bool IsInInterior(void) { return currArea != AREA_MAIN_MAP; } static bool CanSeeWaterFromCurrArea(void); diff --git a/src/core/MenuScreensCustom.cpp b/src/core/MenuScreensCustom.cpp index 0a47553..68e6f26 100644 --- a/src/core/MenuScreensCustom.cpp +++ b/src/core/MenuScreensCustom.cpp @@ -255,7 +255,25 @@ wchar* MultiSamplingDraw(bool *disabled, bool userHovering) { const char* screenModes[] = { "FED_FLS", "FED_WND", "FED_BDL" }; void ScreenModeAfterChange(int8 before, int8 after) { + bool useMouse = FrontEndMenuManager.m_bShowMouse; + int32 selectedOption = FrontEndMenuManager.m_nCurrOption; _psSelectScreenVM(FrontEndMenuManager.m_nPrefsVideoMode); // apply same resolution + // Recreating the video mode can emit a synthetic focus transition even + // though the settings menu is still the foreground window. + psRestoreFocusAfterPause(); + CMenuManager::CentreMousePointer(); + // Ignore the synthetic mouse move caused by recreating the video mode. + int32 mouseX = SCREEN_WIDTH / 2; + int32 mouseY = SCREEN_HEIGHT / 2; + FrontEndMenuManager.m_nMouseTempPosX = mouseX; + FrontEndMenuManager.m_nMouseTempPosY = mouseY; + FrontEndMenuManager.m_nMousePosX = mouseX; + FrontEndMenuManager.m_nMousePosY = mouseY; + FrontEndMenuManager.m_nMouseOldPosX = mouseX; + FrontEndMenuManager.m_nMouseOldPosY = mouseY; + FrontEndMenuManager.m_bShowMouse = useMouse; + // psSelectDevice resets this while recreating the RenderWare video mode. + FrontEndMenuManager.m_nCurrOption = selectedOption; FrontEndMenuManager.SetHelperText(0); } diff --git a/src/core/Timer.cpp b/src/core/Timer.cpp index 72272c9..45b2c3f 100644 --- a/src/core/Timer.cpp +++ b/src/core/Timer.cpp @@ -43,13 +43,16 @@ double frameTime; void CTimer::Initialise(void) { debug("Initialising CTimer...\n"); + // A loading/restart path may recreate the timer while the game is minimized. + // Keep the platform pause alive so the next frame cannot advance silently. + bool wasWindowMinimizedPaused = m_WindowMinimizedPause; ms_fTimeScale = 1.0f; ms_fTimeStep = 1.0f; suspendDepth = 0; m_UserPause = false; m_CodePause = false; - m_WindowMinimizedPause = false; + m_WindowMinimizedPause = wasWindowMinimizedPaused; m_snTimeInMillisecondsNonClipped = 0; m_snPreviousTimeInMilliseconds = 0; m_snTimeInMilliseconds = 1; @@ -75,6 +78,9 @@ void CTimer::Initialise(void) m_FrameCounter = 0; DMAudio.ResetTimers(m_snPreviousTimeInMilliseconds); + + if (m_WindowMinimizedPause) + Suspend(); debug("CTimer ready\n"); } @@ -87,6 +93,26 @@ void CTimer::Shutdown(void) void CTimer::Update(void) { m_snPreviousTimeInMilliseconds = m_snTimeInMilliseconds; + + // Window pause freezes the complete game clock, including pause-mode time and + // the frame counter. Scripted cutscenes use both of those independently of + // the normal time step. + if (m_WindowMinimizedPause) { +#ifdef _WIN32 + if ((double)_nCyclesPerMS != 0.0) { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + _oldPerfCounter = pc; + } else +#endif + { + oldPcTimer = RsTimer(); + } + + ms_fTimeStep = 0.0f; + ms_fTimeStepNonClipped = 0.0f; + return; + } #ifdef _WIN32 if ( (double)_nCyclesPerMS != 0.0 ) @@ -94,11 +120,11 @@ void CTimer::Update(void) LARGE_INTEGER pc; QueryPerformanceCounter(&pc); - int32 updInCycles = (pc.LowPart - _oldPerfCounter.LowPart); // & 0x7FFFFFFF; pointless + int64 updInCycles = pc.QuadPart - _oldPerfCounter.QuadPart; _oldPerfCounter = pc; - float updInCyclesScaled = GetIsPaused() ? updInCycles : updInCycles * ms_fTimeScale; + double updInCyclesScaled = GetIsPaused() ? (double)updInCycles : (double)updInCycles * ms_fTimeScale; // We need that real frame time to fix transparent menu bug. #ifndef FIX_HIGH_FPS_BUGS_ON_FRONTEND @@ -181,7 +207,7 @@ void CTimer::Suspend(void) void CTimer::Resume(void) { - if ( --suspendDepth != 0 ) + if (suspendDepth == 0 || --suspendDepth != 0) return; #ifdef _WIN32 @@ -190,7 +216,7 @@ void CTimer::Resume(void) LARGE_INTEGER pc; QueryPerformanceCounter(&pc); - _oldPerfCounter.LowPart += pc.LowPart - perfSuspendCounter.LowPart; + _oldPerfCounter.QuadPart += pc.QuadPart - perfSuspendCounter.QuadPart; } else #endif @@ -214,7 +240,7 @@ uint32 CTimer::GetCurrentTimeInCycles(void) { LARGE_INTEGER pc; QueryPerformanceCounter(&pc); - return (pc.LowPart - _oldPerfCounter.LowPart); // & 0x7FFFFFFF; pointless + return (uint32)(pc.QuadPart - _oldPerfCounter.QuadPart); } else #endif diff --git a/src/core/World.cpp b/src/core/World.cpp index fb854c7..84c60b6 100644 --- a/src/core/World.cpp +++ b/src/core/World.cpp @@ -7,6 +7,7 @@ #include "EventList.h" #include "Explosion.h" #include "Fire.h" +#include "Game.h" #include "Garages.h" #include "Glass.h" #include "Messages.h" @@ -1921,6 +1922,9 @@ CWorld::RemoveStaticObjects() void CWorld::Process(void) { + if (CGame::IsWindowPauseMenuActive() || CTimer::GetIsWindowMinimizedPaused()) + return; + if(!(CTimer::GetFrameCounter() & 63)) CReferences::PruneAllReferencesInWorld(); if(bProcessCutsceneOnly) { diff --git a/src/core/main.cpp b/src/core/main.cpp index ab4a4d0..42f595e 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -10,6 +10,7 @@ #endif #include "main.h" +#include "crossplatform.h" #include "CdStream.h" #include "General.h" #include "RwHelper.h" @@ -578,11 +579,25 @@ ResetLoadingScreenBar() NumberOfChunksLoaded = 0.0f; } +static bool +PauseLoadingScreenForWindowMinimized(void) +{ + if (!psRefreshAndGetWindowMinimizedPause()) + return false; + + DMAudio.SetStreamsPausedForWindowPause(true); + DMAudio.Service(); + return true; +} + void LoadingScreen(const char *str1, const char *str2, const char *splashscreen) { CSprite2d *splash; + if (PauseLoadingScreenForWindowMinimized()) + return; + #ifdef DISABLE_LOADING_SCREEN if (str1 && str2) return; @@ -669,6 +684,9 @@ LoadingIslandScreen(const char *levelName) { CSprite2d *splash; + if (PauseLoadingScreenForWindowMinimized()) + return; + splash = LoadSplash(nil); if(!DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) return; @@ -1452,8 +1470,17 @@ Render2dStuffAfterFade(void) void Idle(void *arg) { + bool windowPause = psRefreshAndGetWindowMinimizedPause(); + if (!windowPause) + CGame::ResumeWindowPauseMenuAfterFocusRestore(); CTimer::Update(); + if (windowPause || CTimer::GetIsWindowMinimizedPaused()) { + DMAudio.SetStreamsPausedForWindowPause(true); + DMAudio.Service(); + return; + } + tbInit(); CSprite2d::InitPerFrame(); @@ -1620,7 +1647,15 @@ void FrontendIdle(void) { CDraw::CalculateAspectRatio(); + bool windowPause = psRefreshAndGetWindowMinimizedPause(); CTimer::Update(); + + if (windowPause || CTimer::GetIsWindowMinimizedPaused()) { + DMAudio.SetStreamsPausedForWindowPause(true); + DMAudio.Service(); + return; + } + CSprite2d::SetRecipNearClip(); // this should be on InitialiseRenderWare according to PS2 asm. seems like a bug fix CSprite2d::InitPerFrame(); CFont::InitPerFrame(); diff --git a/src/save/GenericGameStorage.cpp b/src/save/GenericGameStorage.cpp index 96a2cc0..fa817c0 100644 --- a/src/save/GenericGameStorage.cpp +++ b/src/save/GenericGameStorage.cpp @@ -474,7 +474,8 @@ DoGameSpecificStuffAfterSucessLoad() StillToFadeOut = true; JustLoadedDontFadeInYet = true; TheCamera.Fade(0.0f, 0); - CTheScripts::Process(); + if (!psRefreshAndGetWindowMinimizedPause()) + CTheScripts::Process(); } bool diff --git a/src/skel/crossplatform.h b/src/skel/crossplatform.h index dbfd16f..8e9859e 100644 --- a/src/skel/crossplatform.h +++ b/src/skel/crossplatform.h @@ -99,6 +99,9 @@ enum eGameState extern RwUInt32 gGameState; RwBool IsForegroundApp(); +RwBool psRefreshAndGetWindowMinimizedPause(void); +void psRefreshFocusAfterPause(void); +void psRestoreFocusAfterPause(void); #ifndef MAX_PATH #if !defined _WIN32 || defined __MINGW32__ diff --git a/src/skel/glfw/glfw.cpp b/src/skel/glfw/glfw.cpp index a2d24cb..4e0e30a 100644 --- a/src/skel/glfw/glfw.cpp +++ b/src/skel/glfw/glfw.cpp @@ -43,6 +43,7 @@ long _dwOperatingSystemVersion; #include "ControllerConfig.h" #include "Frontend.h" #include "Game.h" +#include "CutsceneMgr.h" #include "PCSave.h" #include "MemoryCard.h" #include "Sprite2d.h" @@ -74,6 +75,8 @@ rw::EngineOpenParams openParams; static RwBool ForegroundApp = TRUE; static RwBool WindowIconified = FALSE; static RwBool WindowFocused = TRUE; +static RwBool WindowPauseLatched = FALSE; +static uint8 WindowActiveFrames = 0; static RwBool WindowAudioSuspended = FALSE; static RwBool RwInitialised = FALSE; @@ -98,65 +101,146 @@ static psGlobalType PsGlobal; #define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) static RwBool -IsFullscreenLikeWindowMode(void) +IsWindowMinimizedPauseActive(void) { -#ifdef IMPROVED_VIDEOMODE - return PSGLOBAL(fullScreen) || FrontEndMenuManager.m_nPrefsWindowed == WINDOWMODE_BORDERLESS; -#else - return PSGLOBAL(fullScreen); -#endif + return WindowPauseLatched || WindowIconified || !WindowFocused; } +#ifdef _WIN32 static RwBool -IsWindowMinimizedPauseActive(void) +IsNativeWindowMinimized(HWND window) { - return WindowIconified || (IsFullscreenLikeWindowMode() && !WindowFocused); + if (window == nil) + return TRUE; + + WINDOWPLACEMENT placement = {}; + placement.length = sizeof(placement); + return IsIconic(window) || (GetWindowPlacement(window, &placement) && placement.showCmd == SW_SHOWMINIMIZED); } +#endif static void -SetWindowAudioSuspended(RwBool suspend) +ReadWindowActivityState(void) { - if (WindowAudioSuspended == suspend) + GLFWwindow *window = PSGLOBAL(window); + if (window == nil) + return; + + WindowIconified = glfwGetWindowAttrib(window, GLFW_ICONIFIED); + WindowFocused = glfwGetWindowAttrib(window, GLFW_FOCUSED); + +#ifdef _WIN32 + // GLFW can briefly report a fullscreen window as focused while it is being + // minimized. The native foreground window is authoritative on Windows. + HWND nativeWindow = glfwGetWin32Window(window); + if (nativeWindow != nil) { + WindowIconified = IsNativeWindowMinimized(nativeWindow); + WindowFocused = !WindowIconified && GetForegroundWindow() == nativeWindow; + } +#endif +} + +static void +SetWindowAudioSuspended(RwBool suspend, RwBool force = FALSE) +{ + if (!force && WindowAudioSuspended == suspend) return; WindowAudioSuspended = suspend; if (suspend) { _InputShutdownMouse(); + DMAudio.SetStreamsPausedForWindowPause(true); DMAudio.SetMusicMasterVolume(0); DMAudio.SetEffectsMasterVolume(0); DMAudio.Service(); - if (PSGLOBAL(fullScreen)) - RsEventHandler(rsACTIVATE, (void *)FALSE); } else { - if (PSGLOBAL(fullScreen)) - RsEventHandler(rsACTIVATE, (void *)TRUE); - _InputInitialiseMouse(!FrontEndMenuManager.m_bMenuActive && _InputMouseNeedsExclusive()); + if (CGame::IsWindowPauseMenuActive()) + _InputInitialiseMouse(false); + else + _InputInitialiseMouse(!FrontEndMenuManager.m_bMenuActive && _InputMouseNeedsExclusive()); DMAudio.SetMusicMasterVolume(FrontEndMenuManager.m_PrefsMusicVolume); DMAudio.SetEffectsMasterVolume(FrontEndMenuManager.m_PrefsSfxVolume); + if (!CGame::IsWindowPauseMenuActive()) + DMAudio.SetStreamsPausedForWindowPause(false); + DMAudio.Service(); } } static void UpdateWindowMinimizedPause(void) { - RwBool pause = IsWindowMinimizedPauseActive(); - CTimer::SetWindowMinimizedPause(pause); - if (pause) - { - ForegroundApp = FALSE; - SetWindowAudioSuspended(TRUE); + RwBool windowPause = IsWindowMinimizedPauseActive(); + RwBool wasWindowPause = CTimer::GetIsWindowMinimizedPaused(); + + if (!windowPause) { + CTimer::SetWindowMinimizedPause(false); + ForegroundApp = TRUE; + if (WindowAudioSuspended || wasWindowPause) + SetWindowAudioSuspended(FALSE, TRUE); + return; } + + CTimer::SetWindowMinimizedPause(true); + CGame::InitAfterFocusLoss(); + CCutsceneMgr::RecordWindowPause(); + ForegroundApp = FALSE; + SetWindowAudioSuspended(TRUE); } -static void -RefreshWindowActivityState(void) +static RwBool +RefreshWindowActivityState(RwBool allowClearPauseLatch = FALSE) { - WindowIconified = glfwGetWindowAttrib(PSGLOBAL(window), GLFW_ICONIFIED); - WindowFocused = glfwGetWindowAttrib(PSGLOBAL(window), GLFW_FOCUSED); + ReadWindowActivityState(); + if (WindowIconified || !WindowFocused) { + WindowPauseLatched = TRUE; + WindowActiveFrames = 0; + } else if (allowClearPauseLatch && WindowPauseLatched) { + if (++WindowActiveFrames >= 2) { + WindowPauseLatched = FALSE; + WindowActiveFrames = 0; + } + } else { + WindowActiveFrames = 0; + } UpdateWindowMinimizedPause(); + return IsWindowMinimizedPauseActive(); +} + +RwBool +psRefreshAndGetWindowMinimizedPause(void) +{ + return RefreshWindowActivityState(TRUE); +} + +void +psRefreshFocusAfterPause(void) +{ + psRefreshAndGetWindowMinimizedPause(); +} + +void +psRestoreFocusAfterPause(void) +{ + ReadWindowActivityState(); + if (!WindowIconified && WindowFocused) + WindowPauseLatched = FALSE; + else + WindowPauseLatched = TRUE; + WindowActiveFrames = 0; + + if (IsWindowMinimizedPauseActive()) { + UpdateWindowMinimizedPause(); + return; + } + + RwBool wasWindowPause = CTimer::GetIsWindowMinimizedPaused(); + CTimer::SetWindowMinimizedPause(false); + ForegroundApp = TRUE; + if (WindowAudioSuspended || wasWindowPause) + SetWindowAudioSuspended(FALSE, TRUE); } size_t _dwMemAvailPhys; @@ -248,9 +332,8 @@ psCameraBeginUpdate(RwCamera *camera) { if ( !RwCameraBeginUpdate(Scene.camera) ) { - ForegroundApp = FALSE; + WindowPauseLatched = TRUE; RefreshWindowActivityState(); - SetWindowAudioSuspended(TRUE); return FALSE; } @@ -406,6 +489,8 @@ psInitialize(void) PsGlobal.cursorIsInWindow = FALSE; WindowFocused = TRUE; WindowIconified = FALSE; + WindowPauseLatched = FALSE; + WindowActiveFrames = 0; WindowAudioSuspended = FALSE; PsGlobal.joy1id = -1; @@ -684,8 +769,8 @@ RwBool IsForegroundApp() #ifdef IMPROVED_VIDEOMODE -static void -GetBorderlessMonitorInfo(int *x, int *y, int *width, int *height) +static GLFWmonitor * +GetCurrentMonitor(void) { int numMonitors; GLFWmonitor **monitors = glfwGetMonitors(&numMonitors); @@ -695,6 +780,13 @@ GetBorderlessMonitorInfo(int *x, int *y, int *width, int *height) monitor = monitors[GcurSel]; if (monitor == nil) monitor = glfwGetPrimaryMonitor(); + return monitor; +} + +static void +GetBorderlessMonitorInfo(int *x, int *y, int *width, int *height) +{ + GLFWmonitor *monitor = GetCurrentMonitor(); if (monitor != nil) { const GLFWvidmode *mode = glfwGetVideoMode(monitor); @@ -712,6 +804,22 @@ GetBorderlessMonitorInfo(int *x, int *y, int *width, int *height) *width = FrontEndMenuManager.m_nPrefsWidth; *height = FrontEndMenuManager.m_nPrefsHeight; } + +static void +GetWindowedMonitorWorkArea(int *x, int *y, int *width, int *height) +{ + GLFWmonitor *monitor = GetCurrentMonitor(); + if (monitor != nil) { + glfwGetMonitorWorkarea(monitor, x, y, width, height); + if (*width > 0 && *height > 0) + return; + } + + *x = 0; + *y = 0; + *width = FrontEndMenuManager.m_nPrefsWidth; + *height = FrontEndMenuManager.m_nPrefsHeight; +} #endif /* @@ -1110,6 +1218,9 @@ void psPostRWinit(void) if(!(vm.flags & rwVIDEOMODEEXCLUSIVE)) { #ifdef IMPROVED_VIDEOMODE + // Do not retain an always-on-top state after switching away from + // exclusive fullscreen on platforms which support floating windows. + glfwSetWindowAttrib(PSGLOBAL(window), GLFW_FLOATING, GLFW_FALSE); if (FrontEndMenuManager.m_nPrefsWindowed == WINDOWMODE_BORDERLESS) { int x, y, width, height; GetBorderlessMonitorInfo(&x, &y, &width, &height); @@ -1120,6 +1231,19 @@ void psPostRWinit(void) } else { glfwSetWindowAttrib(PSGLOBAL(window), GLFW_DECORATED, GLFW_TRUE); glfwSetWindowSize(PSGLOBAL(window), RsGlobal.maximumWidth, RsGlobal.maximumHeight); + + int workX, workY, workWidth, workHeight; + int frameLeft, frameTop, frameRight, frameBottom; + GetWindowedMonitorWorkArea(&workX, &workY, &workWidth, &workHeight); + glfwGetWindowFrameSize(PSGLOBAL(window), &frameLeft, &frameTop, &frameRight, &frameBottom); + + int windowWidth = RsGlobal.maximumWidth + frameLeft + frameRight; + int windowHeight = RsGlobal.maximumHeight + frameTop + frameBottom; + int spaceX = workWidth - windowWidth; + int spaceY = workHeight - windowHeight; + glfwSetWindowPos(PSGLOBAL(window), workX + (spaceX > 0 ? spaceX / 2 : 0), + workY + (spaceY > 0 ? spaceY / 2 : 0)); + glfwMaximizeWindow(PSGLOBAL(window)); } #else glfwSetWindowSize(PSGLOBAL(window), RsGlobal.maximumWidth, RsGlobal.maximumHeight); @@ -1445,7 +1569,8 @@ void resizeCB(GLFWwindow* window, int width, int height) { if (RwInitialised && gGameState == GS_PLAYING_GAME) { - RsEventHandler(rsIDLE, (void *)TRUE); + if (!psRefreshAndGetWindowMinimizedPause()) + RsEventHandler(rsIDLE, (void *)TRUE); } if (RwInitialised && height > 0 && width > 0) { @@ -1906,14 +2031,28 @@ cursorEnterCB(GLFWwindow* window, int entered) { void windowFocusCB(GLFWwindow* window, int focused) { - WindowFocused = !!focused; - UpdateWindowMinimizedPause(); + if (!focused) { + WindowFocused = FALSE; + WindowPauseLatched = TRUE; + WindowActiveFrames = 0; + UpdateWindowMinimizedPause(); + return; + } + + RefreshWindowActivityState(TRUE); } void windowIconifyCB(GLFWwindow* window, int iconified) { - WindowIconified = !!iconified; - UpdateWindowMinimizedPause(); + if (iconified) { + WindowIconified = TRUE; + WindowPauseLatched = TRUE; + WindowActiveFrames = 0; + UpdateWindowMinimizedPause(); + return; + } + + RefreshWindowActivityState(TRUE); } /* @@ -2260,6 +2399,9 @@ main(int argc, char *argv[]) case GS_INIT_ONCE: { + if (psRefreshAndGetWindowMinimizedPause()) + break; + //CoUninitialize(); #ifdef PS2_MENU @@ -2288,6 +2430,9 @@ main(int argc, char *argv[]) #ifndef PS2_MENU case GS_INIT_FRONTEND: { + if (psRefreshAndGetWindowMinimizedPause()) + break; + LoadingScreen(nil, nil, "loadsc0"); // LoadingScreen(nil, nil, "loadsc0"); // duplicate @@ -2309,9 +2454,15 @@ main(int argc, char *argv[]) case GS_FRONTEND: { + if (psRefreshAndGetWindowMinimizedPause()) + break; + if(!WindowIconified) RsEventHandler(rsFRONTENDIDLE, nil); + if (psRefreshAndGetWindowMinimizedPause()) + break; + #ifdef PS2_MENU if ( !FrontEndMenuManager.m_bMenuActive || TheMemoryCard.m_bWantToLoad ) #else @@ -2339,6 +2490,9 @@ main(int argc, char *argv[]) case GS_INIT_PLAYING_GAME: { + if (psRefreshAndGetWindowMinimizedPause()) + break; + #ifdef PS2_MENU CGame::Initialise("DATA\\GTA3.DAT"); @@ -2373,6 +2527,9 @@ main(int argc, char *argv[]) case GS_PLAYING_GAME: { + if (psRefreshAndGetWindowMinimizedPause()) + break; + float ms = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); if ( RwInitialised ) { @@ -2385,17 +2542,18 @@ main(int argc, char *argv[]) } else { - if ( RwCameraBeginUpdate(Scene.camera) ) + if (!psRefreshAndGetWindowMinimizedPause()) { - RwCameraEndUpdate(Scene.camera); - RefreshWindowActivityState(); - if (!CTimer::GetIsWindowMinimizedPaused()) + ForegroundApp = TRUE; + SetWindowAudioSuspended(FALSE, TRUE); + } + else + { + if ( RwCameraBeginUpdate(Scene.camera) ) { - ForegroundApp = TRUE; - SetWindowAudioSuspended(FALSE); + RwCameraEndUpdate(Scene.camera); } } - } } diff --git a/src/skel/win/win.cpp b/src/skel/win/win.cpp index db57a43..7b0031b 100644 --- a/src/skel/win/win.cpp +++ b/src/skel/win/win.cpp @@ -57,6 +57,8 @@ static RwBool ForegroundApp = TRUE; static RwBool WindowMinimized = FALSE; static RwBool WindowActive = TRUE; +static RwBool WindowPauseLatched = FALSE; +static uint8 WindowActiveFrames = 0; static RwBool WindowAudioSuspended = FALSE; static RwBool RwInitialised = FALSE; @@ -94,39 +96,61 @@ static psGlobalType PsGlobal; #include "ControllerConfig.h" #include "Frontend.h" #include "Game.h" +#include "CutsceneMgr.h" #include "PCSave.h" #include "AnimViewer.h" #include "MemoryMgr.h" +static RwBool NativeCursorHidden = FALSE; + +static void +SetNativeCursorHidden(RwBool hidden) +{ + if (NativeCursorHidden == hidden) + return; + + if (hidden) { + while (ShowCursor(FALSE) >= 0) {} + } else { + while (ShowCursor(TRUE) < 0) {} + } + NativeCursorHidden = hidden; +} + static void ReleaseWindowMouseFocus(void) { + ReleaseCapture(); + ClipCursor(nil); + SetNativeCursorHidden(FALSE); if (PSGLOBAL(mouse) != nil) PSGLOBAL(mouse)->Unacquire(); } static void -SetWindowAudioSuspended(RwBool suspend) +SetWindowAudioSuspended(RwBool suspend, RwBool force = FALSE) { - if (WindowAudioSuspended == suspend) + if (!force && WindowAudioSuspended == suspend) return; WindowAudioSuspended = suspend; if (suspend) { ReleaseWindowMouseFocus(); + DMAudio.SetStreamsPausedForWindowPause(true); DMAudio.SetMusicMasterVolume(0); DMAudio.SetEffectsMasterVolume(0); DMAudio.Service(); - if (PSGLOBAL(fullScreen)) - RsEventHandler(rsACTIVATE, (void *)FALSE); } else { - if (PSGLOBAL(fullScreen)) - RsEventHandler(rsACTIVATE, (void *)TRUE); + if (CGame::IsWindowPauseMenuActive() && PSGLOBAL(mouse) != nil) + PSGLOBAL(mouse)->Acquire(); DMAudio.SetMusicMasterVolume(FrontEndMenuManager.m_PrefsMusicVolume); DMAudio.SetEffectsMasterVolume(FrontEndMenuManager.m_PrefsSfxVolume); + if (!CGame::IsWindowPauseMenuActive()) + DMAudio.SetStreamsPausedForWindowPause(false); + DMAudio.Service(); } } @@ -140,25 +164,103 @@ IsFullscreenLikeWindowMode(void) #endif } +static RwBool +IsWindowMinimizedPauseActive(void) +{ + return WindowPauseLatched || WindowMinimized || !WindowActive; +} + +static RwBool +IsNativeWindowMinimized(HWND window) +{ + if (window == nil) + return TRUE; + + WINDOWPLACEMENT placement = {}; + placement.length = sizeof(placement); + return IsIconic(window) || (GetWindowPlacement(window, &placement) && placement.showCmd == SW_SHOWMINIMIZED); +} + +static void +ReadWindowActivityState(void) +{ + HWND currentWindow = PSGLOBAL(window); + WindowMinimized = IsNativeWindowMinimized(currentWindow); + WindowActive = currentWindow != nil && !WindowMinimized && GetForegroundWindow() == currentWindow; +} + static void UpdateWindowMinimizedPause(void) { - RwBool pause = WindowMinimized || (IsFullscreenLikeWindowMode() && !WindowActive); - CTimer::SetWindowMinimizedPause(pause); - if (pause) - { - ForegroundApp = FALSE; - SetWindowAudioSuspended(TRUE); + RwBool windowPause = IsWindowMinimizedPauseActive(); + RwBool wasWindowPause = CTimer::GetIsWindowMinimizedPaused(); + + if (!windowPause) { + CTimer::SetWindowMinimizedPause(false); + ForegroundApp = TRUE; + if (WindowAudioSuspended || wasWindowPause) + SetWindowAudioSuspended(FALSE, TRUE); + return; } + + CTimer::SetWindowMinimizedPause(true); + CGame::InitAfterFocusLoss(); + CCutsceneMgr::RecordWindowPause(); + ForegroundApp = FALSE; + SetWindowAudioSuspended(TRUE); } -static void -RefreshWindowActivityState(void) +static RwBool +RefreshWindowActivityState(RwBool allowClearPauseLatch = FALSE) { - HWND currentWindow = PSGLOBAL(window); - WindowMinimized = IsIconic(currentWindow); - WindowActive = GetForegroundWindow() == currentWindow || GetActiveWindow() == currentWindow; + ReadWindowActivityState(); + if (WindowMinimized || !WindowActive) { + WindowPauseLatched = TRUE; + WindowActiveFrames = 0; + } else if (allowClearPauseLatch && WindowPauseLatched) { + if (++WindowActiveFrames >= 2) { + WindowPauseLatched = FALSE; + WindowActiveFrames = 0; + } + } else { + WindowActiveFrames = 0; + } UpdateWindowMinimizedPause(); + return IsWindowMinimizedPauseActive(); +} + +RwBool +psRefreshAndGetWindowMinimizedPause(void) +{ + return RefreshWindowActivityState(TRUE); +} + +void +psRefreshFocusAfterPause(void) +{ + psRefreshAndGetWindowMinimizedPause(); +} + +void +psRestoreFocusAfterPause(void) +{ + ReadWindowActivityState(); + if (!WindowMinimized && WindowActive) + WindowPauseLatched = FALSE; + else + WindowPauseLatched = TRUE; + WindowActiveFrames = 0; + + if (IsWindowMinimizedPauseActive()) { + UpdateWindowMinimizedPause(); + return; + } + + RwBool wasWindowPause = CTimer::GetIsWindowMinimizedPaused(); + CTimer::SetWindowMinimizedPause(false); + ForegroundApp = TRUE; + if (WindowAudioSuspended || wasWindowPause) + SetWindowAudioSuspended(FALSE, TRUE); } #ifdef PS2_MENU @@ -287,10 +389,10 @@ psCameraBeginUpdate(RwCamera *camera) { if ( !RwCameraBeginUpdate(Scene.camera) ) { - ForegroundApp = FALSE; WindowActive = FALSE; + WindowMinimized = IsIconic(PSGLOBAL(window)); + WindowPauseLatched = TRUE; UpdateWindowMinimizedPause(); - SetWindowAudioSuspended(TRUE); return FALSE; } @@ -695,6 +797,8 @@ psInitialize(void) PsGlobal.fullScreen = FALSE; WindowMinimized = FALSE; WindowActive = TRUE; + WindowPauseLatched = FALSE; + WindowActiveFrames = 0; WindowAudioSuspended = FALSE; PsGlobal.dinterface = nil; @@ -1041,17 +1145,38 @@ MainWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) { case WM_SETCURSOR: { - ShowCursor(FALSE); - + if (LOWORD(lParam) != HTCLIENT) { + SetNativeCursorHidden(FALSE); + return DefWindowProc(window, message, wParam, lParam); + } + + SetNativeCursorHidden(TRUE); SetCursor(nil); - - break; // is this correct ? + return TRUE; + } + + case WM_SYSCOMMAND: + { + if ((wParam & 0xFFF0) == SC_MINIMIZE) { + // In exclusive fullscreen, WM_SIZE can arrive after the game has + // already run another frame. Latch the pause before Windows minimizes it. + WindowMinimized = TRUE; + WindowActive = FALSE; + WindowPauseLatched = TRUE; + WindowActiveFrames = 0; + UpdateWindowMinimizedPause(); + } + break; } case WM_SIZE: { RwRect r; - WindowMinimized = wParam == SIZE_MINIMIZED || IsIconic(window); + WindowMinimized = wParam == SIZE_MINIMIZED || IsNativeWindowMinimized(window); + if (WindowMinimized) { + WindowPauseLatched = TRUE; + WindowActiveFrames = 0; + } UpdateWindowMinimizedPause(); r.x = 0; @@ -1104,7 +1229,8 @@ MainWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) if (RwInitialised && gGameState == GS_PLAYING_GAME) { - RsEventHandler(rsIDLE, (void *)TRUE); + if (!psRefreshAndGetWindowMinimizedPause()) + RsEventHandler(rsIDLE, (void *)TRUE); } /* Manually resize window */ @@ -1112,7 +1238,7 @@ MainWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) rect.bottom = newPos->bottom - newPos->top; rect.right = newPos->right - newPos->left; - SetWindowPos(window, HWND_TOP, rect.left, rect.top, + SetWindowPos(window, HWND_NOTOPMOST, rect.left, rect.top, (rect.right - rect.left), (rect.bottom - rect.top), SWP_NOMOVE); @@ -1245,9 +1371,16 @@ MainWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) case WM_ACTIVATEAPP: { - WindowActive = (BOOL)wParam; - WindowMinimized = IsIconic(window); - UpdateWindowMinimizedPause(); + if (!(BOOL)wParam) { + WindowActive = FALSE; + WindowMinimized = IsNativeWindowMinimized(window); + WindowPauseLatched = TRUE; + WindowActiveFrames = 0; + UpdateWindowMinimizedPause(); + } else { + // Do not trust the activation notification alone in fullscreen. + RefreshWindowActivityState(TRUE); + } switch ( gGameState ) { @@ -1309,9 +1442,15 @@ MainWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) case WM_ACTIVATE: { - WindowActive = LOWORD(wParam) != WA_INACTIVE; - WindowMinimized = HIWORD(wParam) || IsIconic(window); - UpdateWindowMinimizedPause(); + if (LOWORD(wParam) == WA_INACTIVE || HIWORD(wParam)) { + WindowActive = FALSE; + WindowMinimized = HIWORD(wParam) || IsNativeWindowMinimized(window); + WindowPauseLatched = TRUE; + WindowActiveFrames = 0; + UpdateWindowMinimizedPause(); + } else { + RefreshWindowActivityState(TRUE); + } return 0L; } @@ -1396,7 +1535,17 @@ MainWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) case WM_SETFOCUS: #endif { - CGame::InitAfterFocusLoss(); +#ifdef FIX_BUGS + WindowActive = FALSE; + WindowPauseLatched = TRUE; + WindowActiveFrames = 0; +#else + WindowActive = TRUE; + WindowPauseLatched = IsNativeWindowMinimized(window); + WindowActiveFrames = 0; +#endif + WindowMinimized = IsNativeWindowMinimized(window); + UpdateWindowMinimizedPause(); break; } @@ -1516,6 +1665,26 @@ GetBorderlessMonitorRect(RECT *rect) rect->bottom = GetSystemMetrics(SM_CYSCREEN); } } + +static void +GetWindowedMonitorWorkArea(RECT *rect) +{ + HMONITOR monitor = MonitorFromWindow(PSGLOBAL(window), MONITOR_DEFAULTTONEAREST); + MONITORINFO monitorInfo; + monitorInfo.cbSize = sizeof(monitorInfo); + + if (monitor != nil && GetMonitorInfo(monitor, &monitorInfo)) { + *rect = monitorInfo.rcWork; + return; + } + + if (!SystemParametersInfo(SPI_GETWORKAREA, 0, rect, 0)) { + rect->left = 0; + rect->top = 0; + rect->right = GetSystemMetrics(SM_CXSCREEN); + rect->bottom = GetSystemMetrics(SM_CYSCREEN); + } +} #endif /* @@ -1725,7 +1894,10 @@ psSelectDevice() int height = rect.bottom - rect.top; SetWindowLong(PSGLOBAL(window), GWL_STYLE, WS_VISIBLE | WS_POPUP); - SetWindowPos(PSGLOBAL(window), HWND_TOP, rect.left, rect.top, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW); + // Leaving exclusive fullscreen can leave this window in the topmost + // z-order group. Borderless must stay a normal, switchable window. + SetWindowPos(PSGLOBAL(window), HWND_NOTOPMOST, rect.left, rect.top, width, height, + SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOACTIVATE); RsGlobal.maximumWidth = width; RsGlobal.maximumHeight = height; @@ -1737,14 +1909,19 @@ psSelectDevice() rect.bottom = FrontEndMenuManager.m_nPrefsHeight; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); - // center it - int spaceX = GetSystemMetrics(SM_CXSCREEN) - (rect.right-rect.left); - int spaceY = GetSystemMetrics(SM_CYSCREEN) - (rect.bottom-rect.top); + RECT workArea; + GetWindowedMonitorWorkArea(&workArea); + int width = rect.right - rect.left; + int height = rect.bottom - rect.top; + int spaceX = (workArea.right - workArea.left) - width; + int spaceY = (workArea.bottom - workArea.top) - height; + int x = workArea.left + (spaceX > 0 ? spaceX / 2 : 0); + int y = workArea.top + (spaceY > 0 ? spaceY / 2 : 0); SetWindowLong(PSGLOBAL(window), GWL_STYLE, WS_VISIBLE | WS_OVERLAPPEDWINDOW); - SetWindowPos(PSGLOBAL(window), HWND_NOTOPMOST, spaceX/2, spaceY/2, - (rect.right - rect.left), - (rect.bottom - rect.top), 0); + SetWindowPos(PSGLOBAL(window), HWND_NOTOPMOST, x, y, width, height, + SWP_FRAMECHANGED | SWP_SHOWWINDOW); + ShowWindow(PSGLOBAL(window), SW_MAXIMIZE); // Have to get actual size because the window perhaps didn't fit GetClientRect(PSGLOBAL(window), &rect); @@ -2274,7 +2451,12 @@ WinMain(HINSTANCE instance, /* * Show the window, and refresh it... */ - ShowWindow(PSGLOBAL(window), cmdShow); +#ifdef IMPROVED_VIDEOMODE + if (FrontEndMenuManager.m_nPrefsWindowed == WINDOWMODE_WINDOWED) + ShowWindow(PSGLOBAL(window), SW_MAXIMIZE); + else +#endif + ShowWindow(PSGLOBAL(window), cmdShow); UpdateWindow(PSGLOBAL(window)); { @@ -2476,6 +2658,9 @@ WinMain(HINSTANCE instance, case GS_INIT_ONCE: { + if (psRefreshAndGetWindowMinimizedPause()) + break; + #ifdef NO_MOVIES if (!gbNoMovies) #endif @@ -2516,6 +2701,9 @@ WinMain(HINSTANCE instance, #ifndef PS2_MENU case GS_INIT_FRONTEND: { + if (psRefreshAndGetWindowMinimizedPause()) + break; + LoadingScreen(nil, nil, "loadsc0"); // LoadingScreen(nil, nil, "loadsc0"); // duplicate @@ -2537,11 +2725,17 @@ WinMain(HINSTANCE instance, case GS_FRONTEND: { + if (psRefreshAndGetWindowMinimizedPause()) + break; + GetWindowPlacement(PSGLOBAL(window), &wp); if (wp.showCmd != SW_SHOWMINIMIZED) RsEventHandler(rsFRONTENDIDLE, nil); + if (psRefreshAndGetWindowMinimizedPause()) + break; + #ifdef PS2_MENU if ( !FrontEndMenuManager.m_bMenuActive || TheMemoryCard.m_bWantToLoad ) #else @@ -2569,6 +2763,9 @@ WinMain(HINSTANCE instance, case GS_INIT_PLAYING_GAME: { + if (psRefreshAndGetWindowMinimizedPause()) + break; + #ifdef PS2_MENU CGame::Initialise("DATA\\GTA3.DAT"); @@ -2603,6 +2800,9 @@ WinMain(HINSTANCE instance, case GS_PLAYING_GAME: { + if (psRefreshAndGetWindowMinimizedPause()) + break; + float ms = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); if ( RwInitialised ) { @@ -2615,18 +2815,26 @@ WinMain(HINSTANCE instance, } else { - if ( RwCameraBeginUpdate(Scene.camera) ) + if (!psRefreshAndGetWindowMinimizedPause()) { - RwCameraEndUpdate(Scene.camera); - RefreshWindowActivityState(); - if (!CTimer::GetIsWindowMinimizedPaused()) + ForegroundApp = TRUE; + SetWindowAudioSuspended(FALSE, TRUE); + } + else if (WindowPauseLatched && WindowActive && !WindowMinimized) + { + // The first active sample intentionally keeps the pause latched. + // Poll once more instead of sleeping until unrelated input arrives. + continue; + } + else + { + if ( RwCameraBeginUpdate(Scene.camera) ) { - ForegroundApp = TRUE; - SetWindowAudioSuspended(FALSE); + RwCameraEndUpdate(Scene.camera); } + + WaitMessage(); } - - WaitMessage(); } }