diff --git a/AGENTS.md b/AGENTS.md index 837278784e8..740a15cee9e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,22 +4,12 @@ GeneralsX is a cross-platform port of Command & Conquer: Generals Zero Hour for **Linux and macOS**, porting legacy Windows DirectX 8 + Miles Sound code to a modern stack (SDL3 + DXVK + OpenAL + 64-bit). This is a **massive C++ game engine** (~500k LOC) preserving retail gameplay while modernizing the platform layer. ## Must-Load Context -<<<<<<< Updated upstream Before starting work, read: - `.github/copilot-instructions.md` – quick reference - `.github/instructions/generalsx.instructions.md` – full architecture - `.github/instructions/git-commit.instructions.md` – commit standards - `.github/instructions/docs.instructions.md` – documentation workflow - `docs/DEV_BLOG/YYYY-MM-DIARY.md` – current development notes -======= -Before work, read: -- `docs/DEV_BLOG/YYYY-MM-DIARY.md` (current month) - -## Key Entry Points -- `GeneralsMD/Code/Main/WinMain.cpp` -- `Generals/Code/Main/WinMain.cpp` -- `Core/GameEngineDevice/Source/` ->>>>>>> Stashed changes ## Platform Focus - **Active**: Linux (`linux64-deploy`), macOS (`macos-vulkan`) diff --git a/Core/GameEngine/Include/Common/GameAudio.h b/Core/GameEngine/Include/Common/GameAudio.h index dae9d7cd132..60cb8e214ae 100644 --- a/Core/GameEngine/Include/Common/GameAudio.h +++ b/Core/GameEngine/Include/Common/GameAudio.h @@ -177,11 +177,10 @@ class AudioManager : public SubsystemInterface AsciiString prevTrackName(const AsciiString& currentTrack ); // changing music tracks - virtual void nextMusicTrack() = 0; - virtual void prevMusicTrack() = 0; + virtual AsciiString nextMusicTrack() = 0; + virtual AsciiString prevMusicTrack() = 0; virtual Bool isMusicPlaying() const = 0; virtual Bool hasMusicTrackCompleted( const AsciiString& trackName, Int numberOfTimes ) const = 0; - virtual AsciiString getMusicTrackName() const = 0; virtual void setAudioEventEnabled( AsciiString eventToAffect, Bool enable ); virtual void setAudioEventVolumeOverride( AsciiString eventToAffect, Real newVolume ); diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 7afb66d91d6..db53e049456 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -281,6 +281,8 @@ void GameLogic::clearGameData( Bool showScoreScreen ) void FixupScoreScreenMovieWindow(); FixupScoreScreenMovieWindow(); + + destroyQuitMenu(); } TheGameEngine->reset(); diff --git a/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h b/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h index 71707bd3054..2ba3483d23f 100644 --- a/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h +++ b/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h @@ -23,6 +23,7 @@ #include "Common/AsciiString.h" #include "Common/GameAudio.h" #include "mss/mss.h" +#include "mutex.h" class AudioEventRTS; @@ -39,8 +40,8 @@ enum PlayingAudioType CPP_11(: Int) enum PlayingStatus CPP_11(: Int) { PS_Playing, - PS_Stopped, - PS_Paused + PS_Stopping, ///< Is about to be stopped + PS_Stopped, ///< Is about to be released }; enum PlayingWhich CPP_11(: Int) @@ -60,22 +61,26 @@ struct PlayingAudio HSTREAM m_stream; }; - PlayingAudioType m_type; - volatile PlayingStatus m_status; // This member is adjusted by another running thread. AudioEventRTS *m_audioEventRTS; - void *m_file; // The file that was opened to play this - Bool m_requestStop; + void *m_file; // The file that was opened to play this + PlayingAudioType m_type; + volatile PlayingStatus m_status; // This member is adjusted by another running thread. + Short m_framesFaded; + Bool m_fade; Bool m_cleanupAudioEventRTS; - Int m_framesFaded; - - PlayingAudio() : - m_type(PAT_INVALID), - m_audioEventRTS(nullptr), - m_requestStop(false), - m_cleanupAudioEventRTS(true), - m_sample(nullptr), - m_framesFaded(0) - { } + + PlayingAudio() + : m_sample(nullptr) + , m_audioEventRTS(nullptr) + , m_file(nullptr) + , m_type(PAT_INVALID) + , m_status(PS_Playing) + , m_framesFaded(0) + , m_fade(false) + , m_cleanupAudioEventRTS(true) + {} + + static_assert(sizeof(m_status) == sizeof(long), "Must be size of long, because it is used with Interlocked functions"); }; struct ProviderInfo @@ -114,7 +119,7 @@ class AudioFileCache // End Protected by mutex // Note: These functions should be used for informational purposes only. For speed reasons, - // they are not protected by the mutex, so they are not guarenteed to be valid if called from + // they are not protected by the mutex, so they are not guaranteed to be valid if called from // outside the audio cache. They should be used as a rough estimate only. UnsignedInt getCurrentlyUsedSize() const { return m_currentlyUsedSize; } UnsignedInt getMaxSize() const { return m_maxSize; } @@ -150,12 +155,10 @@ class MilesAudioManager : public AudioManager MilesAudioManager(); virtual ~MilesAudioManager() override; - - virtual void nextMusicTrack() override; - virtual void prevMusicTrack() override; + virtual AsciiString nextMusicTrack() override; + virtual AsciiString prevMusicTrack() override; virtual Bool isMusicPlaying() const override; virtual Bool hasMusicTrackCompleted( const AsciiString& trackName, Int numberOfTimes ) const override; - virtual AsciiString getMusicTrackName() const override; virtual void openDevice() override; virtual void closeDevice() override; @@ -172,8 +175,8 @@ class MilesAudioManager : public AudioManager ///< NOTE NOTE NOTE !!DO NOT USE THIS IN FOR GAMELOGIC PURPOSES!! NOTE NOTE NOTE virtual Bool isCurrentlyPlaying( AudioHandle handle ) override; - virtual void notifyOfAudioCompletion( UnsignedInt audioCompleted, UnsignedInt flags ) override; - virtual PlayingAudio *findPlayingAudioFrom( UnsignedInt audioCompleted, UnsignedInt flags ); + virtual void notifyOfAudioCompletion( UnsignedInt handle, UnsignedInt flags ) override; + virtual PlayingAudio *findPlayingAudioFrom( UnsignedInt handle, UnsignedInt flags ); virtual UnsignedInt getProviderCount() const override; virtual AsciiString getProviderName( UnsignedInt providerNum ) const override; @@ -208,7 +211,6 @@ class MilesAudioManager : public AudioManager virtual void processRequestList() override; virtual void processPlayingList(); virtual void processFadingList(); - virtual void processStoppedList(); Bool shouldProcessRequestThisFrame( AudioRequest *req ) const; void adjustRequest( AudioRequest *req ); @@ -243,7 +245,6 @@ class MilesAudioManager : public AudioManager void *playSample( AudioEventRTS *event, HSAMPLE sample ); void *playSample3D( AudioEventRTS *event, H3DSAMPLE sample3D ); - protected: void buildProviderList(); void createListener(); void initDelayFilter(); @@ -261,7 +262,13 @@ class MilesAudioManager : public AudioManager PlayingAudio *allocatePlayingAudio(); void releaseMilesHandles( PlayingAudio *release ); void releasePlayingAudio( PlayingAudio *release ); + void stopPlayingAudio( PlayingAudio *release ); + void fadePlayingAudio( PlayingAudio *playing ); + + PlayingAudio *findActiveMusic( const AsciiString *trackName = nullptr ); + const PlayingAudio *findActiveMusic( const AsciiString* trackName = nullptr ) const; + void releasePlayingAudioInListIfStopped(std::list &list, CriticalSectionClass &cs); void stopAllAudioImmediately(); void freeAllMilesHandles(); @@ -272,7 +279,6 @@ class MilesAudioManager : public AudioManager void stopAllSpeech(); - protected: void initFilters( HSAMPLE sample, AudioEventRTS *eventInfo ); void initFilters3D( H3DSAMPLE sample, AudioEventRTS *eventInfo, const Coord3D *pos ); @@ -298,22 +304,22 @@ class MilesAudioManager : public AudioManager std::list m_availableSamples; std::list m_available3DSamples; - // Currently Playing stuff. Useful if we have to preempt it. + // Currently Playing audio. Useful if we have to preempt it. // This should rarely if ever happen, as we mirror this in Sounds, and attempt to // keep preemption from taking place here. std::list m_playingSounds; std::list m_playing3DSounds; std::list m_playingStreams; - // Currently fading stuff. At this point, we just want to let it finish fading, when it is - // done it should be added to the completed list, then "freed" and the counts should be updated - // on the next update + // Currently fading music. We just let it finish fading, then release it. std::list m_fadingAudio; - // Stuff that is done playing (either because it has finished or because it was killed) - // This stuff should be cleaned up during the next update cycle. This includes updating counts - // in the sound engine - std::list m_stoppedAudio; + // TheSuperHackers @fix Erasing from PlayingAudio lists on main thread is not safe when the MSS Timer thread + // needs to iterate the same lists. The critical sections are used to guard writes that will invalidate iterators. + CriticalSectionClass m_playingSoundsCS; + CriticalSectionClass m_playing3DSoundsCS; + CriticalSectionClass m_playingStreamsCS; + CriticalSectionClass m_fadingAudioCS; AudioFileCache *m_audioCache; PlayingAudio *m_binkHandle; @@ -343,17 +349,16 @@ class MilesAudioManagerDummy : public MilesAudioManager virtual void resumeAudio(AudioAffect which) override {} virtual void pauseAmbient(Bool shouldPause) override {} virtual void killAudioEventImmediately(AudioHandle audioEvent) override {} - virtual void nextMusicTrack() override {} - virtual void prevMusicTrack() override {} + virtual AsciiString nextMusicTrack() override { return AsciiString::TheEmptyString; } + virtual AsciiString prevMusicTrack() override { return AsciiString::TheEmptyString; } virtual Bool isMusicPlaying() const override { return false; } virtual Bool hasMusicTrackCompleted(const AsciiString& trackName, Int numberOfTimes) const override { return false; } - virtual AsciiString getMusicTrackName() const override { return ""; } //virtual void openDevice() override {} //virtual void closeDevice() override {} //virtual void* getDevice() override { return nullptr; } virtual void notifyOfAudioCompletion(UnsignedInt audioCompleted, UnsignedInt flags) override {} virtual UnsignedInt getProviderCount() const override { return 0; }; - virtual AsciiString getProviderName(UnsignedInt providerNum) const override { return ""; } + virtual AsciiString getProviderName(UnsignedInt providerNum) const override { return AsciiString::TheEmptyString; } virtual UnsignedInt getProviderIndex(AsciiString providerName) const override { return 0; } virtual void selectProvider(UnsignedInt providerNdx) override {} virtual void unselectProvider() override {} diff --git a/Core/GameEngineDevice/Include/OpenALAudioDevice/OpenALAudioManager.h b/Core/GameEngineDevice/Include/OpenALAudioDevice/OpenALAudioManager.h index 34fd7c61117..86f9aecb895 100644 --- a/Core/GameEngineDevice/Include/OpenALAudioDevice/OpenALAudioManager.h +++ b/Core/GameEngineDevice/Include/OpenALAudioDevice/OpenALAudioManager.h @@ -81,8 +81,8 @@ friend class OpenALAudioFileCache; OpenALAudioManager(); virtual ~OpenALAudioManager(); - virtual void nextMusicTrack(void); - virtual void prevMusicTrack(void); + virtual AsciiString nextMusicTrack(void); + virtual AsciiString prevMusicTrack(void); virtual Bool isMusicPlaying(void) const; virtual Bool hasMusicTrackCompleted(const AsciiString &trackName, Int numberOfTimes) const; virtual AsciiString getMusicTrackName(void) const; diff --git a/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp b/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp index 7625b3edfb2..39343fe5810 100644 --- a/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp +++ b/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp @@ -70,6 +70,8 @@ #include "Common/file.h" +#include + enum { INFINITE_LOOP_COUNT = 1000000 }; @@ -233,10 +235,6 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL channelCount = TheAudio->getNum2DSamples(); for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (!playing) { - continue; - } - playingArray[(int)AIL_sample_user_data(playing->m_sample, 0)] = playing; } @@ -266,10 +264,6 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { playing = *it; - if( !playing ) - { - continue; - } filenameNoSlashes = playing->m_audioEventRTS->getFilename(); filenameNoSlashes = filenameNoSlashes.reverseFind( '\\' ) + 1; @@ -294,10 +288,6 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL channelCount = TheAudio->getNum3DSamples(); for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (!playing) { - continue; - } - playingArray[(int)AIL_3D_user_data(playing->m_3DSample, 0)] = playing; } @@ -361,10 +351,6 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { playing = *it; - if( !playing ) - { - continue; - } filenameNoSlashes = playing->m_audioEventRTS->getFilename(); filenameNoSlashes = filenameNoSlashes.reverseFind('\\') + 1; @@ -388,13 +374,9 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL channel = 1; for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (!playing) { - continue; - } filenameNoSlashes = playing->m_audioEventRTS->getFilename(); filenameNoSlashes = filenameNoSlashes.reverseFind('\\') + 1; - // Calculate Sample volume volume = 100.0f; volume *= getEffectiveVolume(playing->m_audioEventRTS); @@ -415,10 +397,6 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { playing = *it; - if( !playing ) - { - continue; - } filenameNoSlashes = playing->m_audioEventRTS->getFilename(); filenameNoSlashes = filenameNoSlashes.reverseFind('\\') + 1; @@ -486,7 +464,6 @@ void MilesAudioManager::update() processRequestList(); processPlayingList(); processFadingList(); - processStoppedList(); } //------------------------------------------------------------------------------------------------- @@ -498,49 +475,36 @@ void MilesAudioManager::stopAudio( AudioAffect which ) // 3) Set the status to stopped, so that when we next process the playing list, we will // correctly clean up the sample. - std::list::iterator it; PlayingAudio *playing = nullptr; if (BitIsSet(which, AudioAffect_Sound)) { for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_register_EOS_callback(playing->m_sample, nullptr); - AIL_stop_sample(playing->m_sample); - playing->m_status = PS_Stopped; - } + stopPlayingAudio(playing); } } if (BitIsSet(which, AudioAffect_Sound3D)) { for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_register_3D_EOS_callback(playing->m_3DSample, nullptr); - AIL_stop_3D_sample(playing->m_3DSample); - playing->m_status = PS_Stopped; - } + stopPlayingAudio(playing); } } if (BitIsSet(which, AudioAffect_Speech | AudioAffect_Music)) { for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (playing) { - if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - if (!BitIsSet(which, AudioAffect_Music)) { - continue; - } - } else { - if (!BitIsSet(which, AudioAffect_Speech)) { - continue; - } + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + if (!BitIsSet(which, AudioAffect_Music)) { + continue; + } + } else { + if (!BitIsSet(which, AudioAffect_Speech)) { + continue; } - AIL_register_stream_callback(playing->m_stream, nullptr); - AIL_pause_stream(playing->m_stream, 1); - playing->m_status = PS_Stopped; } + stopPlayingAudio(playing); } } } @@ -554,46 +518,42 @@ void MilesAudioManager::pauseAudio( AudioAffect which ) if (BitIsSet(which, AudioAffect_Sound)) { for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_stop_sample(playing->m_sample); - } + DEBUG_ASSERTCRASH(playing->m_sample, ("Sample is not expected to be null")); + AIL_stop_sample(playing->m_sample); } } if (BitIsSet(which, AudioAffect_Sound3D)) { for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_stop_3D_sample(playing->m_3DSample); - } + DEBUG_ASSERTCRASH(playing->m_sample, ("3D Sample is not expected to be null")); + AIL_stop_3D_sample(playing->m_3DSample); } } if (BitIsSet(which, AudioAffect_Speech | AudioAffect_Music)) { for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (playing) { - if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - if (!BitIsSet(which, AudioAffect_Music)) { - continue; - } - } else { - if (!BitIsSet(which, AudioAffect_Speech)) { - continue; - } + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + if (!BitIsSet(which, AudioAffect_Music)) { + continue; + } + } else { + if (!BitIsSet(which, AudioAffect_Speech)) { + continue; } - - AIL_pause_stream(playing->m_stream, 1); } + DEBUG_ASSERTCRASH(playing->m_sample, ("Stream is not expected to be null")); + AIL_pause_stream(playing->m_stream, 1); } } //Get rid of PLAY audio requests when pausing audio. std::list::iterator ait; - for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); /* empty */) + for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ) { AudioRequest *req = (*ait); - if( req && req->m_request == AR_Play ) + if( req->m_request == AR_Play ) { deleteInstance(req); ait = m_audioRequests.erase(ait); @@ -615,36 +575,33 @@ void MilesAudioManager::resumeAudio( AudioAffect which ) if (BitIsSet(which, AudioAffect_Sound)) { for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_resume_sample(playing->m_sample); - } + DEBUG_ASSERTCRASH(playing->m_sample, ("Sample is not expected to be null")); + AIL_resume_sample(playing->m_sample); } } if (BitIsSet(which, AudioAffect_Sound3D)) { for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_resume_3D_sample(playing->m_3DSample); - } + DEBUG_ASSERTCRASH(playing->m_3DSample, ("3D Sample is not expected to be null")); + AIL_resume_3D_sample(playing->m_3DSample); } } if (BitIsSet(which, AudioAffect_Speech | AudioAffect_Music)) { for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (playing) { - if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - if (!BitIsSet(which, AudioAffect_Music)) { - continue; - } - } else { - if (!BitIsSet(which, AudioAffect_Speech)) { - continue; - } + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + if (!BitIsSet(which, AudioAffect_Music)) { + continue; + } + } else { + if (!BitIsSet(which, AudioAffect_Speech)) { + continue; } - AIL_pause_stream(playing->m_stream, 0); } + DEBUG_ASSERTCRASH(playing->m_stream, ("Stream is not expected to be null")); + AIL_pause_stream(playing->m_stream, 0); } } } @@ -690,15 +647,11 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) if (handleToKill) { for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = (*it); - if (!playing) { - continue; - } if (playing->m_audioEventRTS && playing->m_audioEventRTS->getPlayingHandle() == handleToKill) { //Release this streaming channel immediately because we are going to play another sound in it's place. - releasePlayingAudio(playing); - m_playingStreams.erase(it); + stopPlayingAudio(playing); foundSoundToReplace = true; break; } @@ -723,6 +676,8 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) } AIL_set_stream_volume_pan(stream, getEffectiveVolume(event), 0.5f); playStream(event, stream); + + CriticalSectionClass::LockClass lock(m_playingStreamsCS); m_playingStreams.push_back(audio); audio = nullptr; } @@ -746,15 +701,11 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) { for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = (*it); - if (!playing) { - continue; - } if( playing->m_audioEventRTS && playing->m_audioEventRTS->getPlayingHandle() == handleToKill ) { //Release this 3D sound channel immediately because we are going to play another sound in it's place. - releasePlayingAudio(playing); - m_playing3DSounds.erase(it); + stopPlayingAudio(playing); foundSoundToReplace = true; break; } @@ -786,7 +737,6 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) audio->m_3DSample = sample3D; audio->m_file = nullptr; audio->m_type = PAT_3DSample; - m_playing3DSounds.push_back(audio); if (sample3D) { audio->m_file = playSample3D(event, sample3D); @@ -795,13 +745,14 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) if( !audio->m_file ) { - m_playing3DSounds.pop_back(); #ifdef INTENSIVE_AUDIO_DEBUG DEBUG_LOG((" Killed (no handles available)")); #endif } else { + CriticalSectionClass::LockClass lock(m_playing3DSoundsCS); + m_playing3DSounds.push_back(audio); audio = nullptr; #ifdef INTENSIVE_AUDIO_DEBUG DEBUG_LOG((" Playing.")); @@ -811,20 +762,16 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) else { // UI sounds are always 2-D. All other sounds should be Positional - // Unit acknowledgement, etc, falls into the UI category of sound. + // Unit acknowledgment, etc, falls into the UI category of sound. Bool foundSoundToReplace = false; if (handleToKill) { for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = (*it); - if (!playing) { - continue; - } if (playing->m_audioEventRTS && playing->m_audioEventRTS->getPlayingHandle() == handleToKill) { //Release this 2D sound channel immediately because we are going to play another sound in it's place. - releasePlayingAudio(playing); - m_playingSounds.erase(it); + stopPlayingAudio(playing); foundSoundToReplace = true; break; } @@ -857,7 +804,6 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) audio->m_sample = sample; audio->m_file = nullptr; audio->m_type = PAT_Sample; - m_playingSounds.push_back(audio); if (sample) { audio->m_file = playSample(event, sample); @@ -868,14 +814,14 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) #ifdef INTENSIVE_AUDIO_DEBUG DEBUG_LOG((" Killed (no handles available)")); #endif - m_playingSounds.pop_back(); } else { + CriticalSectionClass::LockClass lock(m_playingSoundsCS); + m_playingSounds.push_back(audio); audio = nullptr; + #ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG((" Playing.")); + #endif } - - #ifdef INTENSIVE_AUDIO_DEBUG - DEBUG_LOG((" Playing.")); - #endif } break; } @@ -884,6 +830,7 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) // If we were able to successfully play audio, then we set it to null above. (And it will be freed // later. However, if audio is non-null at this point, then it must be freed. if (audio) { + stopPlayingAudio(audio); releasePlayingAudio(audio); } } @@ -897,67 +844,51 @@ void MilesAudioManager::stopAudioEvent( AudioHandle handle ) std::list::iterator it; if ( handle == AHSV_StopTheMusic || handle == AHSV_StopTheMusicFade ) { - // for music, just find the currently playing music stream and kill it. + // for music, find and stop the currently playing music stream(s). for ( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { PlayingAudio *audio = (*it); - if (!audio) { - continue; - } if( audio->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music ) { if( handle == AHSV_StopTheMusicFade ) { - m_fadingAudio.push_back(audio); + fadePlayingAudio(audio); } else { - //m_stoppedAudio.push_back(audio); - releasePlayingAudio( audio ); + stopPlayingAudio(audio); } - m_playingStreams.erase(it); - break; } } + return; } for ( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { PlayingAudio *audio = (*it); - if (!audio) { - continue; - } if (audio->m_audioEventRTS->getPlayingHandle() == handle) { - // found it - audio->m_requestStop = true; - notifyOfAudioCompletion((UnsignedInt)(audio->m_stream), PAT_Stream); + stopPlayingAudio(audio); break; } } for ( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { PlayingAudio *audio = (*it); - if (!audio) { - continue; - } if (audio->m_audioEventRTS->getPlayingHandle() == handle) { - audio->m_requestStop = true; + stopPlayingAudio(audio); break; } } for ( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { PlayingAudio *audio = (*it); - if (!audio) { - continue; - } if (audio->m_audioEventRTS->getPlayingHandle() == handle) { #ifdef INTENSIVE_AUDIO_DEBUG DEBUG_LOG((" (%s)", audio->m_audioEventRTS->getEventName())); #endif - audio->m_requestStop = true; + stopPlayingAudio(audio); break; } } @@ -971,7 +902,7 @@ void MilesAudioManager::killAudioEventImmediately( AudioHandle audioEvent ) for( ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ait++ ) { AudioRequest *req = (*ait); - if( req && req->m_request == AR_Play && req->m_handleToInteractOn == audioEvent ) + if( req->m_request == AR_Play && req->m_handleToInteractOn == audioEvent ) { deleteInstance(req); ait = m_audioRequests.erase(ait); @@ -984,15 +915,10 @@ void MilesAudioManager::killAudioEventImmediately( AudioHandle audioEvent ) for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); it++ ) { PlayingAudio *audio = (*it); - if( !audio ) - { - continue; - } if( audio->m_audioEventRTS->getPlayingHandle() == audioEvent ) { - releasePlayingAudio( audio ); - m_playing3DSounds.erase( it ); + stopPlayingAudio(audio); return; } } @@ -1001,15 +927,10 @@ void MilesAudioManager::killAudioEventImmediately( AudioHandle audioEvent ) for( it = m_playingSounds.begin(); it != m_playingSounds.end(); it++ ) { PlayingAudio *audio = (*it); - if( !audio ) - { - continue; - } if( audio->m_audioEventRTS->getPlayingHandle() == audioEvent ) { - releasePlayingAudio( audio ); - m_playingSounds.erase( it ); + stopPlayingAudio(audio); return; } } @@ -1018,15 +939,10 @@ void MilesAudioManager::killAudioEventImmediately( AudioHandle audioEvent ) for( it = m_playingStreams.begin(); it != m_playingStreams.end(); it++ ) { PlayingAudio *audio = (*it); - if( !audio ) - { - continue; - } if( audio->m_audioEventRTS->getPlayingHandle() == audioEvent ) { - releasePlayingAudio( audio ); - m_playingStreams.erase( it ); + stopPlayingAudio(audio); return; } } @@ -1056,9 +972,7 @@ void MilesAudioManager::closeFile( void *fileRead ) //------------------------------------------------------------------------------------------------- PlayingAudio *MilesAudioManager::allocatePlayingAudio() { - PlayingAudio *aud = NEW PlayingAudio; // poolify - aud->m_status = PS_Playing; - return aud; + return NEW PlayingAudio; // poolify } @@ -1073,6 +987,7 @@ void MilesAudioManager::releaseMilesHandles( PlayingAudio *release ) AIL_register_EOS_callback(release->m_sample, nullptr); AIL_stop_sample(release->m_sample); m_availableSamples.push_back(release->m_sample); + release->m_sample = nullptr; } break; } @@ -1082,6 +997,7 @@ void MilesAudioManager::releaseMilesHandles( PlayingAudio *release ) AIL_register_3D_EOS_callback(release->m_3DSample, nullptr); AIL_stop_3D_sample(release->m_3DSample); m_available3DSamples.push_back(release->m_3DSample); + release->m_3DSample = nullptr; } break; } @@ -1090,6 +1006,7 @@ void MilesAudioManager::releaseMilesHandles( PlayingAudio *release ) if (release->m_stream) { AIL_register_stream_callback(release->m_stream, nullptr); AIL_close_stream(release->m_stream); + release->m_stream = nullptr; } break; } @@ -1100,6 +1017,31 @@ void MilesAudioManager::releaseMilesHandles( PlayingAudio *release ) //------------------------------------------------------------------------------------------------- void MilesAudioManager::releasePlayingAudio( PlayingAudio *release ) { + DEBUG_ASSERTCRASH(release->m_status == PS_Stopped, ("PlayingAudio must be stopped by now")); + DEBUG_ASSERTCRASH(release->m_type == PAT_INVALID, ("PlayingAudio must be invalidated by now")); + + if (release->m_file) { + closeFile(release->m_file); + release->m_file = nullptr; + } + + if (release->m_cleanupAudioEventRTS) { + releaseAudioEventRTS(release->m_audioEventRTS); + } + + delete release; +} + +//------------------------------------------------------------------------------------------------- +void MilesAudioManager::stopPlayingAudio( PlayingAudio *release ) +{ + // Advance from Playing to Stopping + InterlockedCompareExchange(reinterpret_cast(&release->m_status), PS_Stopping, PS_Playing); + // Advance from Stopping to Stopped + const long prevStatus = InterlockedCompareExchange(reinterpret_cast(&release->m_status), PS_Stopped, PS_Stopping); + if (prevStatus != PS_Stopping) { + return; + } if (release->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_SoundEffect) { if (release->m_type == PAT_Sample) { if (release->m_sample) { @@ -1111,60 +1053,109 @@ void MilesAudioManager::releasePlayingAudio( PlayingAudio *release ) } } } - releaseMilesHandles(release); // forces stop of this audio - closeFile( release->m_file ); - if (release->m_cleanupAudioEventRTS) { - releaseAudioEventRTS(release->m_audioEventRTS); + releaseMilesHandles(release); +} + +//------------------------------------------------------------------------------------------------- +void MilesAudioManager::fadePlayingAudio( PlayingAudio *playing ) +{ + playing->m_fade = true; +} + +//------------------------------------------------------------------------------------------------- +PlayingAudio *MilesAudioManager::findActiveMusic( const AsciiString* trackName ) +{ + std::list::const_iterator it; + PlayingAudio *playing; + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing->m_status != PS_Playing) { + continue; + } + if (playing->m_fade) { + continue; + } + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType != AT_Music) { + continue; + } + if (trackName && *trackName != playing->m_audioEventRTS->getEventName()) { + continue; + } + return playing; } - delete release; - release = nullptr; + return nullptr; } //------------------------------------------------------------------------------------------------- -void MilesAudioManager::stopAllAudioImmediately() +const PlayingAudio *MilesAudioManager::findActiveMusic( const AsciiString* trackName ) const +{ + return const_cast(this)->findActiveMusic(trackName); +} + + +//------------------------------------------------------------------------------------------------- +void MilesAudioManager::releasePlayingAudioInListIfStopped(std::list &list, CriticalSectionClass &cs) { std::list::iterator it; PlayingAudio *playing; + CriticalSectionClass::LockClass lock(cs); - for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ) { - playing = *it; - if (!playing) { + for (it = list.begin(); it != list.end(); ) + { + playing = (*it); + + if (playing->m_status == PS_Stopped) + { + releasePlayingAudio(playing); + it = list.erase(it); continue; } - releasePlayingAudio(playing); - it = m_playingSounds.erase(it); + ++it; } +} - for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) { - playing = *it; - if (!playing) { - continue; - } +//------------------------------------------------------------------------------------------------- +void MilesAudioManager::stopAllAudioImmediately() +{ + std::list::iterator it; + PlayingAudio *playing; - releasePlayingAudio(playing); - it = m_playing3DSounds.erase(it); + // + // Stop audio without locking. Calls into Miles. + // + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { + playing = (*it); + stopPlayingAudio(playing); } - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ) { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = (*it); - if (!playing) { - continue; - } + stopPlayingAudio(playing); + } - releasePlayingAudio(playing); - it = m_playingStreams.erase(it); + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = (*it); + stopPlayingAudio(playing); } - for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); ) { - playing = (*it); - if (!playing) { - continue; - } + for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); ++it) { + playing = (*it); + stopPlayingAudio(playing); + } - releasePlayingAudio(playing); - it = m_fadingAudio.erase(it); - } + // + // Release audio with locking. Must not call into Miles! + // + releasePlayingAudioInListIfStopped(m_playingSounds, m_playingSoundsCS); + releasePlayingAudioInListIfStopped(m_playing3DSounds, m_playing3DSoundsCS); + releasePlayingAudioInListIfStopped(m_playingStreams, m_playingStreamsCS); + releasePlayingAudioInListIfStopped(m_fadingAudio, m_fadingAudioCS); + + DEBUG_ASSERTCRASH(m_playingSounds.empty(), ("List is expected to be empty now")); + DEBUG_ASSERTCRASH(m_playing3DSounds.empty(), ("List is expected to be empty now")); + DEBUG_ASSERTCRASH(m_playingStreams.empty(), ("List is expected to be empty now")); + DEBUG_ASSERTCRASH(m_fadingAudio.empty(), ("List is expected to be empty now")); std::list::iterator hit; for (hit = m_audioForcePlayed.begin(); hit != m_audioForcePlayed.end(); ++hit) { @@ -1185,7 +1176,7 @@ void MilesAudioManager::freeAllMilesHandles() // Walks through the available 2-D and 3-D handles and releases them std::list::iterator it; - for ( it = m_availableSamples.begin(); it != m_availableSamples.end(); /* empty */ ) { + for ( it = m_availableSamples.begin(); it != m_availableSamples.end(); ) { HSAMPLE sample = *it; AIL_release_sample_handle(sample); it = m_availableSamples.erase(it); @@ -1193,7 +1184,7 @@ void MilesAudioManager::freeAllMilesHandles() m_num2DSamples = 0; std::list::iterator it3D; - for ( it3D = m_available3DSamples.begin(); it3D != m_available3DSamples.end(); /* empty */ ) { + for ( it3D = m_available3DSamples.begin(); it3D != m_available3DSamples.end(); ) { H3DSAMPLE sample3D = *it3D; AIL_release_3D_sample_handle(sample3D); it3D = m_available3DSamples.erase(it3D); @@ -1257,17 +1248,10 @@ void MilesAudioManager::stopAllSpeech() { std::list::iterator it; PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ) { + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = (*it); - if (!playing) { - continue; - } - if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Streaming) { - releasePlayingAudio(playing); - it = m_playingStreams.erase(it); - } else { - ++it; + stopPlayingAudio(playing); } } @@ -1321,16 +1305,12 @@ void MilesAudioManager::initFilters3D( H3DSAMPLE sample, AudioEventRTS *event, c } //------------------------------------------------------------------------------------------------- -void MilesAudioManager::nextMusicTrack() +AsciiString MilesAudioManager::nextMusicTrack() { AsciiString trackName; - std::list::iterator it; - PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - trackName = playing->m_audioEventRTS->getEventName(); - } + + if (PlayingAudio *playing = findActiveMusic()) { + trackName = playing->m_audioEventRTS->getEventName(); } // Stop currently playing music @@ -1339,19 +1319,17 @@ void MilesAudioManager::nextMusicTrack() trackName = nextTrackName(trackName); AudioEventRTS newTrack(trackName); TheAudio->addAudioEvent(&newTrack); + + return trackName; } //------------------------------------------------------------------------------------------------- -void MilesAudioManager::prevMusicTrack() +AsciiString MilesAudioManager::prevMusicTrack() { AsciiString trackName; - std::list::iterator it; - PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - trackName = playing->m_audioEventRTS->getEventName(); - } + + if (PlayingAudio *playing = findActiveMusic()) { + trackName = playing->m_audioEventRTS->getEventName(); } // Stop currently playing music @@ -1360,73 +1338,27 @@ void MilesAudioManager::prevMusicTrack() trackName = prevTrackName(trackName); AudioEventRTS newTrack(trackName); TheAudio->addAudioEvent(&newTrack); + + return trackName; } //------------------------------------------------------------------------------------------------- Bool MilesAudioManager::isMusicPlaying() const { - std::list::const_iterator it; - PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - return TRUE; - } - } - - return FALSE; + return findActiveMusic() != nullptr; } //------------------------------------------------------------------------------------------------- Bool MilesAudioManager::hasMusicTrackCompleted( const AsciiString& trackName, Int numberOfTimes ) const { - std::list::const_iterator it; - PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - if (playing->m_audioEventRTS->getEventName() == trackName) { - if (INFINITE_LOOP_COUNT - AIL_stream_loop_count(playing->m_stream) >= numberOfTimes) { - return TRUE; - } - } + if (const PlayingAudio *playing = findActiveMusic(&trackName)) { + if (INFINITE_LOOP_COUNT - AIL_stream_loop_count(playing->m_stream) >= numberOfTimes) { + return TRUE; } } - return FALSE; } -//------------------------------------------------------------------------------------------------- -AsciiString MilesAudioManager::getMusicTrackName() const -{ - // First check the requests. If there's one there, then report that as the currently playing track. - std::list::const_iterator ait; - for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ++ait) { - if ((*ait)->m_request != AR_Play) { - continue; - } - - if (!(*ait)->m_usePendingEvent) { - continue; - } - - if ((*ait)->m_pendingEvent->getAudioEventInfo()->m_soundType == AT_Music) { - return (*ait)->m_pendingEvent->getEventName(); - } - } - - std::list::const_iterator it; - PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - return playing->m_audioEventRTS->getEventName(); - } - } - - return AsciiString::TheEmptyString; -} - //------------------------------------------------------------------------------------------------- void MilesAudioManager::openDevice() { @@ -1482,21 +1414,21 @@ Bool MilesAudioManager::isCurrentlyPlaying( AudioHandle handle ) for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getPlayingHandle() == handle) { + if (playing->m_audioEventRTS->getPlayingHandle() == handle) { return true; } } for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getPlayingHandle() == handle) { + if (playing->m_audioEventRTS->getPlayingHandle() == handle) { return true; } } for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getPlayingHandle() == handle) { + if (playing->m_audioEventRTS->getPlayingHandle() == handle) { return true; } } @@ -1506,7 +1438,7 @@ Bool MilesAudioManager::isCurrentlyPlaying( AudioHandle handle ) AudioRequest *req = nullptr; for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ++ait) { req = *ait; - if (req && req->m_usePendingEvent && req->m_pendingEvent->getPlayingHandle() == handle) { + if (req->m_usePendingEvent && req->m_pendingEvent->getPlayingHandle() == handle) { return true; } } @@ -1515,9 +1447,9 @@ Bool MilesAudioManager::isCurrentlyPlaying( AudioHandle handle ) } //------------------------------------------------------------------------------------------------- -void MilesAudioManager::notifyOfAudioCompletion( UnsignedInt audioCompleted, UnsignedInt flags ) +void MilesAudioManager::notifyOfAudioCompletion( UnsignedInt handle, UnsignedInt flags ) { - PlayingAudio *playing = findPlayingAudioFrom(audioCompleted, flags); + PlayingAudio *playing = findPlayingAudioFrom(handle, flags); if (!playing) { DEBUG_CRASH(("Audio has completed playing, but we can't seem to find it. - jkmcd")); return; @@ -1568,46 +1500,60 @@ void MilesAudioManager::notifyOfAudioCompletion( UnsignedInt audioCompleted, Uns if (playing->m_type == PAT_Stream) { if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { playStream(playing->m_audioEventRTS, playing->m_stream); - return; } } - playing->m_status = PS_Stopped; // it will be cleaned up on the next frame update + // it will be cleaned up on the next frame update + InterlockedCompareExchange(reinterpret_cast(&playing->m_status), PS_Stopping, PS_Playing); } //------------------------------------------------------------------------------------------------- -PlayingAudio *MilesAudioManager::findPlayingAudioFrom( UnsignedInt audioCompleted, UnsignedInt flags ) +PlayingAudio *MilesAudioManager::findPlayingAudioFrom( UnsignedInt handle, UnsignedInt flags ) { std::list::iterator it; PlayingAudio *playing; if (flags == PAT_Sample) { - HSAMPLE sample = (HSAMPLE) audioCompleted; + HSAMPLE sample = (HSAMPLE) handle; + CriticalSectionClass::LockClass lock(m_playingSoundsCS); for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing && playing->m_sample == sample) { + if (playing->m_sample == sample) { return playing; } } } if (flags == PAT_3DSample) { - H3DSAMPLE sample3D = (H3DSAMPLE) audioCompleted; + H3DSAMPLE sample3D = (H3DSAMPLE) handle; + CriticalSectionClass::LockClass lock(m_playing3DSoundsCS); for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing && playing->m_3DSample == sample3D) { + if (playing->m_3DSample == sample3D) { return playing; } } } if (flags == PAT_Stream) { - HSTREAM stream = (HSTREAM) audioCompleted; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_stream == stream) { - return playing; + HSTREAM stream = (HSTREAM) handle; + { + CriticalSectionClass::LockClass lock(m_playingStreamsCS); + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing->m_stream == stream) { + return playing; + } + } + } + { + CriticalSectionClass::LockClass lock(m_fadingAudioCS); + for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); ++it) { + playing = *it; + if (playing->m_stream == stream) { + return playing; + } } } } @@ -1849,9 +1795,6 @@ Bool MilesAudioManager::doesViolateLimit( AudioEventRTS *event ) const std::list::const_iterator arIt; for (arIt = m_audioRequests.begin(); arIt != m_audioRequests.end(); ++arIt) { AudioRequest *req = (*arIt); - if (req == nullptr) { - continue; - } if( req->m_usePendingEvent ) { if( req->m_pendingEvent->getEventName() == event->getEventName() ) @@ -2048,16 +1991,11 @@ Bool MilesAudioManager::killLowestPrioritySoundImmediately( AudioEventRTS *event for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { PlayingAudio *playing = (*it); - if( !playing ) - { - continue; - } if( playing->m_audioEventRTS && playing->m_audioEventRTS == lowestPriorityEvent ) { //Release this 3D sound channel immediately because we are going to play another sound in it's place. - releasePlayingAudio( playing ); - m_playing3DSounds.erase( it ); + stopPlayingAudio(playing); return TRUE; } } @@ -2067,16 +2005,11 @@ Bool MilesAudioManager::killLowestPrioritySoundImmediately( AudioEventRTS *event for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { PlayingAudio *playing = (*it); - if( !playing ) - { - continue; - } if( playing->m_audioEventRTS && playing->m_audioEventRTS == lowestPriorityEvent ) { //Release this sound channel immediately because we are going to play another sound in it's place. - releasePlayingAudio( playing ); - m_playingSounds.erase( it ); + stopPlayingAudio(playing); return TRUE; } } @@ -2095,8 +2028,8 @@ void MilesAudioManager::adjustVolumeOfPlayingAudio(AsciiString eventName, Real n PlayingAudio *playing = nullptr; for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getEventName() == eventName) { - // Adjust it + if (playing->m_audioEventRTS->getEventName() == eventName) { + DEBUG_ASSERTCRASH(playing->m_sample, ("Sample is not expected to be null")); playing->m_audioEventRTS->setVolume(newVolume); AIL_sample_volume_pan(playing->m_sample, nullptr, &pan); AIL_set_sample_volume_pan(playing->m_sample, getEffectiveVolume(playing->m_audioEventRTS), pan); @@ -2105,8 +2038,8 @@ void MilesAudioManager::adjustVolumeOfPlayingAudio(AsciiString eventName, Real n for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getEventName() == eventName) { - // Adjust it + if (playing->m_audioEventRTS->getEventName() == eventName) { + DEBUG_ASSERTCRASH(playing->m_3DSample, ("3D Sample is not expected to be null")); playing->m_audioEventRTS->setVolume(newVolume); AIL_set_3D_sample_volume(playing->m_3DSample, getEffectiveVolume(playing->m_audioEventRTS)); } @@ -2114,8 +2047,8 @@ void MilesAudioManager::adjustVolumeOfPlayingAudio(AsciiString eventName, Real n for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getEventName() == eventName) { - // Adjust it + if (playing->m_audioEventRTS->getEventName() == eventName) { + DEBUG_ASSERTCRASH(playing->m_stream, ("Stream is not expected to be null")); playing->m_audioEventRTS->setVolume(newVolume); AIL_stream_volume_pan(playing->m_stream, nullptr, &pan); AIL_set_stream_volume_pan(playing->m_stream, getEffectiveVolume(playing->m_audioEventRTS), pan); @@ -2130,45 +2063,30 @@ void MilesAudioManager::removePlayingAudio( AsciiString eventName ) std::list::iterator it; PlayingAudio *playing = nullptr; - for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ) + for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getEventName() == eventName ) - { - releasePlayingAudio( playing ); - it = m_playingSounds.erase(it); - } - else + if( playing->m_audioEventRTS->getEventName() == eventName ) { - it++; + stopPlayingAudio(playing); } } - for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) + for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getEventName() == eventName ) - { - releasePlayingAudio( playing ); - it = m_playing3DSounds.erase(it); - } - else + if( playing->m_audioEventRTS->getEventName() == eventName ) { - it++; + stopPlayingAudio(playing); } } - for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ) + for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getEventName() == eventName ) + if( playing->m_audioEventRTS->getEventName() == eventName ) { - releasePlayingAudio( playing ); - it = m_playingStreams.erase(it); - } - else - { - it++; + stopPlayingAudio(playing); } } } @@ -2179,45 +2097,30 @@ void MilesAudioManager::removeAllDisabledAudio() std::list::iterator it; PlayingAudio *playing = nullptr; - for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ) + for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getVolume() == 0.0f ) + if( playing->m_audioEventRTS->getVolume() == 0.0f ) { - releasePlayingAudio( playing ); - it = m_playingSounds.erase(it); - } - else - { - it++; + stopPlayingAudio(playing); } } - for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) + for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getVolume() == 0.0f ) + if( playing->m_audioEventRTS->getVolume() == 0.0f ) { - releasePlayingAudio( playing ); - it = m_playing3DSounds.erase(it); - } - else - { - it++; + stopPlayingAudio(playing); } } - for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ) + for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getVolume() == 0.0f ) - { - releasePlayingAudio( playing ); - it = m_playingStreams.erase(it); - } - else + if( playing->m_audioEventRTS->getVolume() == 0.0f ) { - it++; + stopPlayingAudio(playing); } } } @@ -2226,12 +2129,8 @@ void MilesAudioManager::removeAllDisabledAudio() void MilesAudioManager::processRequestList() { std::list::iterator it; - for (it = m_audioRequests.begin(); it != m_audioRequests.end(); /* empty */) { + for (it = m_audioRequests.begin(); it != m_audioRequests.end(); ) { AudioRequest *req = (*it); - if (req == nullptr) { - continue; - } - if (!shouldProcessRequestThisFrame(req)) { adjustRequest(req); ++it; @@ -2249,122 +2148,122 @@ void MilesAudioManager::processRequestList() //------------------------------------------------------------------------------------------------- void MilesAudioManager::processPlayingList() { - // There are two types of processing we have to do here. - // 1. Move the item to the stopped list if it has become stopped. - // 2. Update the position of the audio if it is positional std::list::iterator it; PlayingAudio *playing; - for (it = m_playingSounds.begin(); it != m_playingSounds.end(); /* empty */) { + // + // Stop audio without locking. Calls into Miles. + // Update the position of the audio if it is positional. + // + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = (*it); - if (!playing) - { - it = m_playingSounds.erase(it); + + if (playing->m_status == PS_Stopping) { + stopPlayingAudio(playing); continue; } - if (playing->m_status == PS_Stopped) - { - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_playingSounds.erase(it); + if (playing->m_status == PS_Stopped) { + continue; } - else + + if (m_volumeHasChanged) { - if (m_volumeHasChanged) - { - adjustPlayingVolume(playing); - } - ++it; + adjustPlayingVolume(playing); } } - for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) - { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = (*it); - if (!playing) - { - it = m_playing3DSounds.erase(it); + + if (playing->m_status == PS_Stopping) { + stopPlayingAudio(playing); continue; } - if (playing->m_status == PS_Stopped) + if (playing->m_status == PS_Stopped) { + continue; + } + + if (m_volumeHasChanged) { - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_playing3DSounds.erase(it); + adjustPlayingVolume(playing); } - else + + const Coord3D *pos = getCurrentPositionFromEvent(playing->m_audioEventRTS); + if (pos) { - if (m_volumeHasChanged) + if( playing->m_audioEventRTS->isDead() ) { - adjustPlayingVolume(playing); + stopAudioEvent( playing->m_audioEventRTS->getPlayingHandle() ); } - - const Coord3D *pos = getCurrentPositionFromEvent(playing->m_audioEventRTS); - if (pos) + else { - if( playing->m_audioEventRTS->isDead() ) + Real volForConsideration = getEffectiveVolume(playing->m_audioEventRTS); + volForConsideration /= (m_sound3DVolume > 0.0f ? m_soundVolume : 1.0f); + Bool playAnyways = BitIsSet( playing->m_audioEventRTS->getAudioEventInfo()->m_type, ST_GLOBAL) + || playing->m_audioEventRTS->getAudioEventInfo()->m_priority == AP_CRITICAL; + if( volForConsideration < m_audioSettings->m_minVolume && !playAnyways ) { - stopAudioEvent( playing->m_audioEventRTS->getPlayingHandle() ); - it++; - continue; + stopPlayingAudio(playing); } else { - Real volForConsideration = getEffectiveVolume(playing->m_audioEventRTS); - volForConsideration /= (m_sound3DVolume > 0.0f ? m_soundVolume : 1.0f); - Bool playAnyways = BitIsSet( playing->m_audioEventRTS->getAudioEventInfo()->m_type, ST_GLOBAL) || playing->m_audioEventRTS->getAudioEventInfo()->m_priority == AP_CRITICAL; - if( volForConsideration < m_audioSettings->m_minVolume && !playAnyways ) - { - // don't want to get an additional callback for this sample - AIL_register_3D_EOS_callback(playing->m_3DSample, nullptr); - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_playing3DSounds.erase(it); - continue; - } - else - { - Real x = pos->x; - Real y = pos->y; - Real z = pos->z; - AIL_set_3D_position( playing->m_3DSample, x, y, z ); - } + Real x = pos->x; + Real y = pos->y; + Real z = pos->z; + DEBUG_ASSERTCRASH(playing->m_3DSample, ("3D Sample is not expected to be null")); + AIL_set_3D_position( playing->m_3DSample, x, y, z ); } } - else - { - AIL_register_3D_EOS_callback(playing->m_3DSample, nullptr); - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_playing3DSounds.erase(it); - continue; - } - - ++it; + } + else + { + stopPlayingAudio(playing); } } - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ) { + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = (*it); - if (!playing) - { - it = m_playingStreams.erase(it); + + if (playing->m_status == PS_Stopping) { + stopPlayingAudio(playing); continue; } - if (playing->m_status == PS_Stopped) - { - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_playingStreams.erase(it); + if (playing->m_status == PS_Stopped) { + continue; } - else + + if (m_volumeHasChanged) { - if (m_volumeHasChanged) + adjustPlayingVolume(playing); + } + } + + // + // Release audio with locking. Must not call into Miles! + // + releasePlayingAudioInListIfStopped(m_playingSounds, m_playingSoundsCS); + releasePlayingAudioInListIfStopped(m_playing3DSounds, m_playing3DSoundsCS); + releasePlayingAudioInListIfStopped(m_playingStreams, m_playingStreamsCS); + + // + // Transfer streams to fade list when signaled. + // + { + CriticalSectionClass::LockClass lock(m_playingStreamsCS); + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ) { + playing = (*it); + + if (playing->m_fade) { - adjustPlayingVolume(playing); + { + CriticalSectionClass::LockClass lock(m_fadingAudioCS); + m_fadingAudio.push_back(playing); + } + it = m_playingStreams.erase(it); + continue; } ++it; @@ -2395,9 +2294,6 @@ Bool MilesAudioManager::has3DSensitiveStreamsPlaying() const { const PlayingAudio *playing = (*it); - if ( ! playing ) - continue; - if ( playing->m_audioEventRTS->getAudioEventInfo()->m_soundType != AT_Music ) { return TRUE; @@ -2420,18 +2316,18 @@ void MilesAudioManager::processFadingList() std::list::iterator it; PlayingAudio *playing; - for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); /* emtpy */) { - playing = *it; - if (!playing) { + // + // Stop audio without locking. Calls into Miles. + // + for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); ++it) { + playing = (*it); + + if (playing->m_framesFaded >= getAudioSettings()->m_fadeAudioFrames) { + stopPlayingAudio(playing); continue; } - if (playing->m_framesFaded >= getAudioSettings()->m_fadeAudioFrames) { - playing->m_status = PS_Stopped; - playing->m_requestStop = true; - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_fadingAudio.erase(it); + if (playing->m_status == PS_Stopped) { continue; } @@ -2458,29 +2354,15 @@ void MilesAudioManager::processFadingList() AIL_set_stream_volume_pan(playing->m_stream, volume, 0.5f); break; } - } - - ++it; } -} -//------------------------------------------------------------------------------------------------- -void MilesAudioManager::processStoppedList() -{ - std::list::iterator it; - PlayingAudio *playing; - - for (it = m_stoppedAudio.begin(); it != m_stoppedAudio.end(); /* emtpy */) { - playing = *it; - if (playing) { - releasePlayingAudio(playing); - } - it = m_stoppedAudio.erase(it); - } + // + // Release audio with locking. Must not call into Miles! + // + releasePlayingAudioInListIfStopped(m_fadingAudio, m_fadingAudioCS); } - //------------------------------------------------------------------------------------------------- Bool MilesAudioManager::shouldProcessRequestThisFrame( AudioRequest *req ) const { @@ -2602,31 +2484,19 @@ void MilesAudioManager::closeAnySamplesUsingFile( const void *fileToClose ) std::list::iterator it; PlayingAudio *playing; - for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ) { + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { playing = *it; - if (!playing) { - continue; - } if (playing->m_file == fileToClose) { - releasePlayingAudio(playing); - it = m_playingSounds.erase(it); - } else { - ++it; + stopPlayingAudio(playing); } } - for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { playing = *it; - if (!playing) { - continue; - } if (playing->m_file == fileToClose) { - releasePlayingAudio(playing); - it = m_playing3DSounds.erase(it); - } else { - ++it; + stopPlayingAudio(playing); } } } @@ -2736,7 +2606,7 @@ Bool MilesAudioManager::startNextLoop( PlayingAudio *looping ) closeFile(looping->m_file); looping->m_file = nullptr; - if (looping->m_requestStop) { + if (looping->m_status != PS_Playing) { return false; } @@ -2748,9 +2618,7 @@ Bool MilesAudioManager::startNextLoop( PlayingAudio *looping ) // fake it out so that this sound appears done, but also so that it will not // delete the sound on completion (which would suck) looping->m_cleanupAudioEventRTS = false; - looping->m_requestStop = true; - looping->m_status = PS_Stopped; - + InterlockedCompareExchange(reinterpret_cast(&looping->m_status), PS_Stopping, PS_Playing); AudioRequest *req = allocateAudioRequest(true); req->m_pendingEvent = looping->m_audioEventRTS; @@ -2961,6 +2829,7 @@ void *MilesAudioManager::getHandleForBink() aud->m_type = PAT_Sample; if (!aud->m_sample) { + stopPlayingAudio(aud); releasePlayingAudio(aud); return nullptr; } @@ -2977,6 +2846,7 @@ void *MilesAudioManager::getHandleForBink() void MilesAudioManager::releaseHandleForBink() { if (m_binkHandle) { + stopPlayingAudio(m_binkHandle); releasePlayingAudio(m_binkHandle); m_binkHandle = nullptr; } diff --git a/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp b/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp index 761f4881186..c267c6f4e9b 100644 --- a/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp +++ b/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp @@ -1410,7 +1410,7 @@ void OpenALAudioManager::stopAllSpeech(void) //} //------------------------------------------------------------------------------------------------- -void OpenALAudioManager::nextMusicTrack(void) +AsciiString OpenALAudioManager::nextMusicTrack(void) { AsciiString trackName; std::list::iterator it; @@ -1430,10 +1430,12 @@ void OpenALAudioManager::nextMusicTrack(void) trackName = nextTrackName(trackName); AudioEventRTS newTrack(trackName); TheAudio->addAudioEvent(&newTrack); + + return trackName; } //------------------------------------------------------------------------------------------------- -void OpenALAudioManager::prevMusicTrack(void) +AsciiString OpenALAudioManager::prevMusicTrack(void) { AsciiString trackName; std::list::iterator it; @@ -1453,6 +1455,8 @@ void OpenALAudioManager::prevMusicTrack(void) trackName = prevTrackName(trackName); AudioEventRTS newTrack(trackName); TheAudio->addAudioEvent(&newTrack); + + return trackName; } //------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp index 41a5e929890..ddcf34c2296 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp @@ -2182,51 +2182,53 @@ void W3DModelDraw::adjustAnimation(const ModelConditionInfo* prevState, Real pre const W3DAnimationInfo& animInfo = m_curState->m_animations[m_whichAnimInCurState]; - HAnimClass* animHandle = animInfo.getAnimHandle(); // note that this now returns an ADDREFED handle, which must be released by the caller! - if (m_renderObject && animHandle) + if (m_renderObject) { - Int startFrame = 0; - if (m_curState->m_mode == RenderObjClass::ANIM_MODE_ONCE_BACKWARDS || - m_curState->m_mode == RenderObjClass::ANIM_MODE_LOOP_BACKWARDS) + HAnimClass* animHandle = animInfo.getAnimHandle(); // note that this now returns an ADDREFED handle, which must be released by the caller! + if (animHandle) { - startFrame = animHandle->Get_Num_Frames()-1; - } + Int startFrame = 0; + if (m_curState->m_mode == RenderObjClass::ANIM_MODE_ONCE_BACKWARDS || + m_curState->m_mode == RenderObjClass::ANIM_MODE_LOOP_BACKWARDS) + { + startFrame = animHandle->Get_Num_Frames()-1; + } - if (testFlagBit(m_curState->m_flags, RANDOMIZE_START_FRAME)) - { - startFrame = GameClientRandomValue(0, animHandle->Get_Num_Frames()-1); - } - else if (testFlagBit(m_curState->m_flags, START_FRAME_FIRST)) - { - startFrame = 0; - } - else if (testFlagBit(m_curState->m_flags, START_FRAME_LAST)) - { - startFrame = animHandle->Get_Num_Frames()-1; - } - // order is important here: MAINTAIN_FRAME_ACROSS_STATES is overridden by the other bits, above. - else if (isAnyMaintainFrameFlagSet(m_curState->m_flags) && - prevState && - prevState != m_curState && - isAnyMaintainFrameFlagSet(prevState->m_flags) && - isCommonMaintainFrameFlagSet(m_curState->m_flags, prevState->m_flags) && - prevAnimFraction >= 0.0) - { - startFrame = REAL_TO_INT(prevAnimFraction * animHandle->Get_Num_Frames()-1); - } + if (testFlagBit(m_curState->m_flags, RANDOMIZE_START_FRAME)) + { + startFrame = GameClientRandomValue(0, animHandle->Get_Num_Frames()-1); + } + else if (testFlagBit(m_curState->m_flags, START_FRAME_FIRST)) + { + startFrame = 0; + } + else if (testFlagBit(m_curState->m_flags, START_FRAME_LAST)) + { + startFrame = animHandle->Get_Num_Frames()-1; + } + // order is important here: MAINTAIN_FRAME_ACROSS_STATES is overridden by the other bits, above. + else if (isAnyMaintainFrameFlagSet(m_curState->m_flags) && + prevState && + prevState != m_curState && + isAnyMaintainFrameFlagSet(prevState->m_flags) && + isCommonMaintainFrameFlagSet(m_curState->m_flags, prevState->m_flags) && + prevAnimFraction >= 0.0) + { + startFrame = REAL_TO_INT(prevAnimFraction * animHandle->Get_Num_Frames()-1); + } - m_renderObject->Set_Animation(animHandle, startFrame, m_curState->m_mode); - REF_PTR_RELEASE(animHandle); - animHandle = nullptr; + m_renderObject->Set_Animation(animHandle, startFrame, m_curState->m_mode); + REF_PTR_RELEASE(animHandle); + animHandle = nullptr; - if (m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) - { - HLodClass *hlod = (HLodClass*)m_renderObject; - Real factor = GameClientRandomValueReal( m_curState->m_animMinSpeedFactor, m_curState->m_animMaxSpeedFactor ); - hlod->Set_Animation_Frame_Rate_Multiplier( factor ); + if (m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) + { + HLodClass *hlod = (HLodClass*)m_renderObject; + Real factor = GameClientRandomValueReal( m_curState->m_animMinSpeedFactor, m_curState->m_animMaxSpeedFactor ); + hlod->Set_Animation_Frame_Rate_Multiplier( factor ); + } } } - } else { diff --git a/Core/Libraries/Include/Lib/BaseType.h b/Core/Libraries/Include/Lib/BaseType.h index 70a43592989..71eee401d59 100644 --- a/Core/Libraries/Include/Lib/BaseType.h +++ b/Core/Libraries/Include/Lib/BaseType.h @@ -139,6 +139,7 @@ inline Real deg2rad(Real rad) { return rad * (PI/180); } //----------------------------------------------------------------------------- // TheSuperHackers @build xezon 17/03/2025 Renames BitTest to BitIsSet to prevent conflict with BitTest macro from winnt.h #define BitIsSet( x, i ) ( ( (x) & (i) ) != 0 ) +#define BitsAreSet( x, i ) ( ( (x) & (i) ) == (i) ) #define BitSet( x, i ) ( (x) |= (i) ) #define BitClear( x, i ) ( (x ) &= ~(i) ) #define BitToggle( x, i ) ( (x) ^= (i) ) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/hcanim.cpp b/Core/Libraries/Source/WWVegas/WW3D2/hcanim.cpp index 704898a8875..3bc866b7517 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/hcanim.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/hcanim.cpp @@ -293,10 +293,6 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) /* ** Now, read in all of the other chunks (motion channels). */ - TimeCodedMotionChannelClass * tc_chan; - AdaptiveDeltaMotionChannelClass * ad_chan; - TimeCodedBitChannelClass * newbitchan; - while (cload.Open_Chunk()) { switch (cload.Cur_Chunk_ID()) { @@ -306,7 +302,8 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) switch ( Flavor ) { case ANIM_FLAVOR_TIMECODED: - + { + TimeCodedMotionChannelClass* tc_chan = nullptr; if (!read_channel(cload,&tc_chan)) { goto Error; } @@ -322,8 +319,11 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) } break; + } case ANIM_FLAVOR_ADAPTIVE_DELTA: + { + AdaptiveDeltaMotionChannelClass* ad_chan = nullptr; if (!read_channel(cload,&ad_chan)) { goto Error; } @@ -338,10 +338,13 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) WWDEBUG_SAY(("ERROR! animation %s indexes a bone not present in the model. Please re-export!",Name)); } break; + } } break; case W3D_CHUNK_COMPRESSED_BIT_CHANNEL: + { + TimeCodedBitChannelClass* newbitchan = nullptr; if (!read_bit_channel(cload,&newbitchan)) { goto Error; } @@ -357,6 +360,7 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) } break; + } default: break; @@ -387,20 +391,32 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) *=============================================================================================*/ bool HCompressedAnimClass::read_channel(ChunkLoadClass & cload,TimeCodedMotionChannelClass * * newchan) { - *newchan = W3DNEW TimeCodedMotionChannelClass; - bool result = (*newchan)->Load_W3D(cload); - - return result; - + TimeCodedMotionChannelClass* channel = W3DNEW TimeCodedMotionChannelClass; + if (channel->Load_W3D(cload)) + { + *newchan = channel; + return true; + } + else + { + delete channel; + return false; + } } bool HCompressedAnimClass::read_channel(ChunkLoadClass & cload,AdaptiveDeltaMotionChannelClass * * newchan) { - *newchan = W3DNEW AdaptiveDeltaMotionChannelClass; - bool result = (*newchan)->Load_W3D(cload); - - return result; - + AdaptiveDeltaMotionChannelClass* channel = W3DNEW AdaptiveDeltaMotionChannelClass; + if (channel->Load_W3D(cload)) + { + *newchan = channel; + return true; + } + else + { + delete channel; + return false; + } } @@ -483,11 +499,17 @@ void HCompressedAnimClass::add_channel(AdaptiveDeltaMotionChannelClass * newchan *=============================================================================================*/ bool HCompressedAnimClass::read_bit_channel(ChunkLoadClass & cload,TimeCodedBitChannelClass * * newchan) { - *newchan = W3DNEW TimeCodedBitChannelClass; - bool result = (*newchan)->Load_W3D(cload); - - return result; - + TimeCodedBitChannelClass* channel = W3DNEW TimeCodedBitChannelClass; + if (channel->Load_W3D(cload)) + { + *newchan = channel; + return true; + } + else + { + delete channel; + return false; + } } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp index 681c4c0b0fa..9b5970062ee 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp @@ -191,7 +191,7 @@ void SegLineRendererClass::Set_Texture_Tile_Factor(float factor) ///@todo: I raised this number and didn't see much difference on our min-spec. -MW const static float MAX_LINE_TILING_FACTOR = 50.0f; if (factor > MAX_LINE_TILING_FACTOR) { - WWDEBUG_SAY(("Texture (%s) Tile Factor (%.2f) too large in SegLineRendererClass!", Get_Texture()->Get_Texture_Name().str(), TextureTileFactor)); + WWDEBUG_SAY(("Texture (%s) Tile Factor (%.2f) too large in SegLineRendererClass!", Peek_Texture()->Get_Texture_Name().str(), TextureTileFactor)); factor = MAX_LINE_TILING_FACTOR; } else { factor = MAX(factor, 0.0f); diff --git a/Dependencies/Utility/CMakeLists.txt b/Dependencies/Utility/CMakeLists.txt index a810813bd62..90acedcaec3 100644 --- a/Dependencies/Utility/CMakeLists.txt +++ b/Dependencies/Utility/CMakeLists.txt @@ -4,6 +4,7 @@ set(UTILITY_SRC Utility/endian_compat.h Utility/fstream_adapter.h Utility/hash_map_adapter.h + Utility/interlocked_adapter.h Utility/intrin_compat.h Utility/iostream_adapter.h Utility/mem_compat.h diff --git a/Dependencies/Utility/Utility/interlocked_adapter.h b/Dependencies/Utility/Utility/interlocked_adapter.h new file mode 100644 index 00000000000..45da6a2f9cb --- /dev/null +++ b/Dependencies/Utility/Utility/interlocked_adapter.h @@ -0,0 +1,28 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#if defined(_MSC_VER) && _MSC_VER < 1300 + +inline long InterlockedCompareExchange(long volatile *Destination, long Exchange, long Comparand) +{ + return (long)InterlockedCompareExchange((PVOID*)Destination, (PVOID)Exchange, (PVOID)Comparand); +} + +#endif diff --git a/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h b/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h index 28ecc024578..76c736131a0 100644 --- a/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h +++ b/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h @@ -354,9 +354,84 @@ EMPTY_DTOR(MetaMapRec) class MetaEventTranslator : public GameMessageTranslator { private: - - Int m_lastKeyDown; // really a MappableKeyType - Int m_lastModState; // really a MappableKeyModState + struct KeyDownInfo + { + KeyDownInfo() : m_modStateBits(0) {} + + static UnsignedInt getMaxKeyModStateCount() + { + return 7; + } + + static MappableKeyModState toKeyModState(UnsignedInt index) + { + switch (index) + { + case 0: return CTRL; + case 1: return ALT; + case 2: return SHIFT; + case 3: return CTRL_ALT; + case 4: return SHIFT_CTRL; + case 5: return SHIFT_ALT; + case 6: return SHIFT_ALT_CTRL; + } + return NONE; + } + + static UnsignedInt toIndex(MappableKeyModState modState) + { + switch (modState) + { + case CTRL: return 0; + case ALT: return 1; + case SHIFT: return 2; + case CTRL_ALT: return 3; + case SHIFT_CTRL: return 4; + case SHIFT_ALT: return 5; + case SHIFT_ALT_CTRL: return 6; + } + return 7; + } + + Bool isKeyDown() const + { + return m_modStateBits != 0; + } + + MappableKeyModState getKeyModState(UnsignedInt index) + { + if (BitIsSet(m_modStateBits, 1 << index)) + { + return toKeyModState(index); + } + return NONE; + } + + void clearKeyModState(UnsignedInt index) + { + BitClear(m_modStateBits, 1 << index); + } + + Bool hasKeyModState(MappableKeyModState modState) const + { + return BitIsSet(m_modStateBits, 1 << toIndex(modState)); + } + + void setKeyModState(MappableKeyModState modState) + { + BitSet(m_modStateBits, 1 << toIndex(modState)); + } + + void clearKeyModState(MappableKeyModState modState) + { + BitClear(m_modStateBits, 1 << toIndex(modState)); + } + + private: + UnsignedByte m_modStateBits; ///< Fits all combinations of CTRL+ALT+SHIFT, storing 1 bit for each + }; + + KeyDownInfo m_keyDownInfos[KEY_COUNT]; enum { NUM_MOUSE_BUTTONS = 3 }; ICoord2D m_mouseDownPosition[NUM_MOUSE_BUTTONS]; @@ -366,6 +441,16 @@ class MetaEventTranslator : public GameMessageTranslator MetaEventTranslator(); virtual ~MetaEventTranslator() override; virtual GameMessageDisposition translateGameMessage(const GameMessage *msg) override; + +private: + void onMouseEvent(const GameMessage *msg); + + void onKeyEvent(const GameMessage *msg, GameMessageDisposition &disp); + void onKeyModStateRemoved(GameMessageDisposition &disp, MappableKeyModState keyModState); + void onKeyPressed(GameMessageDisposition &disp, Int systemKeyState, MappableKeyType keyType, MappableKeyModState keyModState); + + static MappableKeyType getActionKeyType(Int systemKey); ///< CRTL, ALT, SHIFT will be treated as MK_NONE + static MappableKeyModState getKeyModState(Int systemKeyState); ///< Extract CTRL, ALT, SHIFT key mod state }; //----------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Include/GameLogic/Object.h b/Generals/Code/GameEngine/Include/GameLogic/Object.h index 25c9590f8a6..dc17b8eae67 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Object.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Object.h @@ -717,7 +717,7 @@ class Object : public Thing, public Snapshot Object* m_containedBy; /**< an object can only be contained by at most one other object, this is that object (if present) */ - ObjectID m_xferContainedByID; ///< xfer uses IDs to store pointers and looks them up after + ObjectID m_containedByID; ///< ID of the object we're contained by; only to be used when m_containedBy cannot be used UnsignedInt m_containedByFrame; ///< frame we were contained by m_containedBy Real m_constructionPercent; ///< for objects being built ... this is the amount completed (0.0 to 100.0) diff --git a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp index 1d01553023c..7c66f5a2850 100644 --- a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp @@ -285,6 +285,9 @@ GameEngine::~GameEngine() delete TheSubsystemList; TheSubsystemList = nullptr; + delete TheSkirmishGameInfo; + TheSkirmishGameInfo = nullptr; + delete TheNetwork; TheNetwork = nullptr; diff --git a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp index 9ac294382e3..3d3bc513fd7 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -2979,8 +2979,8 @@ void Player::removeUpgrade( const UpgradeTemplate *upgradeTemplate ) if( upgrade->getStatus() == UPGRADE_STATUS_COMPLETE ) onUpgradeRemoved(); + deleteInstance(upgrade); } - } diff --git a/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp b/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp index dc6cb96aab6..9dd149c1adf 100644 --- a/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp +++ b/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp @@ -102,6 +102,8 @@ static void embedPristineMap( AsciiString map, Xfer *xfer ) if( file->read( buffer, fileSize ) != fileSize ) { + delete[] buffer; + DEBUG_CRASH(( "embedPristineMap - Error reading from file '%s'", map.str() )); throw SC_INVALID_DATA; @@ -159,6 +161,8 @@ static void embedInUseMap( AsciiString map, Xfer *xfer ) if( fread( buffer, 1, fileSize, fp ) != fileSize ) { + delete[] buffer; + DEBUG_CRASH(( "embedInUseMap - Error reading from file '%s'", map.str() )); throw SC_INVALID_DATA; @@ -214,6 +218,8 @@ static void extractAndSaveMap( AsciiString mapToSave, Xfer *xfer ) if( fwrite( buffer, 1, dataSize, fp ) != dataSize ) { + delete[] buffer; + DEBUG_CRASH(( "extractAndSaveMap - Error writing to file '%s'", mapToSave.str() )); throw SC_INVALID_DATA; diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index b657b769b4d..ae4b136dbb1 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -4521,8 +4521,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_MUSIC_NEXT_TRACK: { - TheAudio->nextMusicTrack(); - TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", TheAudio->getMusicTrackName().str()) ); + AsciiString trackName = TheAudio->nextMusicTrack(); + TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", trackName.str()) ); disp = DESTROY_MESSAGE; break; } @@ -4531,8 +4531,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_MUSIC_PREV_TRACK: { - TheAudio->prevMusicTrack(); - TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", TheAudio->getMusicTrackName().str()) ); + AsciiString trackName = TheAudio->prevMusicTrack(); + TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", trackName.str()) ); disp = DESTROY_MESSAGE; break; } diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index 5a0aa8e7e0a..b278e4b3fab 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -340,9 +340,7 @@ static const FieldParse TheMetaMapFieldParseTable[] = // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- -MetaEventTranslator::MetaEventTranslator() : - m_lastKeyDown(MK_NONE), - m_lastModState(0) +MetaEventTranslator::MetaEventTranslator() { for (Int i = 0; i < NUM_MOUSE_BUTTONS; ++i) { m_nextUpShouldCreateDoubleClick[i] = FALSE; @@ -400,194 +398,293 @@ static Bool isMessageUsable(CommandUsableInType usableIn) GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessage *msg) { GameMessageDisposition disp = KEEP_MESSAGE; - GameMessage::Type t = msg->getType(); + const GameMessage::Type t = msg->getType(); if (t == GameMessage::MSG_RAW_KEY_DOWN || t == GameMessage::MSG_RAW_KEY_UP) { - MappableKeyType key = (MappableKeyType)msg->getArgument(0)->integer; - Int keyState = msg->getArgument(1)->integer; + onKeyEvent(msg, disp); + } + else if (t > GameMessage::MSG_RAW_MOUSE_BEGIN && t < GameMessage::MSG_RAW_MOUSE_END ) + { + onMouseEvent(msg); + } - // for our purposes here, we don't care to distinguish between right and left keys, - // so just fudge a little to simplify things. - Int newModState = 0; + return disp; +} - if( keyState & KEY_STATE_CONTROL ) +//------------------------------------------------------------------------------------------------- +void MetaEventTranslator::onMouseEvent(const GameMessage *msg) +{ + Int index = 3; + switch (msg->getType()) + { + case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN: { - newModState |= CTRL; + --index; + m_mouseDownPosition[index] = msg->getArgument(0)->pixel; + m_nextUpShouldCreateDoubleClick[index] = FALSE; + break; } - if( keyState & KEY_STATE_SHIFT ) + case GameMessage::MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK: { - newModState |= SHIFT; + --index; + m_nextUpShouldCreateDoubleClick[index] = TRUE; + break; } - if( keyState & KEY_STATE_ALT ) + case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP: { - newModState |= ALT; + --index; + + constexpr const GameMessage::Type SingleClickMessages[3] = + { + GameMessage::MSG_MOUSE_LEFT_CLICK, + GameMessage::MSG_MOUSE_MIDDLE_CLICK, + GameMessage::MSG_MOUSE_RIGHT_CLICK, + }; + constexpr const GameMessage::Type DoubleClickMessages[3] = + { + GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK, + GameMessage::MSG_MOUSE_MIDDLE_DOUBLE_CLICK, + GameMessage::MSG_MOUSE_RIGHT_DOUBLE_CLICK, + }; + + const ICoord2D location = msg->getArgument(0)->pixel; + const GameMessage::Type messageType = m_nextUpShouldCreateDoubleClick[index] ? DoubleClickMessages[index] : SingleClickMessages[index]; + GameMessage *newMessage = TheMessageStream->insertMessage(messageType, const_cast(msg)); + + IRegion2D pixelRegion; + buildRegion( &m_mouseDownPosition[index], &location, &pixelRegion ); + if (abs(pixelRegion.hi.x - pixelRegion.lo.x) < TheMouse->m_dragTolerance && + abs(pixelRegion.hi.y - pixelRegion.lo.y) < TheMouse->m_dragTolerance) + { + pixelRegion.hi.x = pixelRegion.lo.x; + pixelRegion.hi.y = pixelRegion.lo.y; + } + + newMessage->appendPixelRegionArgument( pixelRegion ); + + // append the modifier keys to the message. + newMessage->appendIntegerArgument( msg->getArgument(1)->integer ); + + // append the time to the message. + //newMessage->appendIntegerArgument( msg->getArgument(2)->integer ); + break; } + } +} + +//------------------------------------------------------------------------------------------------- +void MetaEventTranslator::onKeyEvent(const GameMessage *msg, GameMessageDisposition &disp) +{ + const Int systemKey = msg->getArgument(0)->integer; + const Int systemKeyState = msg->getArgument(1)->integer; + + const MappableKeyType keyType = getActionKeyType(systemKey); + const MappableKeyModState keyModState = getKeyModState(systemKeyState); + + const Bool modStateRemoved = (keyType == MK_NONE) && (msg->getType() == GameMessage::MSG_RAW_KEY_UP); - for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + if (modStateRemoved) + { + onKeyModStateRemoved(disp, keyModState); + } + else + { + onKeyPressed(disp, systemKeyState, keyType, keyModState); + } +} + +//------------------------------------------------------------------------------------------------- +void MetaEventTranslator::onKeyModStateRemoved(GameMessageDisposition &disp, MappableKeyModState keyModState) +{ + // TheSuperHackers @fix The key handler now ignores the order in which modifier keys are released. + // This avoids frustrating experiences where a wrong button release order would skip an important key event. + + for (Int keyDownIndex = 0; keyDownIndex < ARRAY_SIZE(m_keyDownInfos); ++keyDownIndex) + { + const MappableKeyType keyDown = (MappableKeyType)keyDownIndex; + KeyDownInfo &keyDownInfo = m_keyDownInfos[keyDownIndex]; + + if (!keyDownInfo.isKeyDown()) + continue; + + for (UnsignedInt modStateIndex = 0; modStateIndex < KeyDownInfo::getMaxKeyModStateCount(); ++modStateIndex) { - if (!isMessageUsable(map->m_usableIn)) + const MappableKeyModState keyDownModState = keyDownInfo.getKeyModState(modStateIndex); + + if (keyDownModState == NONE) continue; - // check for the special case of mods-only-changed. - if ( - map->m_key == MK_NONE && - newModState != m_lastModState && - ( - (map->m_transition == UP && map->m_modState == m_lastModState) || - (map->m_transition == DOWN && map->m_modState == newModState) - ) - ) - { - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() Mods-only change: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); - disp = DESTROY_MESSAGE; - break; - } + if (BitsAreSet(keyModState, keyDownModState)) + continue; - // ok, now check for "normal" key transitions. - if ( - map->m_key == key && - map->m_modState == newModState && - ( - (map->m_transition == UP && (keyState & KEY_STATE_UP)) || - (map->m_transition == DOWN && (keyState & KEY_STATE_DOWN)) //|| - //(map->m_transition == DOUBLEDOWN && (keyState & KEY_STATE_DOWN) && m_lastKeyDown == key) - ) - ) + // Forget that this key and mod state are pressed. + keyDownInfo.clearKeyModState(modStateIndex); + + for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) { + if (!isMessageUsable(map->m_usableIn)) + continue; - if( keyState & KEY_STATE_AUTOREPEAT ) - { - // if it's an autorepeat of a "known" key, don't generate the meta-event, - // but DO eat the keystroke so no one else can mess with it - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() auto-repeat: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - } - else - { + const Bool isMatchingKeyCombo = map->m_key == keyDown && map->m_modState == keyDownModState; + const Bool isTransitionUp = map->m_transition == UP; - // THIS IS A GREASY HACK... MESSAGE SHOULD BE HANDLED IN A TRANSLATOR, BUT DURING CINEMATICS THE TRANSLATOR IS DISABLED - if( map->m_meta == GameMessage::MSG_META_TOGGLE_FAST_FORWARD_REPLAY) - { - #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)//may be defined in GameCommon.h - if( TheGlobalData ) - #else - if( TheGlobalData && TheGameLogic->isInReplayGame()) - #endif - { - if ( TheWritableGlobalData ) - TheWritableGlobalData->m_TiVOFastMode = 1 - TheGlobalData->m_TiVOFastMode; - - if ( TheInGameUI ) - TheInGameUI->messageNoFormat( TheGlobalData->m_TiVOFastMode - ? TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_ON", L"Fast Forward is on") - : TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_OFF", L"Fast Forward is off") - ); - } - disp = KEEP_MESSAGE; // cause for goodness sake, this key gets used a lot by non-replay hotkeys - break; - } - - - /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() normal: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - } + if (!(isMatchingKeyCombo && isTransitionUp)) + continue; + + TheMessageStream->appendMessage(map->m_meta); disp = DESTROY_MESSAGE; - break; } } - - if (t == GameMessage::MSG_RAW_KEY_DOWN) - m_lastKeyDown = key; - m_lastModState = newModState; } +} +//------------------------------------------------------------------------------------------------- +void MetaEventTranslator::onKeyPressed(GameMessageDisposition &disp, Int systemKeyState, MappableKeyType keyType, MappableKeyModState keyModState) +{ + // TheSuperHackers @info The regular key handler only triggers events when the mapped key is pressed, + // not when the modifier (CTRL, ALT, SHIFT) is pressed, unless the key is MK_NONE. - if (t > GameMessage::MSG_RAW_MOUSE_BEGIN && t < GameMessage::MSG_RAW_MOUSE_END ) + for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) { - Int index = 3; - switch (t) - { - case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN: - { - --index; - m_mouseDownPosition[index] = msg->getArgument(0)->pixel; - m_nextUpShouldCreateDoubleClick[index] = FALSE; - break; - } + if (!isMessageUsable(map->m_usableIn)) + continue; + + const Bool isMatchingKeyCombo = map->m_key == keyType && map->m_modState == keyModState; + const Bool isMatchingTransitionUp = map->m_transition == UP && (systemKeyState & KEY_STATE_UP) != 0; + const Bool isMatchingTransitionDown = map->m_transition == DOWN && (systemKeyState & KEY_STATE_DOWN) != 0; + //const Bool isMatchingTransitionDoubleDown = map->m_transition == DOUBLEDOWN && (systemKeyState & KEY_STATE_DOWN) && m_lastKeyDown == key; - case GameMessage::MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK: + if (isMatchingKeyCombo && (isMatchingTransitionUp || isMatchingTransitionDown /*|| isMatchingTransitionDoubleDown*/)) + { + if( systemKeyState & KEY_STATE_AUTOREPEAT ) { - --index; - m_nextUpShouldCreateDoubleClick[index] = TRUE; - break; + // if it's an autorepeat of a "known" key, don't generate the meta-event, + // but DO eat the keystroke so no one else can mess with it + //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() auto-repeat: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); } - - case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP: + else { - --index; - - constexpr const GameMessage::Type SingleClickMessages[3] = + // THIS IS A GREASY HACK... MESSAGE SHOULD BE HANDLED IN A TRANSLATOR, BUT DURING CINEMATICS THE TRANSLATOR IS DISABLED + if( map->m_meta == GameMessage::MSG_META_TOGGLE_FAST_FORWARD_REPLAY) { - GameMessage::MSG_MOUSE_LEFT_CLICK, - GameMessage::MSG_MOUSE_MIDDLE_CLICK, - GameMessage::MSG_MOUSE_RIGHT_CLICK, - }; - constexpr const GameMessage::Type DoubleClickMessages[3] = - { - GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK, - GameMessage::MSG_MOUSE_MIDDLE_DOUBLE_CLICK, - GameMessage::MSG_MOUSE_RIGHT_DOUBLE_CLICK, - }; - - const ICoord2D location = msg->getArgument(0)->pixel; - const GameMessage::Type messageType = m_nextUpShouldCreateDoubleClick[index] ? DoubleClickMessages[index] : SingleClickMessages[index]; - GameMessage *newMessage = TheMessageStream->insertMessage(messageType, const_cast(msg)); - - IRegion2D pixelRegion; - buildRegion( &m_mouseDownPosition[index], &location, &pixelRegion ); - if (abs(pixelRegion.hi.x - pixelRegion.lo.x) < TheMouse->m_dragTolerance && - abs(pixelRegion.hi.y - pixelRegion.lo.y) < TheMouse->m_dragTolerance) - { - pixelRegion.hi.x = pixelRegion.lo.x; - pixelRegion.hi.y = pixelRegion.lo.y; + #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)//may be defined in GameCommon.h + if( TheGlobalData ) + #else + if( TheGlobalData && TheGameLogic->isInReplayGame()) + #endif + { + if ( TheWritableGlobalData ) + TheWritableGlobalData->m_TiVOFastMode = 1 - TheGlobalData->m_TiVOFastMode; + + if ( TheInGameUI ) + TheInGameUI->messageNoFormat( TheGlobalData->m_TiVOFastMode + ? TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_ON", L"Fast Forward is on") + : TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_OFF", L"Fast Forward is off") + ); + } + disp = KEEP_MESSAGE; // cause for goodness sake, this key gets used a lot by non-replay hotkeys + break; } - newMessage->appendPixelRegionArgument( pixelRegion ); + /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); + //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() normal: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); + } - // append the modifier keys to the message. - newMessage->appendIntegerArgument( msg->getArgument(1)->integer ); + disp = DESTROY_MESSAGE; + break; + } + } - // append the time to the message. - //newMessage->appendIntegerArgument( msg->getArgument(2)->integer ); - break; - } + if (systemKeyState & KEY_STATE_DOWN) + { +#ifdef DUMP_ALL_KEYS_TO_LOG + WideChar Wkey = TheKeyboard->getPrintableKey(keyType, 0); + UnicodeString uKey; + uKey.set(&Wkey); + AsciiString aKey; + aKey.translate(uKey); + DEBUG_LOG(("^%s ", aKey.str())); +#endif + + if (keyModState != NONE) + { + // Remember that this key and mod state are pressed. + m_keyDownInfos[keyType].setKeyModState(keyModState); + } + } + else + { + if (keyModState != NONE) + { + DEBUG_ASSERTCRASH(keyType != MK_NONE, ("Key is expected to be not MK_NONE")); + // Forget that this key and mod state are pressed. + m_keyDownInfos[keyType].clearKeyModState(keyModState); } + } +} +//------------------------------------------------------------------------------------------------- +MappableKeyType MetaEventTranslator::getActionKeyType(Int systemKey) +{ + switch (systemKey) + { + case KEY_LCTRL: + case KEY_RCTRL: + case KEY_LSHIFT: + case KEY_RSHIFT: + case KEY_LALT: + case KEY_RALT: + return MK_NONE; + default: + return (MappableKeyType)systemKey; } +} - return disp; +//------------------------------------------------------------------------------------------------- +MappableKeyModState MetaEventTranslator::getKeyModState(Int systemKeyState) +{ + // for our purposes here, we don't care to distinguish between right and left keys, + // so just fudge a little to simplify things. + Int keyModState = 0; + + if( systemKeyState & KEY_STATE_CONTROL ) + { + keyModState |= CTRL; + } + + if( systemKeyState & KEY_STATE_SHIFT ) + { + keyModState |= SHIFT; + } + + if( systemKeyState & KEY_STATE_ALT ) + { + keyModState |= ALT; + } + + return (MappableKeyModState)keyModState; } //------------------------------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp b/Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp index c9c98aa8b87..49d3b6f9c5a 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp @@ -535,7 +535,12 @@ void SidesList::prepareForMP_or_Skirmish() break; } } - if (curSide == -1) continue; + if (curSide == -1) + { + deleteInstance(scripts[i]); + scripts[i] = nullptr; + continue; + } deleteInstance(getSkirmishSideInfo(curSide)->getScriptList()); getSkirmishSideInfo(curSide)->setScriptList(scripts[i]); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 64bb6fe4c6d..4b7fca0ea86 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -174,7 +174,7 @@ Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatu m_physics(nullptr), m_geometryInfo(tt->getTemplateGeometryInfo()), m_containedBy(nullptr), - m_xferContainedByID(INVALID_ID), + m_containedByID(INVALID_ID), m_containedByFrame(0), m_behaviors(nullptr), m_body(nullptr), @@ -622,6 +622,22 @@ void Object::onContainedBy( Object *containedBy ) clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); m_containedBy = containedBy; m_containedByFrame = TheGameLogic->getFrame(); + +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @info Set INVALID_ID if the container object was destroyed + // to indicate that the pointer will become a dangling pointer in the next frame. + if (containedBy && !containedBy->isDestroyed()) + { + m_containedByID = containedBy->getID(); + } + else + { + m_containedByID = INVALID_ID; + } +#else + DEBUG_ASSERTCRASH(containedBy == nullptr || !containedBy->isDestroyed(), + ("Object::onContainedBy - Adding into a destroyed container")); +#endif } //------------------------------------------------------------------------------------------------- @@ -632,6 +648,10 @@ void Object::onRemovedFrom( Object *removedFrom ) clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_MASKED, OBJECT_STATUS_UNSELECTABLE ) ); m_containedBy = nullptr; m_containedByFrame = 0; + +#if RETAIL_COMPATIBLE_CRC + m_containedByID = INVALID_ID; +#endif } //------------------------------------------------------------------------------------------------- @@ -682,9 +702,33 @@ void Object::onDestroy() { // This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up. - if( m_containedBy && m_containedBy->getContain() ) + if (m_containedBy) { - m_containedBy->getContain()->removeFromContain( this ); +#if RETAIL_COMPATIBLE_CRC + if (m_containedByID == INVALID_ID) + { + // TheSuperHackers @bugfix Caball009 25/05/2026 Due to a potential use-after-free bug that cannot be fixed + // with retail compatibility, the 'contained by' pointer of this object may point to an already destroyed object. + // Avoid removing this object from the contain list, because it could crash the game, + // as the begin / end iterator for STLPort and MSVC std::list implementations depends on dynamically allocated memory. + DEBUG_CRASH(("container object must be valid; this looks like use-after-free")); + } + else + { + DEBUG_ASSERTCRASH(TheGameLogic->findObjectByID(m_containedByID) == m_containedBy, + ("contained by pointer is out of sync with contained by ID")); + + if (ContainModuleInterface* contain = m_containedBy->getContain()) + { + contain->removeFromContain(this); + } + } +#else + if (ContainModuleInterface* contain = m_containedBy->getContain()) + { + contain->removeFromContain(this); + } +#endif } // @@ -3730,16 +3774,18 @@ void Object::xfer( Xfer *xfer ) // No, the contain module is just going to friend_ reach in and set this for us. // Containers more complicated than Open (like Tunnel) can't do that. Our variable, // our responsibility. +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @tweak Contained by ID is already set with retail compatibility; don't overwrite it. if( xfer->getXferMode() == XFER_SAVE ) { if( m_containedBy != nullptr ) - m_xferContainedByID = m_containedBy->getID(); + m_containedByID = m_containedBy->getID(); else - m_xferContainedByID = INVALID_ID; + m_containedByID = INVALID_ID; } +#endif - - xfer->xferObjectID( &m_xferContainedByID ); + xfer->xferObjectID( &m_containedByID ); } // contained by frame @@ -3964,8 +4010,8 @@ void Object::xfer( Xfer *xfer ) //------------------------------------------------------------------------------------------------- void Object::loadPostProcess() { - if( m_xferContainedByID != INVALID_ID ) - m_containedBy = TheGameLogic->findObjectByID(m_xferContainedByID); + if( m_containedByID != INVALID_ID ) + m_containedBy = TheGameLogic->findObjectByID(m_containedByID); else m_containedBy = nullptr; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp index 64da934984c..e088695d72e 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp @@ -164,6 +164,7 @@ BattlePlanUpdate::~BattlePlanUpdate() TheAudio->removeAudioEvent( m_holdTheLineUnpack.getPlayingHandle() ); TheAudio->removeAudioEvent( m_holdTheLinePack.getPlayingHandle() ); + deleteInstance(m_bonuses); } // ------------------------------------------------------------------------------------------------ diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp index e0c835c881c..2d75bd3199f 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp @@ -236,14 +236,13 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) /* ** Now, read in all of the other chunks (motion channels). */ - MotionChannelClass * newchan; - BitChannelClass * newbitchan; - while (cload.Open_Chunk()) { switch (cload.Cur_Chunk_ID()) { case W3D_CHUNK_ANIMATION_CHANNEL: + { + MotionChannelClass* newchan = nullptr; if (!read_channel(cload,&newchan,pre30)) { goto Error; } @@ -258,8 +257,11 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) delete newchan; } break; + } case W3D_CHUNK_BIT_CHANNEL: + { + BitChannelClass* newbitchan = nullptr; if (!read_bit_channel(cload,&newbitchan,pre30)) { goto Error; } @@ -274,6 +276,7 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) delete newbitchan; } break; + } default: break; @@ -304,14 +307,22 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) *=============================================================================================*/ bool HRawAnimClass::read_channel(ChunkLoadClass & cload,MotionChannelClass * * newchan,bool pre30) { - *newchan = W3DNEW MotionChannelClass; - bool result = (*newchan)->Load_W3D(cload); + MotionChannelClass* channel = W3DNEW MotionChannelClass; + if (channel->Load_W3D(cload)) + { + if (pre30) + { + channel->PivotIdx += 1; + } - if (result && pre30) { - (*newchan)->PivotIdx += 1; + *newchan = channel; + return true; + } + else + { + delete channel; + return false; } - - return result; } /*********************************************************************************************** @@ -378,14 +389,22 @@ void HRawAnimClass::add_channel(MotionChannelClass * newchan) *=============================================================================================*/ bool HRawAnimClass::read_bit_channel(ChunkLoadClass & cload,BitChannelClass * * newchan,bool pre30) { - *newchan = W3DNEW BitChannelClass; - bool result = (*newchan)->Load_W3D(cload); + BitChannelClass* channel = W3DNEW BitChannelClass; + if (channel->Load_W3D(cload)) + { + if (pre30) + { + channel->PivotIdx += 1; + } - if (result && pre30) { - (*newchan)->PivotIdx += 1; + *newchan = channel; + return true; + } + else + { + delete channel; + return false; } - - return result; } diff --git a/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp b/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp index 100d475c713..0264bf1ae37 100644 --- a/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp @@ -1421,6 +1421,9 @@ void ScriptDialog::OnLoad() msg += m_readPlayerNames[i].str(); msg += ", discarding scripts for this player."; ::AfxMessageBox(msg); + + deleteInstance(scripts[i]); + scripts[i] = nullptr; continue; } } @@ -1428,31 +1431,34 @@ void ScriptDialog::OnLoad() curSide = 0; ::AfxMessageBox("Imported scripts came from more players than exist in this map. Additional scripts moved to Neutral player."); } - ScriptList *pSL = m_sides.getSideInfo(curSide)->getScriptList(); - Script *pScr; - Script *pNextScr; - Int j=0; - for (pScr = scripts[i]->getScript(); pScr; pScr=pNextScr) { - pNextScr=pScr->getNext(); - pScr->setNextScript(nullptr); - pSL->addScript(pScr, j); //unlink it and add. - j++; - } - j=0; - ScriptGroup *pGroup; - ScriptGroup *pNextGroup; - for (pGroup = scripts[i]->getScriptGroup(); pGroup; pGroup=pNextGroup) { - pNextGroup=pGroup->getNext(); - pGroup->setNextGroup(nullptr); - pSL->addGroup(pGroup, j); - j++; - } - scripts[i]->discard(); /* Frees the script list, but none of it's children, as they have been - copied into the current scripts. */ - scripts[i] = nullptr; + ScriptList *pSL = m_sides.getSideInfo(curSide)->getScriptList(); if (pSL) { + Script *pScr; + Script *pNextScr; + Int j=0; + for (pScr = scripts[i]->getScript(); pScr; pScr=pNextScr) { + pNextScr=pScr->getNext(); + pScr->setNextScript(nullptr); + pSL->addScript(pScr, j); //unlink it and add. + j++; + } + j=0; + ScriptGroup *pGroup; + ScriptGroup *pNextGroup; + for (pGroup = scripts[i]->getScriptGroup(); pGroup; pGroup=pNextGroup) { + pNextGroup=pGroup->getNext(); + pGroup->setNextGroup(nullptr); + pSL->addGroup(pGroup, j); + j++; + } + scripts[i]->discard(); /* Frees the script list, but none of it's children, as they have been + copied into the current scripts. */ + scripts[i] = nullptr; reloadPlayer(curSide, pSL); + } else { + deleteInstance(scripts[i]); + scripts[i] = nullptr; } } diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h b/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h index 1bfc1a9ea2b..a610c4f5e7d 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h @@ -354,9 +354,84 @@ EMPTY_DTOR(MetaMapRec) class MetaEventTranslator : public GameMessageTranslator { private: - - Int m_lastKeyDown; // really a MappableKeyType - Int m_lastModState; // really a MappableKeyModState + struct KeyDownInfo + { + KeyDownInfo() : m_modStateBits(0) {} + + static UnsignedInt getMaxKeyModStateCount() + { + return 7; + } + + static MappableKeyModState toKeyModState(UnsignedInt index) + { + switch (index) + { + case 0: return CTRL; + case 1: return ALT; + case 2: return SHIFT; + case 3: return CTRL_ALT; + case 4: return SHIFT_CTRL; + case 5: return SHIFT_ALT; + case 6: return SHIFT_ALT_CTRL; + } + return NONE; + } + + static UnsignedInt toIndex(MappableKeyModState modState) + { + switch (modState) + { + case CTRL: return 0; + case ALT: return 1; + case SHIFT: return 2; + case CTRL_ALT: return 3; + case SHIFT_CTRL: return 4; + case SHIFT_ALT: return 5; + case SHIFT_ALT_CTRL: return 6; + } + return 7; + } + + Bool isKeyDown() const + { + return m_modStateBits != 0; + } + + MappableKeyModState getKeyModState(UnsignedInt index) + { + if (BitIsSet(m_modStateBits, 1 << index)) + { + return toKeyModState(index); + } + return NONE; + } + + void clearKeyModState(UnsignedInt index) + { + BitClear(m_modStateBits, 1 << index); + } + + Bool hasKeyModState(MappableKeyModState modState) const + { + return BitIsSet(m_modStateBits, 1 << toIndex(modState)); + } + + void setKeyModState(MappableKeyModState modState) + { + BitSet(m_modStateBits, 1 << toIndex(modState)); + } + + void clearKeyModState(MappableKeyModState modState) + { + BitClear(m_modStateBits, 1 << toIndex(modState)); + } + + private: + UnsignedByte m_modStateBits; ///< Fits all combinations of CTRL+ALT+SHIFT, storing 1 bit for each + }; + + KeyDownInfo m_keyDownInfos[KEY_COUNT]; enum { NUM_MOUSE_BUTTONS = 3 }; ICoord2D m_mouseDownPosition[NUM_MOUSE_BUTTONS]; @@ -366,6 +441,16 @@ class MetaEventTranslator : public GameMessageTranslator MetaEventTranslator(); virtual ~MetaEventTranslator() override; virtual GameMessageDisposition translateGameMessage(const GameMessage *msg) override; + +private: + void onMouseEvent(const GameMessage *msg); + + void onKeyEvent(const GameMessage *msg, GameMessageDisposition &disp); + void onKeyModStateRemoved(GameMessageDisposition &disp, MappableKeyModState keyModState); + void onKeyPressed(GameMessageDisposition &disp, Int systemKeyState, MappableKeyType keyType, MappableKeyModState keyModState); + + static MappableKeyType getActionKeyType(Int systemKey); ///< CRTL, ALT, SHIFT will be treated as MK_NONE + static MappableKeyModState getKeyModState(Int systemKeyState); ///< Extract CTRL, ALT, SHIFT key mod state }; //----------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index bfdf333393f..29d391b81a3 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -761,7 +761,7 @@ class Object : public Thing, public Snapshot Object* m_containedBy; /**< an object can only be contained by at most one other object, this is that object (if present) */ - ObjectID m_xferContainedByID; ///< xfer uses IDs to store pointers and looks them up after + ObjectID m_containedByID; ///< ID of the object we're contained by; only to be used when m_containedBy cannot be used UnsignedInt m_containedByFrame; ///< frame we were contained by m_containedBy Real m_constructionPercent; ///< for objects being built ... this is the amount completed (0.0 to 100.0) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 6f9ce5a65c7..3df81ea7789 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -287,6 +287,12 @@ GameEngine::~GameEngine() delete TheSubsystemList; TheSubsystemList = nullptr; + delete TheSkirmishGameInfo; + TheSkirmishGameInfo = nullptr; + + delete TheChallengeGameInfo; + TheChallengeGameInfo = nullptr; + delete TheNetwork; TheNetwork = nullptr; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index ec5149a081d..1b5a4fa7285 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -3168,13 +3168,13 @@ void Player::removeUpgrade( const UpgradeTemplate *upgradeTemplate ) if( upgrade->getStatus() == UPGRADE_STATUS_COMPLETE ) onUpgradeRemoved(); - if( ThePlayerList->getLocalPlayer() == this ) - { - TheControlBar->markUIDirty(); - } + deleteInstance(upgrade); + if( ThePlayerList->getLocalPlayer() == this ) + { + TheControlBar->markUIDirty(); + } } - } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp index b2c4d3fba4c..f503e975f8c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp @@ -104,6 +104,8 @@ static void embedPristineMap( AsciiString map, Xfer *xfer ) if( file->read( buffer, fileSize ) != fileSize ) { + delete[] buffer; + DEBUG_CRASH(( "embedPristineMap - Error reading from file '%s'", map.str() )); throw SC_INVALID_DATA; @@ -161,6 +163,8 @@ static void embedInUseMap( AsciiString map, Xfer *xfer ) if( fread( buffer, 1, fileSize, fp ) != fileSize ) { + delete[] buffer; + DEBUG_CRASH(( "embedInUseMap - Error reading from file '%s'", map.str() )); throw SC_INVALID_DATA; @@ -216,6 +220,8 @@ static void extractAndSaveMap( AsciiString mapToSave, Xfer *xfer ) if( fwrite( buffer, 1, dataSize, fp ) != dataSize ) { + delete[] buffer; + DEBUG_CRASH(( "extractAndSaveMap - Error writing to file '%s'", mapToSave.str() )); throw SC_INVALID_DATA; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 9d9d363a0a2..fa221abbb39 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -117,17 +117,13 @@ static_assert(ARRAY_SIZE(TheDrawableIconNames) == MAX_ICONS + 1, "Incorrect arra * * OK, so it's a bit of a hack, but it saves memory in every Drawable */ -static DynamicAudioEventInfo * getNoSoundMarker() -{ - static DynamicAudioEventInfo * marker = nullptr; +class DynamicAudioEventInfoStatic : public DynamicAudioEventInfo +{}; +static DynamicAudioEventInfoStatic s_noSoundMarker; - if ( marker == nullptr ) - { - // Initialize first time function is called - marker = newInstance( DynamicAudioEventInfo ); - } - - return marker; +static DynamicAudioEventInfo* getNoSoundMarker() +{ + return &s_noSoundMarker; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 1f15291995a..3ffdc71758e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -4904,8 +4904,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_MUSIC_NEXT_TRACK: { - TheAudio->nextMusicTrack(); - TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", TheAudio->getMusicTrackName().str()) ); + AsciiString trackName = TheAudio->nextMusicTrack(); + TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", trackName.str()) ); disp = DESTROY_MESSAGE; break; } @@ -4914,8 +4914,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_MUSIC_PREV_TRACK: { - TheAudio->prevMusicTrack(); - TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", TheAudio->getMusicTrackName().str()) ); + AsciiString trackName = TheAudio->prevMusicTrack(); + TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", trackName.str()) ); disp = DESTROY_MESSAGE; break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index 5f7d773a129..8dac7f17bf4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -379,9 +379,7 @@ static const FieldParse TheMetaMapFieldParseTable[] = // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- -MetaEventTranslator::MetaEventTranslator() : - m_lastKeyDown(MK_NONE), - m_lastModState(0) +MetaEventTranslator::MetaEventTranslator() { for (Int i = 0; i < NUM_MOUSE_BUTTONS; ++i) { m_nextUpShouldCreateDoubleClick[i] = FALSE; @@ -439,214 +437,293 @@ static Bool isMessageUsable(CommandUsableInType usableIn) GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessage *msg) { GameMessageDisposition disp = KEEP_MESSAGE; - GameMessage::Type t = msg->getType(); + const GameMessage::Type t = msg->getType(); if (t == GameMessage::MSG_RAW_KEY_DOWN || t == GameMessage::MSG_RAW_KEY_UP) { - MappableKeyType key = (MappableKeyType)msg->getArgument(0)->integer; - Int keyState = msg->getArgument(1)->integer; - - // for our purposes here, we don't care to distinguish between right and left keys, - // so just fudge a little to simplify things. - Int newModState = 0; + onKeyEvent(msg, disp); + } + else if (t > GameMessage::MSG_RAW_MOUSE_BEGIN && t < GameMessage::MSG_RAW_MOUSE_END ) + { + onMouseEvent(msg); + } - if( keyState & KEY_STATE_CONTROL ) - { - newModState |= CTRL; - } + return disp; +} - if( keyState & KEY_STATE_SHIFT ) +//------------------------------------------------------------------------------------------------- +void MetaEventTranslator::onMouseEvent(const GameMessage *msg) +{ + Int index = 3; + switch (msg->getType()) + { + case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN: { - newModState |= SHIFT; + --index; + m_mouseDownPosition[index] = msg->getArgument(0)->pixel; + m_nextUpShouldCreateDoubleClick[index] = FALSE; + break; } - if( keyState & KEY_STATE_ALT ) + case GameMessage::MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK: { - newModState |= ALT; + --index; + m_nextUpShouldCreateDoubleClick[index] = TRUE; + break; } - - for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP: { - if (!isMessageUsable(map->m_usableIn)) - continue; + --index; - // check for the special case of mods-only-changed. - if ( - map->m_key == MK_NONE && - newModState != m_lastModState && - ( - (map->m_transition == UP && map->m_modState == m_lastModState) || - (map->m_transition == DOWN && map->m_modState == newModState) - ) - ) + constexpr const GameMessage::Type SingleClickMessages[3] = { - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() Mods-only change: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); - disp = DESTROY_MESSAGE; - break; + GameMessage::MSG_MOUSE_LEFT_CLICK, + GameMessage::MSG_MOUSE_MIDDLE_CLICK, + GameMessage::MSG_MOUSE_RIGHT_CLICK, + }; + constexpr const GameMessage::Type DoubleClickMessages[3] = + { + GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK, + GameMessage::MSG_MOUSE_MIDDLE_DOUBLE_CLICK, + GameMessage::MSG_MOUSE_RIGHT_DOUBLE_CLICK, + }; + + const ICoord2D location = msg->getArgument(0)->pixel; + const GameMessage::Type messageType = m_nextUpShouldCreateDoubleClick[index] ? DoubleClickMessages[index] : SingleClickMessages[index]; + GameMessage *newMessage = TheMessageStream->insertMessage(messageType, const_cast(msg)); + + IRegion2D pixelRegion; + buildRegion( &m_mouseDownPosition[index], &location, &pixelRegion ); + if (abs(pixelRegion.hi.x - pixelRegion.lo.x) < TheMouse->m_dragTolerance && + abs(pixelRegion.hi.y - pixelRegion.lo.y) < TheMouse->m_dragTolerance) + { + pixelRegion.hi.x = pixelRegion.lo.x; + pixelRegion.hi.y = pixelRegion.lo.y; } - // ok, now check for "normal" key transitions. - if ( - map->m_key == key && - map->m_modState == newModState && - ( - (map->m_transition == UP && (keyState & KEY_STATE_UP)) || - (map->m_transition == DOWN && (keyState & KEY_STATE_DOWN)) //|| - //(map->m_transition == DOUBLEDOWN && (keyState & KEY_STATE_DOWN) && m_lastKeyDown == key) - ) - ) - { + newMessage->appendPixelRegionArgument( pixelRegion ); - if( keyState & KEY_STATE_AUTOREPEAT ) - { - // if it's an autorepeat of a "known" key, don't generate the meta-event, - // but DO eat the keystroke so no one else can mess with it - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() auto-repeat: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - } - else - { + // append the modifier keys to the message. + newMessage->appendIntegerArgument( msg->getArgument(1)->integer ); - // THIS IS A GREASY HACK... MESSAGE SHOULD BE HANDLED IN A TRANSLATOR, BUT DURING CINEMATICS THE TRANSLATOR IS DISABLED - if( map->m_meta == GameMessage::MSG_META_TOGGLE_FAST_FORWARD_REPLAY) - { - #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)//may be defined in GameCommon.h - if( TheGlobalData ) - #else - if( TheGlobalData && TheGameLogic->isInReplayGame()) - #endif - { - if ( TheWritableGlobalData ) - TheWritableGlobalData->m_TiVOFastMode = 1 - TheGlobalData->m_TiVOFastMode; - - if ( TheInGameUI ) - TheInGameUI->messageNoFormat( TheGlobalData->m_TiVOFastMode - ? TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_ON", L"Fast Forward is on") - : TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_OFF", L"Fast Forward is off") - ); - } - disp = KEEP_MESSAGE; // cause for goodness sake, this key gets used a lot by non-replay hotkeys - break; - } - - - /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() normal: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - } - disp = DESTROY_MESSAGE; - break; - } + // append the time to the message. + //newMessage->appendIntegerArgument( msg->getArgument(2)->integer ); + break; } + } +} +//------------------------------------------------------------------------------------------------- +void MetaEventTranslator::onKeyEvent(const GameMessage *msg, GameMessageDisposition &disp) +{ + const Int systemKey = msg->getArgument(0)->integer; + const Int systemKeyState = msg->getArgument(1)->integer; + const MappableKeyType keyType = getActionKeyType(systemKey); + const MappableKeyModState keyModState = getKeyModState(systemKeyState); - if (t == GameMessage::MSG_RAW_KEY_DOWN) - { - m_lastKeyDown = key; + const Bool modStateRemoved = (keyType == MK_NONE) && (msg->getType() == GameMessage::MSG_RAW_KEY_UP); + if (modStateRemoved) + { + onKeyModStateRemoved(disp, keyModState); + } + else + { + onKeyPressed(disp, systemKeyState, keyType, keyModState); + } +} -#ifdef DUMP_ALL_KEYS_TO_LOG +//------------------------------------------------------------------------------------------------- +void MetaEventTranslator::onKeyModStateRemoved(GameMessageDisposition &disp, MappableKeyModState keyModState) +{ + // TheSuperHackers @fix The key handler now ignores the order in which modifier keys are released. + // This avoids frustrating experiences where a wrong button release order would skip an important key event. - WideChar Wkey = TheKeyboard->getPrintableKey(key, 0); - UnicodeString uKey; - uKey.set(&Wkey); - AsciiString aKey; - aKey.translate(uKey); - DEBUG_LOG(("^%s ", aKey.str())); -#endif + for (Int keyDownIndex = 0; keyDownIndex < ARRAY_SIZE(m_keyDownInfos); ++keyDownIndex) + { + const MappableKeyType keyDown = (MappableKeyType)keyDownIndex; + KeyDownInfo &keyDownInfo = m_keyDownInfos[keyDownIndex]; - } + if (!keyDownInfo.isKeyDown()) + continue; + for (UnsignedInt modStateIndex = 0; modStateIndex < KeyDownInfo::getMaxKeyModStateCount(); ++modStateIndex) + { + const MappableKeyModState keyDownModState = keyDownInfo.getKeyModState(modStateIndex); + if (keyDownModState == NONE) + continue; - m_lastModState = newModState; - } + if (BitsAreSet(keyModState, keyDownModState)) + continue; + // Forget that this key and mod state are pressed. + keyDownInfo.clearKeyModState(modStateIndex); - if (t > GameMessage::MSG_RAW_MOUSE_BEGIN && t < GameMessage::MSG_RAW_MOUSE_END ) - { - Int index = 3; - switch (t) - { - case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN: + for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) { - --index; - m_mouseDownPosition[index] = msg->getArgument(0)->pixel; - m_nextUpShouldCreateDoubleClick[index] = FALSE; - break; + if (!isMessageUsable(map->m_usableIn)) + continue; + + const Bool isMatchingKeyCombo = map->m_key == keyDown && map->m_modState == keyDownModState; + const Bool isTransitionUp = map->m_transition == UP; + + if (!(isMatchingKeyCombo && isTransitionUp)) + continue; + + TheMessageStream->appendMessage(map->m_meta); + disp = DESTROY_MESSAGE; } + } + } +} + +//------------------------------------------------------------------------------------------------- +void MetaEventTranslator::onKeyPressed(GameMessageDisposition &disp, Int systemKeyState, MappableKeyType keyType, MappableKeyModState keyModState) +{ + // TheSuperHackers @info The regular key handler only triggers events when the mapped key is pressed, + // not when the modifier (CTRL, ALT, SHIFT) is pressed, unless the key is MK_NONE. - case GameMessage::MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK: + for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + { + if (!isMessageUsable(map->m_usableIn)) + continue; + + const Bool isMatchingKeyCombo = map->m_key == keyType && map->m_modState == keyModState; + const Bool isMatchingTransitionUp = map->m_transition == UP && (systemKeyState & KEY_STATE_UP) != 0; + const Bool isMatchingTransitionDown = map->m_transition == DOWN && (systemKeyState & KEY_STATE_DOWN) != 0; + //const Bool isMatchingTransitionDoubleDown = map->m_transition == DOUBLEDOWN && (systemKeyState & KEY_STATE_DOWN) && m_lastKeyDown == key; + + if (isMatchingKeyCombo && (isMatchingTransitionUp || isMatchingTransitionDown /*|| isMatchingTransitionDoubleDown*/)) + { + if( systemKeyState & KEY_STATE_AUTOREPEAT ) { - --index; - m_nextUpShouldCreateDoubleClick[index] = TRUE; - break; + // if it's an autorepeat of a "known" key, don't generate the meta-event, + // but DO eat the keystroke so no one else can mess with it + //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() auto-repeat: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); } - - case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP: + else { - --index; - - constexpr const GameMessage::Type SingleClickMessages[3] = + // THIS IS A GREASY HACK... MESSAGE SHOULD BE HANDLED IN A TRANSLATOR, BUT DURING CINEMATICS THE TRANSLATOR IS DISABLED + if( map->m_meta == GameMessage::MSG_META_TOGGLE_FAST_FORWARD_REPLAY) { - GameMessage::MSG_MOUSE_LEFT_CLICK, - GameMessage::MSG_MOUSE_MIDDLE_CLICK, - GameMessage::MSG_MOUSE_RIGHT_CLICK, - }; - constexpr const GameMessage::Type DoubleClickMessages[3] = - { - GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK, - GameMessage::MSG_MOUSE_MIDDLE_DOUBLE_CLICK, - GameMessage::MSG_MOUSE_RIGHT_DOUBLE_CLICK, - }; - - const ICoord2D location = msg->getArgument(0)->pixel; - const GameMessage::Type messageType = m_nextUpShouldCreateDoubleClick[index] ? DoubleClickMessages[index] : SingleClickMessages[index]; - GameMessage *newMessage = TheMessageStream->insertMessage(messageType, const_cast(msg)); - - IRegion2D pixelRegion; - buildRegion( &m_mouseDownPosition[index], &location, &pixelRegion ); - if (abs(pixelRegion.hi.x - pixelRegion.lo.x) < TheMouse->m_dragTolerance && - abs(pixelRegion.hi.y - pixelRegion.lo.y) < TheMouse->m_dragTolerance) - { - pixelRegion.hi.x = pixelRegion.lo.x; - pixelRegion.hi.y = pixelRegion.lo.y; + #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)//may be defined in GameCommon.h + if( TheGlobalData ) + #else + if( TheGlobalData && TheGameLogic->isInReplayGame()) + #endif + { + if ( TheWritableGlobalData ) + TheWritableGlobalData->m_TiVOFastMode = 1 - TheGlobalData->m_TiVOFastMode; + + if ( TheInGameUI ) + TheInGameUI->messageNoFormat( TheGlobalData->m_TiVOFastMode + ? TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_ON", L"Fast Forward is on") + : TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_OFF", L"Fast Forward is off") + ); + } + disp = KEEP_MESSAGE; // cause for goodness sake, this key gets used a lot by non-replay hotkeys + break; } - newMessage->appendPixelRegionArgument( pixelRegion ); + /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); + //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() normal: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); + } - // append the modifier keys to the message. - newMessage->appendIntegerArgument( msg->getArgument(1)->integer ); + disp = DESTROY_MESSAGE; + break; + } + } - // append the time to the message. - //newMessage->appendIntegerArgument( msg->getArgument(2)->integer ); - break; - } + if (systemKeyState & KEY_STATE_DOWN) + { +#ifdef DUMP_ALL_KEYS_TO_LOG + WideChar Wkey = TheKeyboard->getPrintableKey(keyType, 0); + UnicodeString uKey; + uKey.set(&Wkey); + AsciiString aKey; + aKey.translate(uKey); + DEBUG_LOG(("^%s ", aKey.str())); +#endif + if (keyModState != NONE) + { + // Remember that this key and mod state are pressed. + m_keyDownInfos[keyType].setKeyModState(keyModState); } + } + else + { + if (keyModState != NONE) + { + DEBUG_ASSERTCRASH(keyType != MK_NONE, ("Key is expected to be not MK_NONE")); + // Forget that this key and mod state are pressed. + m_keyDownInfos[keyType].clearKeyModState(keyModState); + } } +} - return disp; +//------------------------------------------------------------------------------------------------- +MappableKeyType MetaEventTranslator::getActionKeyType(Int systemKey) +{ + switch (systemKey) + { + case KEY_LCTRL: + case KEY_RCTRL: + case KEY_LSHIFT: + case KEY_RSHIFT: + case KEY_LALT: + case KEY_RALT: + return MK_NONE; + default: + return (MappableKeyType)systemKey; + } +} + +//------------------------------------------------------------------------------------------------- +MappableKeyModState MetaEventTranslator::getKeyModState(Int systemKeyState) +{ + // for our purposes here, we don't care to distinguish between right and left keys, + // so just fudge a little to simplify things. + Int keyModState = 0; + + if( systemKeyState & KEY_STATE_CONTROL ) + { + keyModState |= CTRL; + } + + if( systemKeyState & KEY_STATE_SHIFT ) + { + keyModState |= SHIFT; + } + + if( systemKeyState & KEY_STATE_ALT ) + { + keyModState |= ALT; + } + + return (MappableKeyModState)keyModState; } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp index 8794ecc4e57..b8592dc5512 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp @@ -562,7 +562,12 @@ void SidesList::prepareForMP_or_Skirmish() break; } } - if (curSide == -1) continue; + if (curSide == -1) + { + deleteInstance(scripts[i]); + scripts[i] = nullptr; + continue; + } deleteInstance(getSkirmishSideInfo(curSide)->getScriptList()); getSkirmishSideInfo(curSide)->setScriptList(scripts[i]); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index f88e77b3b5c..0ebb8c06c69 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -183,7 +183,7 @@ Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatu m_physics(nullptr), m_geometryInfo(tt->getTemplateGeometryInfo()), m_containedBy(nullptr), - m_xferContainedByID(INVALID_ID), + m_containedByID(INVALID_ID), m_containedByFrame(0), m_behaviors(nullptr), m_body(nullptr), @@ -692,6 +692,22 @@ void Object::onContainedBy( Object *containedBy ) m_containedBy = containedBy; m_containedByFrame = TheGameLogic->getFrame(); +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @info Set INVALID_ID if the container object was destroyed + // to indicate that the pointer will become a dangling pointer in the next frame. + if (containedBy && !containedBy->isDestroyed()) + { + m_containedByID = containedBy->getID(); + } + else + { + m_containedByID = INVALID_ID; + } +#else + DEBUG_ASSERTCRASH(containedBy == nullptr || !containedBy->isDestroyed(), + ("Object::onContainedBy - Adding into a destroyed container")); +#endif + handlePartitionCellMaintenance(); // which should unlook me now that I am contained } @@ -705,6 +721,10 @@ void Object::onRemovedFrom( Object *removedFrom ) m_containedBy = nullptr; m_containedByFrame = 0; +#if RETAIL_COMPATIBLE_CRC + m_containedByID = INVALID_ID; +#endif + handlePartitionCellMaintenance(); // get a clean look, now that I am outdoors, again } @@ -757,9 +777,33 @@ void Object::onDestroy() { // This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up. - if( m_containedBy && m_containedBy->getContain() ) + if (m_containedBy) { - m_containedBy->getContain()->removeFromContain( this ); +#if RETAIL_COMPATIBLE_CRC + if (m_containedByID == INVALID_ID) + { + // TheSuperHackers @bugfix Caball009 25/05/2026 Due to a potential use-after-free bug that cannot be fixed + // with retail compatibility, the 'contained by' pointer of this object may point to an already destroyed object. + // Avoid removing this object from the contain list, because it could crash the game, + // as the begin / end iterator for STLPort and MSVC std::list implementations depends on dynamically allocated memory. + DEBUG_CRASH(("container object must be valid; this looks like use-after-free")); + } + else + { + DEBUG_ASSERTCRASH(TheGameLogic->findObjectByID(m_containedByID) == m_containedBy, + ("contained by pointer is out of sync with contained by ID")); + + if (ContainModuleInterface* contain = m_containedBy->getContain()) + { + contain->removeFromContain(this); + } + } +#else + if (ContainModuleInterface* contain = m_containedBy->getContain()) + { + contain->removeFromContain(this); + } +#endif } // @@ -4249,16 +4293,18 @@ void Object::xfer( Xfer *xfer ) // No, the contain module is just going to friend_ reach in and set this for us. // Containers more complicated than Open (like Tunnel) can't do that. Our variable, // our responsibility. +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @tweak Contained by ID is already set with retail compatibility; don't overwrite it. if( xfer->getXferMode() == XFER_SAVE ) { if( m_containedBy != nullptr ) - m_xferContainedByID = m_containedBy->getID(); + m_containedByID = m_containedBy->getID(); else - m_xferContainedByID = INVALID_ID; + m_containedByID = INVALID_ID; } +#endif - - xfer->xferObjectID( &m_xferContainedByID ); + xfer->xferObjectID( &m_containedByID ); } // contained by frame @@ -4483,8 +4529,8 @@ void Object::xfer( Xfer *xfer ) //------------------------------------------------------------------------------------------------- void Object::loadPostProcess() { - if( m_xferContainedByID != INVALID_ID ) - m_containedBy = TheGameLogic->findObjectByID(m_xferContainedByID); + if( m_containedByID != INVALID_ID ) + m_containedBy = TheGameLogic->findObjectByID(m_containedByID); else m_containedBy = nullptr; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp index d4d0cead9dd..333d39662c1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp @@ -164,6 +164,7 @@ BattlePlanUpdate::~BattlePlanUpdate() TheAudio->removeAudioEvent( m_holdTheLineUnpack.getPlayingHandle() ); TheAudio->removeAudioEvent( m_holdTheLinePack.getPlayingHandle() ); + deleteInstance(m_bonuses); } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngineDevice/Source/OpenALAudioManager.cpp b/GeneralsMD/Code/GameEngineDevice/Source/OpenALAudioManager.cpp index 4a475b3b16e..0fd48f070d6 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/OpenALAudioManager.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/OpenALAudioManager.cpp @@ -430,17 +430,19 @@ Bool OpenALAudioManager::isCurrentlyPlaying(AudioHandle handle) /** * Music playback - next track */ -void OpenALAudioManager::nextMusicTrack(void) +AsciiString OpenALAudioManager::nextMusicTrack(void) { fprintf(stderr, "DEBUG: OpenALAudioManager::nextMusicTrack() - stub\n"); + return AsciiString::TheEmptyString; } /** * Music playback - previous track */ -void OpenALAudioManager::prevMusicTrack(void) +AsciiString OpenALAudioManager::prevMusicTrack(void) { fprintf(stderr, "DEBUG: OpenALAudioManager::prevMusicTrack() - stub\n"); + return AsciiString::TheEmptyString; } /** diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp index 54fdfb732e3..377e813236e 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp @@ -236,14 +236,13 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) /* ** Now, read in all of the other chunks (motion channels). */ - MotionChannelClass * newchan; - BitChannelClass * newbitchan; - while (cload.Open_Chunk()) { switch (cload.Cur_Chunk_ID()) { case W3D_CHUNK_ANIMATION_CHANNEL: + { + MotionChannelClass* newchan = nullptr; if (!read_channel(cload,&newchan,pre30)) { goto Error; } @@ -258,8 +257,11 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) delete newchan; } break; + } case W3D_CHUNK_BIT_CHANNEL: + { + BitChannelClass* newbitchan = nullptr; if (!read_bit_channel(cload,&newbitchan,pre30)) { goto Error; } @@ -274,6 +276,7 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) delete newbitchan; } break; + } default: break; @@ -304,15 +307,22 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) *=============================================================================================*/ bool HRawAnimClass::read_channel(ChunkLoadClass & cload,MotionChannelClass * * newchan,bool pre30) { - *newchan = W3DNEW MotionChannelClass; - bool result = (*newchan)->Load_W3D(cload); + MotionChannelClass* channel = W3DNEW MotionChannelClass; + if (channel->Load_W3D(cload)) + { + if (pre30) + { + channel->Set_Pivot(channel->Get_Pivot() + 1); + } - if (result && pre30) { -// (*newchan)->PivotIdx += 1; - (*newchan)->Set_Pivot((*newchan)->Get_Pivot()+1); + *newchan = channel; + return true; + } + else + { + delete channel; + return false; } - - return result; } /*********************************************************************************************** @@ -379,14 +389,22 @@ void HRawAnimClass::add_channel(MotionChannelClass * newchan) *=============================================================================================*/ bool HRawAnimClass::read_bit_channel(ChunkLoadClass & cload,BitChannelClass * * newchan,bool pre30) { - *newchan = W3DNEW BitChannelClass; - bool result = (*newchan)->Load_W3D(cload); + BitChannelClass* channel = W3DNEW BitChannelClass; + if (channel->Load_W3D(cload)) + { + if (pre30) + { + channel->PivotIdx += 1; + } - if (result && pre30) { - (*newchan)->PivotIdx += 1; + *newchan = channel; + return true; + } + else + { + delete channel; + return false; } - - return result; } diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp index fd36b02b024..27af92ec44a 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp @@ -1584,6 +1584,9 @@ void ScriptDialog::OnLoad() msg += m_readPlayerNames[i].str(); msg += ", discarding scripts for this player."; ::AfxMessageBox(msg); + + deleteInstance(scripts[i]); + scripts[i] = nullptr; continue; } } @@ -1591,8 +1594,8 @@ void ScriptDialog::OnLoad() curSide = 0; ::AfxMessageBox("Imported scripts came from more players than exist in this map. Additional scripts moved to Neutral player."); } - ScriptList *pSL = m_sides.getSideInfo(curSide)->getScriptList(); + ScriptList *pSL = m_sides.getSideInfo(curSide)->getScriptList(); if (pSL) { Script *pScr; Script *pNextScr; @@ -1616,8 +1619,10 @@ void ScriptDialog::OnLoad() copied into the current scripts. */ scripts[i] = nullptr; //reloadPlayer(curSide, pSL); + } else { + deleteInstance(scripts[i]); + scripts[i] = nullptr; } - } for (i = 0; i < m_sides.getNumSides(); i++) { diff --git a/docs/DEV_BLOG/2026-06-DIARY.md b/docs/DEV_BLOG/2026-06-DIARY.md new file mode 100644 index 00000000000..33bbd496f3d --- /dev/null +++ b/docs/DEV_BLOG/2026-06-DIARY.md @@ -0,0 +1,70 @@ +# 2026-06 Developer Diary + +## 06-04-2026 — TheSuperHackers upstream sync + +**Branch**: `thesuperhackers-sync-06-04-2026` +**Upstream tip**: `7c78a5e17` +**Local ancestor**: `20f42549c` +**Sync commit**: `1eaa1b71c` +**PRs brought in**: #2710, #2747, #2718, #2577, #2758 (5 commits total) + +### Summary + +Small upstream sync. Only 5 commits divergent on the upstream side; 1813 commits +divergent on ours. The bulk of the previous syncs had already absorbed the +heavy file-unification work from upstream, so this sync was a clean refactor +plus a couple of bug fixes. + +### Conflict resolution + +Only two `UU` conflicts, both in `MetaEvent.cpp` (Generals and GeneralsMD). +Plus two broken auto-merges that needed manual cleanup: + +- `MetaEvent.h` (both branches) — git auto-merged upstream's new + `onKeyEvent`/`KeyDownInfo` declarations on top of our old `m_lastKeyDown`/ + `m_lastModState` members. Result was internally inconsistent. Replaced + the member block with upstream's `KeyDownInfo m_keyDownInfos[KEY_COUNT]`. +- `MetaEvent.cpp` constructor — kept the now-dead `m_lastKeyDown(MK_NONE)` and + `m_lastModState(0)` initializers. Dropped them. +- `GameAudio.h` base-class API change — `nextMusicTrack` / `prevMusicTrack` + now return `AsciiString`; `getMusicTrackName` removed from the abstract base. + Updated our OpenAL backend (`Core/GameEngineDevice/.../OpenALAudioManager.{h,cpp}`) + to match the new signatures, returning the new track name. +- `BaseType.h` — added missing `BitsAreSet` macro that upstream's MetaEvent + code depends on. +- `AGENTS.md` — pre-existing merge markers (from commit `996eedd4e`, not from + this sync) cleared. Kept the "Updated upstream" version which matches the + current file structure. + +### Cross-platform impact + +Full audit. SDL3, DXVK, OpenAL, FFmpeg layers are intact. No platform-specific +code leaked into shared headers. The only shared header that changed +(`GameAudio.h`) was updated in a way that PRESERVES the cross-platform audio +backend (OpenAL). Miles audio is reference-only on Linux/macOS. + +### Validation + +- macOS `macos-vulkan` configure + build: ✓ (GeneralsXZH and GeneralsX) +- Runtime smoke (10s timeout kill): ✓ both binaries initialize SDL3, OpenAL, + Vulkan, file systems, INI parser, and enter the main loop cleanly. Exit 0. +- Linux `linux64-deploy` Docker build: deferred to CI (user request — robust + CI covers it). + +### Lessons + +- git's auto-merge can produce a SYNTHACTICALLY clean but SEMANTICALLY + inconsistent header when one side adds new members and the other refactors + existing members away. Always inspect the auto-merged result of refactor + PRs that touch class definitions. +- When upstream's API changes, downstream's overrides need to be inspected + even if the merge reports "no conflict" — the absence of conflict doesn't + mean absence of impact. +- Always run a final `git status` scan just before committing. Four files + that should have been staged slipped through. Amend was needed. + +### TODO + +- Verify Linux CI build passes. +- Manual smoke test with real game assets (not just init path). +- Open PR on GitHub. diff --git a/docs/WORKDIR/planning/PLAN-2026-06-04_THESUPERHACKERS_SYNC.md b/docs/WORKDIR/planning/PLAN-2026-06-04_THESUPERHACKERS_SYNC.md new file mode 100644 index 00000000000..6aa0b9fb74e --- /dev/null +++ b/docs/WORKDIR/planning/PLAN-2026-06-04_THESUPERHACKERS_SYNC.md @@ -0,0 +1,176 @@ +# PLAN-2026-06-04 — TheSuperHackers Upstream Sync + +**Branch**: `thesuperhackers-sync-06-04-2026` +**Source**: `thesuperhackers/main` @ `7c78a5e17` +**Last local sync ancestor**: `20f42549c` +**Commits being merged**: 5 (very small sync; previous syncs caught most of upstream) +**Divergence**: 1813 commits ours / 5 commits theirs + +--- + +## 1 — Scope: What Is Being Brought In + +| PR | Commit | Subject | Risk | +|---|---|---|---| +| #2710 | `20f42549c` | fix(memory): Fix various memory leaks (2) | Low — defensive guards + RAII placement | +| #2747 | `df2224bf1` | bugfix(object): Avoid crash in Object::onDestroy with dangling contain module (Troop Crawler) | Low — null check + cached pointer | +| #2718 | `b0becc407` + `a7fad3bb2` | refactor + fix MilesAudioManager (threading + cleanup) | **HIGH — touches shared `AudioManager` base class API** | +| #2758 | `7c78a5e17` | refactor(metaevent): Split `MetaEventTranslator::translateGameMessage()` | **HIGH — bundles #2577 modifier-release-order fix, replaces members** | + +--- + +## 2 — File Surface + +### Real conflicts (`UU`) +- `Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp` +- `GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp` + +### Auto-merged but LOGICALLY BROKEN (must be hand-repaired) +- `Generals/Code/GameEngine/Include/GameClient/MetaEvent.h` +- `GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h` + - Auto-merge unioned NEW declarations (`onKeyEvent`, `KeyDownInfo`, etc.) but DID NOT remove the OLD members (`m_lastKeyDown`, `m_lastModState`). Header is internally inconsistent. The constructor in the cpp still initializes the old members. Must replace member section with upstream's `KeyDownInfo` block. + +### Auto-merged with downstream impact on GeneralsX-specific code +- `Core/GameEngine/Include/Common/GameAudio.h` — base-class virtual signatures changed: + - `nextMusicTrack()` / `prevMusicTrack()` now return `AsciiString` (was `void`) + - `getMusicTrackName()` was REMOVED from the abstract base + - **Impact**: `Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.{h,cpp}` overrides must be updated to match new signatures, otherwise the class becomes abstract and won't link. + +### Auto-merged, accepting as-is (verified clean) +- `Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h` +- `Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp` +- `Core/GameEngineDevice/Source/W3DDevice/.../W3DModelDraw.cpp` +- `Core/Libraries/Source/WWVegas/WW3D2/hcanim.cpp` +- `Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp` +- `Dependencies/Utility/CMakeLists.txt` +- `Dependencies/Utility/Utility/interlocked_adapter.h` (new file) +- `Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp` +- `{Generals,GeneralsMD}/Code/GameEngine/Include/GameLogic/Object.h` +- `{Generals,GeneralsMD}/Code/GameEngine/Source/Common/GameEngine.cpp` +- `{Generals,GeneralsMD}/Code/GameEngine/Source/Common/RTS/Player.cpp` +- `{Generals,GeneralsMD}/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp` +- `{Generals,GeneralsMD}/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp` +- `{Generals,GeneralsMD}/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp` +- `{Generals,GeneralsMD}/Code/GameEngine/Source/GameLogic/Object/Object.cpp` +- `{Generals,GeneralsMD}/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp` +- `{Generals,GeneralsMD}/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp` +- `{Generals,GeneralsMD}/Code/Tools/WorldBuilder/src/ScriptDialog.cpp` +- `GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp` + +These are bugfix patches with small targeted changes. Spot-check during build. + +--- + +## 3 — CI/CD Protection Audit + +Per Critical Merge Instructions: +- This sync brought in **zero** modifications to `.github/workflows/`, `.github/ISSUE_TEMPLATE/`, or `.github/copilot-instructions.md` (the upstream commits don't touch those paths). +- Confirmed by inspecting the 5 commits' file lists. Nothing to reject. + +--- + +## 4 — Conflict Resolution Decisions + +### 4.1 `MetaEvent.cpp` (both branches) — UU conflict + +**Conflict region**: keyboard handling block inside `MetaEventTranslator::translateGameMessage`. + +**HEAD**: ~100 lines of inline keyboard logic using `m_lastKeyDown` / `m_lastModState`. + +**Upstream**: One line `onKeyEvent(msg, disp);`, with the logic extracted into new methods (`onKeyEvent`, `onKeyModStateRemoved`, `onKeyPressed`) plus the modifier-release-order bug fix from #2577. The new methods are already present at the bottom of the merged file (auto-merge added them). + +**Decision**: Take the upstream side (`onKeyEvent(msg, disp);`). Rationale: +- The new methods are the SAME logic, just refactored + bug-fixed. +- The bug fix in #2577 (ignore modifier-release order) is a real gameplay improvement. +- Keeping HEAD would dead-code the new methods AND lose the fix. +- The conflict is purely about WHICH code path runs; there is no cross-platform code to preserve here. + +### 4.2 `MetaEvent.h` (both branches) — broken auto-merge + +**Problem**: Auto-merge unioned new method declarations + new `KeyDownInfo` struct, but kept the old `m_lastKeyDown` / `m_lastModState` member fields. The result references members the upstream code no longer uses, AND still has the old members. + +**Decision**: Replace the entire `MetaEventTranslator` member section (between `private:` and `enum { NUM_MOUSE_BUTTONS … }`) with the upstream version — i.e. delete `m_lastKeyDown` / `m_lastModState`, insert the full `KeyDownInfo` struct and `KeyDownInfo m_keyDownInfos[KEY_COUNT];`. Rationale: +- The cpp body (auto-merged + the upstream `onKeyEvent` call) only uses `m_keyDownInfos`, never the old members. Keeping the old members is dead memory and a maintenance trap. +- The new struct is purely internal to the translator — no public API or cross-platform code is affected. + +### 4.3 `MetaEvent.cpp` constructor — broken auto-merge + +**Problem**: Constructor still initializes `m_lastKeyDown(MK_NONE)` and `m_lastModState(0)`, which won't exist after fixing the header (4.2). + +**Decision**: Remove those two member initializers. `m_keyDownInfos[]` self-initializes via `KeyDownInfo()` → `m_modStateBits(0)`. Loop over mouse buttons stays. + +### 4.4 `GameAudio.h` base-class API change → OpenAL override fix + +**Problem**: Upstream changed `AudioManager::nextMusicTrack()` and `prevMusicTrack()` to return `AsciiString` (was `void`) and removed `getMusicTrackName()`. Our `OpenALAudioManager` still uses the old signatures, which will: +- Fail to override (silent), making the class abstract and breaking link +- Leave `getMusicTrackName` as a non-override helper (acceptable) + +**Decision**: +- Update `Core/GameEngineDevice/Include/OpenALAudioDevice/OpenALAudioManager.h` and `OpenALAudioManager.cpp` to return `AsciiString` from `nextMusicTrack`/`prevMusicTrack`, returning the new track name (mirror the Miles upstream impl). +- Keep `getMusicTrackName()` as a class-only helper (no `override` since base removed it). The OpenAL impl uses it internally for tracking. +- Update the dead stub `GeneralsMD/Code/GameEngineDevice/Source/OpenALAudioManager.cpp` for source-tree cleanliness (file is never compiled in current configs but the signatures should match for future maintenance). + +Rationale: The Miles upstream change makes the call site simpler (`AsciiString name = TheAudio->nextMusicTrack();`). CommandXlat.cpp already uses the new pattern (auto-merged from upstream). Our OpenAL backend must match so the cross-platform stack stays linkable. + +### 4.5 Audio API change risk re-check + +Verified callers via grep: +- `CommandXlat.cpp` (both branches) uses `AsciiString trackName = TheAudio->nextMusicTrack();` — matches new API. +- No remaining callers of `AudioManager::getMusicTrackName()` — safe to drop from base. + +### 4.6 No INI parser changes in this sync + +The macOS INI numeric-parsing risk noted in the merge instructions does **not** apply here. None of the 5 commits touch `INI.cpp` or `INIWebpageURL.cpp`. + +### 4.7 No file moves into `Core/` in this sync + +The "many files moved from Generals/GeneralsMD into Core/" warning from the instructions does **not** apply to this 5-commit sync. The Core/ unification mostly happened in earlier upstream syncs (already in our tree). Our last sync ancestor `20f42549c` is recent. + +--- + +## 5 — Risk-Mitigation Plan per Subsystem + +### Audio (HIGH RISK) +- The Miles refactor + base-class API change is the most invasive piece. +- Mitigation: + - Update OpenAL signatures (4.4). + - Configure + build Linux and macOS to catch link-time errors. + - Smoke-test main menu load (audio init path runs on startup). + - Do NOT touch Miles cpp/h other than what auto-merged — Miles is reference-only on Linux/macOS, but the code still compiles in some configs. + +### Input / Keyboard (MEDIUM RISK) +- MetaEvent refactor bundles a real behavioral fix. The fix only matters when multiple modifier keys are pressed and released out of order, so smoke-testing main menu navigation is enough to verify no regression. + +### Memory leak patches (LOW RISK) +- Small, targeted, defensive. Build verification is sufficient. + +### CI / build system (NO CHANGE) +- Our `.github/` is unchanged. +- `Dependencies/Utility/CMakeLists.txt` adds a new header; verified the new header (`interlocked_adapter.h`) exists. + +--- + +## 6 — Execution Steps (order) + +1. Fix `Generals/Code/GameEngine/Include/GameClient/MetaEvent.h` (replace member block with upstream's). +2. Fix `GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h` (same). +3. Resolve `Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp` UU + drop dead constructor initializers. +4. Resolve `GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp` UU + drop dead constructor initializers. +5. Update `Core/GameEngineDevice/Include/OpenALAudioDevice/OpenALAudioManager.h` — new signatures. +6. Update `Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp` — return track name. +7. Update `GeneralsMD/Code/GameEngineDevice/Source/OpenALAudioManager.cpp` stub for parity (low priority — for tree cleanliness only). +8. Search for any other stale `getMusicTrackName` usage from the base class — none expected based on audit. +9. Verify zero conflict markers anywhere. +10. Configure macOS (`macos-vulkan`). +11. Build macOS `z_generals` and `z_generalsxzh` (or whatever targets succeed). +12. If macOS builds clean, attempt Linux (`linux64-deploy`) via Docker. +13. Runtime smoke for `GeneralsXZH` and `GeneralsX` (enter main loop, exit cleanly). +14. Remove generated artifacts (caches, build directories left in working tree). +15. Commit with attribution to upstream PRs. +16. Push branch to origin. + +--- + +## 7 — Open Questions / Deferred Items + +- None identified. All five upstream commits are well-scoped.