diff --git a/ChaosMod/Components/Component.h b/ChaosMod/Components/Component.h index 49ff7e4dd..2074be13f 100644 --- a/ChaosMod/Components/Component.h +++ b/ChaosMod/Components/Component.h @@ -90,7 +90,12 @@ class Component Component &operator=(const Component &) = delete; - virtual void OnModPauseCleanup() + enum PauseCleanupFlags + { + // Passed if called from another thread + PauseCleanupFlags_UnsafeCleanup = (1 << 0) + }; + virtual void OnModPauseCleanup(PauseCleanupFlags cleanupFlags = {}) { } diff --git a/ChaosMod/Components/CrossingChallenge.cpp b/ChaosMod/Components/CrossingChallenge.cpp index 06631ce7f..72396b3ce 100644 --- a/ChaosMod/Components/CrossingChallenge.cpp +++ b/ChaosMod/Components/CrossingChallenge.cpp @@ -561,7 +561,7 @@ void CrossingChallenge::OnRun() } } -void CrossingChallenge::OnModPauseCleanup() +void CrossingChallenge::OnModPauseCleanup(PauseCleanupFlags cleanupFlags) { if (m_StartedState != 2) SET_ENTITY_INVINCIBLE(PLAYER_PED_ID(), false); @@ -569,10 +569,14 @@ void CrossingChallenge::OnModPauseCleanup() m_StartedState = 0; m_ButtonsScaleformHandle = 0; m_ButtonsScaleformLoading = false; - if (DOES_BLIP_EXIST(m_StartBlip)) - REMOVE_BLIP(&m_StartBlip); - if (DOES_BLIP_EXIST(m_EndBlip)) - REMOVE_BLIP(&m_EndBlip); + + if (!(cleanupFlags & PauseCleanupFlags_UnsafeCleanup)) + { + if (DOES_BLIP_EXIST(m_StartBlip)) + REMOVE_BLIP(&m_StartBlip); + if (DOES_BLIP_EXIST(m_EndBlip)) + REMOVE_BLIP(&m_EndBlip); + } if (m_HelpMessageTick != -1) { diff --git a/ChaosMod/Components/CrossingChallenge.h b/ChaosMod/Components/CrossingChallenge.h index d0304392e..f7b376a78 100644 --- a/ChaosMod/Components/CrossingChallenge.h +++ b/ChaosMod/Components/CrossingChallenge.h @@ -95,7 +95,7 @@ class CrossingChallenge : public Component CrossingChallenge(); virtual void OnRun() override; - virtual void OnModPauseCleanup() override; + virtual void OnModPauseCleanup(PauseCleanupFlags cleanupFlags = {}) override; virtual void OnKeyInput(DWORD key, bool repeated, bool isUpNow, bool isCtrlPressed, bool isShiftPressed, bool isAltPressed) override; inline void IncrementEffects() diff --git a/ChaosMod/Components/DebugMenu.h b/ChaosMod/Components/DebugMenu.h index f2afa5268..e51238908 100644 --- a/ChaosMod/Components/DebugMenu.h +++ b/ChaosMod/Components/DebugMenu.h @@ -34,7 +34,6 @@ class DebugMenu : public Component DebugMenu(); virtual void OnRun() override; - virtual void OnKeyInput(DWORD key, bool repeated, bool isUpNow, bool isCtrlPressed, bool isShiftPressed, bool isAltPressed) override; diff --git a/ChaosMod/Components/DebugSocket.cpp b/ChaosMod/Components/DebugSocket.cpp index c5b355a6e..16b589e91 100644 --- a/ChaosMod/Components/DebugSocket.cpp +++ b/ChaosMod/Components/DebugSocket.cpp @@ -269,6 +269,24 @@ DebugSocket::DebugSocket() } } +void DebugSocket::OnModPauseCleanup(PauseCleanupFlags cleanupFlags) +{ + Close(); +} + +void DebugSocket::OnRun() +{ + if (!m_DelegateQueue.empty()) + { + std::lock_guard lock(m_DelegateQueueMutex); + while (!m_DelegateQueue.empty()) + { + m_DelegateQueue.front()(); + m_DelegateQueue.pop(); + } + } +} + void DebugSocket::Close() { m_Server->stop(); @@ -288,22 +306,4 @@ void DebugSocket::ScriptLog(std::string_view scriptName, std::string_view text) client->send(json.dump()); } -void DebugSocket::OnModPauseCleanup() -{ - Close(); -} - -void DebugSocket::OnRun() -{ - if (!m_DelegateQueue.empty()) - { - std::lock_guard lock(m_DelegateQueueMutex); - while (!m_DelegateQueue.empty()) - { - m_DelegateQueue.front()(); - m_DelegateQueue.pop(); - } - } -} - #endif \ No newline at end of file diff --git a/ChaosMod/Components/DebugSocket.h b/ChaosMod/Components/DebugSocket.h index 8e7067359..e82f85d61 100644 --- a/ChaosMod/Components/DebugSocket.h +++ b/ChaosMod/Components/DebugSocket.h @@ -46,6 +46,9 @@ class DebugSocket : public Component public: DebugSocket(); + virtual void OnModPauseCleanup(PauseCleanupFlags cleanupFlags = {}) override; + virtual void OnRun() override; + private: void Connect(); @@ -53,9 +56,6 @@ class DebugSocket : public Component void Close(); void ScriptLog(std::string_view scriptName, std::string_view text); - - virtual void OnModPauseCleanup() override; - virtual void OnRun() override; }; #endif \ No newline at end of file diff --git a/ChaosMod/Components/EffectDispatchTimer.cpp b/ChaosMod/Components/EffectDispatchTimer.cpp index ad0ed69b1..3f99fbf61 100644 --- a/ChaosMod/Components/EffectDispatchTimer.cpp +++ b/ChaosMod/Components/EffectDispatchTimer.cpp @@ -24,6 +24,44 @@ EffectDispatchTimer::EffectDispatchTimer(const std::array &timerColor) g_OptionsManager.GetConfigValue({ "DistanceType" }, OPTION_DEFAULT_DISTANCE_TYPE)); } +void EffectDispatchTimer::OnRun() +{ + auto curTime = GetTickCount64(); + + if (m_EnableTimer && m_DrawTimerBar + && (!ComponentExists() || !GetComponent()->HideChaosUI) + && (!ComponentExists() || !GetComponent()->DisableChaos)) + { + float percentage = m_FakeTimerPercentage != 0.f ? m_FakeTimerPercentage : m_TimerPercentage; + + // Timer bar at the top + DRAW_RECT(.5f, .01f, 1.f, .021f, 0, 0, 0, 127, false); + + if (ComponentExists() && GetComponent()->FlipChaosUI) + DRAW_RECT(1.f - percentage * .5f, .01f, percentage, .018f, m_TimerColor[0], m_TimerColor[1], + m_TimerColor[2], 255, false); + else + DRAW_RECT(percentage * .5f, .01f, percentage, .018f, m_TimerColor[0], m_TimerColor[1], m_TimerColor[2], 255, + false); + } + + int deltaTime = curTime - m_Timer; + + // The game was paused + if (deltaTime > 1000) + deltaTime = 0; + + if (!m_PauseTimer) + { + if (m_DistanceChaosState.EnableDistanceBasedEffectDispatch) + UpdateTravelledDistance(); + else + UpdateTimer(deltaTime); + } + + m_Timer = curTime; +} + void EffectDispatchTimer::UpdateTimer(int deltaTime) { if (!m_EnableTimer || (ComponentExists() && GetComponent()->DisableChaos)) @@ -177,42 +215,4 @@ void EffectDispatchTimer::SetTimerPaused(bool pause) bool EffectDispatchTimer::IsUsingDistanceBasedDispatch() const { return m_DistanceChaosState.EnableDistanceBasedEffectDispatch; -} - -void EffectDispatchTimer::OnRun() -{ - auto curTime = GetTickCount64(); - - if (m_EnableTimer && m_DrawTimerBar - && (!ComponentExists() || !GetComponent()->HideChaosUI) - && (!ComponentExists() || !GetComponent()->DisableChaos)) - { - float percentage = m_FakeTimerPercentage != 0.f ? m_FakeTimerPercentage : m_TimerPercentage; - - // Timer bar at the top - DRAW_RECT(.5f, .01f, 1.f, .021f, 0, 0, 0, 127, false); - - if (ComponentExists() && GetComponent()->FlipChaosUI) - DRAW_RECT(1.f - percentage * .5f, .01f, percentage, .018f, m_TimerColor[0], m_TimerColor[1], - m_TimerColor[2], 255, false); - else - DRAW_RECT(percentage * .5f, .01f, percentage, .018f, m_TimerColor[0], m_TimerColor[1], m_TimerColor[2], 255, - false); - } - - int deltaTime = curTime - m_Timer; - - // The game was paused - if (deltaTime > 1000) - deltaTime = 0; - - if (!m_PauseTimer) - { - if (m_DistanceChaosState.EnableDistanceBasedEffectDispatch) - UpdateTravelledDistance(); - else - UpdateTimer(deltaTime); - } - - m_Timer = curTime; } \ No newline at end of file diff --git a/ChaosMod/Components/EffectDispatchTimer.h b/ChaosMod/Components/EffectDispatchTimer.h index c45f7b9eb..79414f7c8 100644 --- a/ChaosMod/Components/EffectDispatchTimer.h +++ b/ChaosMod/Components/EffectDispatchTimer.h @@ -30,6 +30,8 @@ class EffectDispatchTimer : public Component public: EffectDispatchTimer(const std::array &timerColor); + virtual void OnRun() override; + private: void UpdateTimer(int deltaTime); void UpdateTravelledDistance(); @@ -53,6 +55,4 @@ class EffectDispatchTimer : public Component void SetTimerPaused(bool pause); bool IsUsingDistanceBasedDispatch() const; - - virtual void OnRun() override; }; \ No newline at end of file diff --git a/ChaosMod/Components/EffectDispatcher.cpp b/ChaosMod/Components/EffectDispatcher.cpp index e80e7aad5..1e15b0540 100644 --- a/ChaosMod/Components/EffectDispatcher.cpp +++ b/ChaosMod/Components/EffectDispatcher.cpp @@ -38,17 +38,17 @@ static void _DispatchEffect(EffectDispatcher *effectDispatcher, const EffectDisp // Increase weight for all effects first for (auto &[effectId, enabledEffectData] : g_EnabledEffects) if (!enabledEffectData.IsMeta()) - enabledEffectData.Weight += enabledEffectData.WeightMult; + enabledEffectData.Weight += enabledEffectData.WeightMult * enabledEffectData.WeightMult; // Reset weight of this effect (or every effect in group) to reduce chance of same effect (group) happening multiple // times in a row if (effectData.GroupType.empty()) - effectData.Weight = effectData.WeightMult; + effectData.Weight = effectData.WeightMult * effectData.WeightMult; else { for (auto &[effectId, enabledEffectData] : g_EnabledEffects) if (enabledEffectData.GroupType == effectData.GroupType) - effectData.Weight = effectData.WeightMult; + enabledEffectData.Weight = enabledEffectData.WeightMult * enabledEffectData.WeightMult; } auto playEffectDispatchSound = [&](EffectDispatcher::ActiveEffect &activeEffect) @@ -263,7 +263,7 @@ EffectDispatcher::EffectDispatcher(const std::array &textColor, const s g_EffectDispatcherThread = CreateFiber(0, _OnRunEffects, this); } -void EffectDispatcher::OnModPauseCleanup() +void EffectDispatcher::OnModPauseCleanup(PauseCleanupFlags cleanupFlags) { ClearEffects(); } diff --git a/ChaosMod/Components/EffectDispatcher.h b/ChaosMod/Components/EffectDispatcher.h index 3fd663df1..9cbb218a9 100644 --- a/ChaosMod/Components/EffectDispatcher.h +++ b/ChaosMod/Components/EffectDispatcher.h @@ -100,15 +100,15 @@ class EffectDispatcher : public Component EffectDispatcher(const std::array &textColor, const std::array &effectTimerColor); + virtual void OnModPauseCleanup(PauseCleanupFlags cleanupFlags = {}) override; + virtual void OnRun() override; + private: float GetEffectTopSpace(); void RegisterPermanentEffects(); public: - virtual void OnModPauseCleanup() override; - virtual void OnRun() override; - void DrawEffectTexts(); void DispatchEffect(const EffectIdentifier &effectId, diff --git a/ChaosMod/Components/EffectShortcuts.h b/ChaosMod/Components/EffectShortcuts.h index 8ea5a8c2a..f5818276c 100644 --- a/ChaosMod/Components/EffectShortcuts.h +++ b/ChaosMod/Components/EffectShortcuts.h @@ -20,7 +20,6 @@ class EffectShortcuts : public Component EffectShortcuts(); virtual void OnRun() override; - virtual void OnKeyInput(DWORD key, bool repeated, bool isUpNow, bool isCtrlPressed, bool isShiftPressed, bool isAltPressed) override; }; \ No newline at end of file diff --git a/ChaosMod/Components/EffectSound/EffectSound3D.cpp b/ChaosMod/Components/EffectSound/EffectSound3D.cpp index ce9f52ce0..49d8aab49 100644 --- a/ChaosMod/Components/EffectSound/EffectSound3D.cpp +++ b/ChaosMod/Components/EffectSound/EffectSound3D.cpp @@ -21,7 +21,7 @@ EffectSound3D::EffectSound3D() { Sleep(100); - if (IS_PAUSE_MENU_ACTIVE()) + if (GetTickCount64() > m_ThreadPingTimestamp + 100) { std::lock_guard lock(m_SoundsMutex); for (auto &[soundId, sound] : m_Sounds) @@ -40,7 +40,7 @@ void EffectSound3D::FreeSounds() } } -void EffectSound3D::OnModPauseCleanup() +void EffectSound3D::OnModPauseCleanup(PauseCleanupFlags cleanupFlags) { m_IsStopping = true; m_PauseSoundsThread.join(); @@ -55,9 +55,11 @@ void EffectSound3D::OnRun() if (m_Sounds.empty()) return; - auto playerPed = PLAYER_PED_ID(); + m_ThreadPingTimestamp = GetTickCount64(); - auto adjCamCoords = GET_GAMEPLAY_CAM_COORD(); + auto playerPed = PLAYER_PED_ID(); + + auto adjCamCoords = GET_GAMEPLAY_CAM_COORD(); ma_engine_listener_set_position(&m_maEngine, 0, adjCamCoords.x, adjCamCoords.y, adjCamCoords.z); float camHeading = GET_GAMEPLAY_CAM_RELATIVE_HEADING(); diff --git a/ChaosMod/Components/EffectSound/EffectSound3D.h b/ChaosMod/Components/EffectSound/EffectSound3D.h index f04d1abf8..263ac2bce 100644 --- a/ChaosMod/Components/EffectSound/EffectSound3D.h +++ b/ChaosMod/Components/EffectSound/EffectSound3D.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,7 @@ class EffectSound3D : public EffectSoundManager std::mutex m_SoundsMutex; bool m_IsStopping = false; std::thread m_PauseSoundsThread; + uint64_t m_ThreadPingTimestamp; public: EffectSound3D(); @@ -31,7 +33,7 @@ class EffectSound3D : public EffectSoundManager void FreeSounds(); public: - virtual void OnModPauseCleanup() override; + virtual void OnModPauseCleanup(PauseCleanupFlags cleanupFlags = {}) override; virtual void OnRun() override; virtual DWORD64 HandleSound(const std::string &soundFile) override; virtual void SetSoundOptions(DWORD64 soundId, const EffectSoundPlayOptions &soundPlayOptions) override; diff --git a/ChaosMod/Components/HelpTextQueue.cpp b/ChaosMod/Components/HelpTextQueue.cpp index b7c27c040..7193eb276 100644 --- a/ChaosMod/Components/HelpTextQueue.cpp +++ b/ChaosMod/Components/HelpTextQueue.cpp @@ -4,15 +4,7 @@ #include "Memory/Hooks/GetLabelTextHook.h" -void HelpTextQueue::DisplayLabel(std::string_view label, std::uint8_t durationSecs) -{ - if (durationSecs == 0) - return; - - m_HelpTextQueue.emplace(label, durationSecs / 1000.f); -} - -void HelpTextQueue::OnModPauseCleanup() +void HelpTextQueue::OnModPauseCleanup(PauseCleanupFlags cleanupFlags) { Hooks::ClearCustomLabels(); } @@ -29,4 +21,12 @@ void HelpTextQueue::OnRun() if ((helpText.TimerSecs -= GET_FRAME_TIME()) < 0.f) m_HelpTextQueue.pop(); +} + +void HelpTextQueue::DisplayLabel(std::string_view label, std::uint8_t durationSecs) +{ + if (durationSecs == 0) + return; + + m_HelpTextQueue.emplace(label, durationSecs / 1000.f); } \ No newline at end of file diff --git a/ChaosMod/Components/HelpTextQueue.h b/ChaosMod/Components/HelpTextQueue.h index e8737bd73..e86cea4ac 100644 --- a/ChaosMod/Components/HelpTextQueue.h +++ b/ChaosMod/Components/HelpTextQueue.h @@ -14,8 +14,8 @@ class HelpTextQueue : public Component std::queue m_HelpTextQueue; public: - void DisplayLabel(std::string_view label, std::uint8_t durationSecs); - - virtual void OnModPauseCleanup() override; + virtual void OnModPauseCleanup(PauseCleanupFlags cleanupFlags = {}) override; virtual void OnRun() override; + + void DisplayLabel(std::string_view label, std::uint8_t durationSecs); }; \ No newline at end of file diff --git a/ChaosMod/Components/LuaScripts.cpp b/ChaosMod/Components/LuaScripts.cpp index 194064717..731504591 100644 --- a/ChaosMod/Components/LuaScripts.cpp +++ b/ChaosMod/Components/LuaScripts.cpp @@ -560,7 +560,7 @@ LuaScripts::LuaScripts() } } -void LuaScripts::OnModPauseCleanup() +void LuaScripts::OnModPauseCleanup(PauseCleanupFlags cleanupFlags) { // Clean up all registered script effects for (auto it = g_RegisteredEffects.begin(); it != g_RegisteredEffects.end();) diff --git a/ChaosMod/Components/LuaScripts.h b/ChaosMod/Components/LuaScripts.h index 80412886e..850a5bc5d 100644 --- a/ChaosMod/Components/LuaScripts.h +++ b/ChaosMod/Components/LuaScripts.h @@ -72,7 +72,7 @@ class LuaScripts : public Component public: LuaScripts(); - virtual void OnModPauseCleanup() override; + virtual void OnModPauseCleanup(PauseCleanupFlags cleanupFlags = {}) override; private: void SetupGlobalState(); diff --git a/ChaosMod/Components/SplashTexts.cpp b/ChaosMod/Components/SplashTexts.cpp index cac6cb5de..2d28b1b12 100644 --- a/ChaosMod/Components/SplashTexts.cpp +++ b/ChaosMod/Components/SplashTexts.cpp @@ -25,7 +25,7 @@ SplashTexts::SplashTexts() ms_InitalSplashShown = true; } -void SplashTexts::OnModPauseCleanup() +void SplashTexts::OnModPauseCleanup(PauseCleanupFlags cleanupFlags) { m_ActiveSplashes.clear(); } diff --git a/ChaosMod/Components/SplashTexts.h b/ChaosMod/Components/SplashTexts.h index 950404948..fc44641c6 100644 --- a/ChaosMod/Components/SplashTexts.h +++ b/ChaosMod/Components/SplashTexts.h @@ -32,7 +32,7 @@ class SplashTexts : public Component SplashTexts(); virtual void OnRun() override; - virtual void OnModPauseCleanup() override; + virtual void OnModPauseCleanup(PauseCleanupFlags cleanupFlags = {}) override; void ShowSplash(const std::string &text, const ScreenTextVector &textPos, float scale, ScreenTextColor textColor, std::uint8_t timeSecs = 10); diff --git a/ChaosMod/Components/Voting.cpp b/ChaosMod/Components/Voting.cpp index 087e5322a..04c6200d5 100644 --- a/ChaosMod/Components/Voting.cpp +++ b/ChaosMod/Components/Voting.cpp @@ -23,172 +23,7 @@ Voting::Voting(const std::array &textColor) : Component(), m_TextColor( m_VoteablePrefix = g_OptionsManager.GetVotingValue({ "VoteablePrefix" }); } -bool Voting::Init() -{ - if (std::count_if(g_EnabledEffects.begin(), g_EnabledEffects.end(), - [](const auto &pair) { return !pair.second.IsExcludedFromVoting(); }) - < 3) - { - ErrorOutWithMsg("You need at least 3 enabled effects (which are not excluded from voting) to enable voting." - " Reverting to normal mode."); - - return false; - } - - // A previous instance of the voting proxy could still be running, wait for it to release the mutex - auto mutex = OpenMutex(SYNCHRONIZE, FALSE, L"ChaosModVVotingMutex"); - if (mutex) - { - WaitForSingleObject(mutex, INFINITE); - ReleaseMutex(mutex); - CloseHandle(mutex); - } - - m_SecsBeforeVoting = g_OptionsManager.GetVotingValue({ "VotingSecsBeforeVoting", "TwitchVotingSecsBeforeVoting" }, - OPTION_DEFAULT_TWITCH_SECS_BEFORE_VOTING); - - m_OverlayMode = g_OptionsManager.GetVotingValue({ "VotingOverlayMode", "TwitchVotingOverlayMode" }, - static_cast(OPTION_DEFAULT_TWITCH_OVERLAY_MODE)); - - m_VotingMode = g_OptionsManager.GetVotingValue({ "VotingChanceSystem", "TwitchVotingChanceSystem" }, - OPTION_DEFAULT_TWITCH_PROPORTIONAL_VOTING) - ? VotingMode::Percentage - : VotingMode::Majority; - m_EnableVotingChanceSystemRetainInitialChance = - g_OptionsManager.GetVotingValue({ "VotingChanceSystemRetainChance", "TwitchVotingChanceSystemRetainChance" }, - OPTION_DEFAULT_TWITCH_PROPORTIONAL_VOTING_RETAIN_CHANCE); - - m_EnableRandomEffectVoteable = g_OptionsManager.GetVotingValue( - { "RandomEffectVoteableEnable", "TwitchRandomEffectVoteableEnable" }, OPTION_DEFAULT_TWITCH_RANDOM_EFFECT); - - STARTUPINFO startupInfo = {}; - PROCESS_INFORMATION procInfo = {}; - - auto str = _wcsdup(VOTING_PROXY_START_ARGS); -#ifdef CHAOSDEBUG - DWORD attributes = 0; - if (DoesFeatureFlagExist("forcenovotingconsole")) - attributes = CREATE_NO_WINDOW; - - bool result = CreateProcess(NULL, str, NULL, NULL, TRUE, attributes, NULL, NULL, &startupInfo, &procInfo); -#else - bool result = CreateProcess(NULL, str, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &procInfo); -#endif - free(str); - - if (!result) - { - ErrorOutWithMsg((std::ostringstream() - << "Error while starting chaosmod/TwitchChatVotingProxy.exe (Error Code: " << GetLastError() - << "). Please verify the file exists. Reverting to normal mode.") - .str()); - - return false; - } - - return true; -} - -bool Voting::IsEnabled() const -{ - return m_EnableVoting; -} - -VotingMode Voting::GetVotingMode() const -{ - return m_VotingMode; -} - -void Voting::HandleMsg(std::string_view message) -{ - if (message == "hello") - { - if (!m_ReceivedHello) - { - m_ReceivedHello = true; - m_NoPingRuns = 0; - - LOG("Received hello from voting pipe"); - - if (ComponentExists()) - GetComponent()->SetShouldDispatchEffects(false); - - SendToPipe("hello_back"); - } - } - else if (message == "ping") - { - m_LastPing = GetTickCount64(); - m_NoPingRuns = 0; - } - else - { - auto receivedJSON = nlohmann::json::parse(message); - if (!receivedJSON.empty()) - { - std::string identifier = receivedJSON["Identifier"]; - if (identifier == "voteresult") - { - int result = receivedJSON["SelectedOption"]; - - m_HasReceivedResult = true; - - // If random effect voteable (result == 3) won, dispatch random effect later - m_ChosenEffectId = - std::make_unique(result == 3 ? EffectIdentifier() : m_EffectChoices[result]->Id); - } - else if (identifier == "currentvotes") - { - std::vector options = receivedJSON["Votes"]; - if (options.size() == m_EffectChoices.size()) - { - for (size_t idx = 0; idx < options.size(); idx++) - { - int votes = options[idx]; - m_EffectChoices[idx]->ChanceVotes = votes; - } - } - } - else if (identifier == "error") - { - std::string message = receivedJSON["Message"]; - ErrorOutWithMsg(message); - } - } - } -} - -std::string Voting::GetPipeJson(std::string_view identifier, std::vector params) -{ - nlohmann::json finalJSON; - finalJSON["Identifier"] = identifier; - finalJSON["Options"] = params; - return finalJSON.dump(); -} - -void Voting::SendToPipe(std::string_view identifier, std::vector params) -{ - auto msg = GetPipeJson(identifier, params); - msg += "\n"; - WriteFile(m_PipeHandle, msg.c_str(), msg.length(), NULL, NULL); -} - -void Voting::ErrorOutWithMsg(std::string_view message) -{ - std::wstring wStr = { message.begin(), message.end() }; - MessageBox(NULL, wStr.c_str(), L"ChaosModV Error", MB_OK | MB_ICONERROR); - - DisconnectNamedPipe(m_PipeHandle); - CloseHandle(m_PipeHandle); - m_PipeHandle = INVALID_HANDLE_VALUE; - - if (ComponentExists()) - GetComponent()->SetShouldDispatchEffects(true); - - m_EnableVoting = false; -} - -void Voting::OnModPauseCleanup() +void Voting::OnModPauseCleanup(PauseCleanupFlags cleanupFlags) { if (m_PipeHandle != INVALID_HANDLE_VALUE) { @@ -465,4 +300,169 @@ void Voting::OnRun() y += .05f; } } -} \ No newline at end of file +} + +bool Voting::Init() +{ + if (std::count_if(g_EnabledEffects.begin(), g_EnabledEffects.end(), + [](const auto &pair) { return !pair.second.IsExcludedFromVoting(); }) + < 3) + { + ErrorOutWithMsg("You need at least 3 enabled effects (which are not excluded from voting) to enable voting." + " Reverting to normal mode."); + + return false; + } + + // A previous instance of the voting proxy could still be running, wait for it to release the mutex + auto mutex = OpenMutex(SYNCHRONIZE, FALSE, L"ChaosModVVotingMutex"); + if (mutex) + { + WaitForSingleObject(mutex, INFINITE); + ReleaseMutex(mutex); + CloseHandle(mutex); + } + + m_SecsBeforeVoting = g_OptionsManager.GetVotingValue({ "VotingSecsBeforeVoting", "TwitchVotingSecsBeforeVoting" }, + OPTION_DEFAULT_TWITCH_SECS_BEFORE_VOTING); + + m_OverlayMode = g_OptionsManager.GetVotingValue({ "VotingOverlayMode", "TwitchVotingOverlayMode" }, + static_cast(OPTION_DEFAULT_TWITCH_OVERLAY_MODE)); + + m_VotingMode = g_OptionsManager.GetVotingValue({ "VotingChanceSystem", "TwitchVotingChanceSystem" }, + OPTION_DEFAULT_TWITCH_PROPORTIONAL_VOTING) + ? VotingMode::Percentage + : VotingMode::Majority; + m_EnableVotingChanceSystemRetainInitialChance = + g_OptionsManager.GetVotingValue({ "VotingChanceSystemRetainChance", "TwitchVotingChanceSystemRetainChance" }, + OPTION_DEFAULT_TWITCH_PROPORTIONAL_VOTING_RETAIN_CHANCE); + + m_EnableRandomEffectVoteable = g_OptionsManager.GetVotingValue( + { "RandomEffectVoteableEnable", "TwitchRandomEffectVoteableEnable" }, OPTION_DEFAULT_TWITCH_RANDOM_EFFECT); + + STARTUPINFO startupInfo = {}; + PROCESS_INFORMATION procInfo = {}; + + auto str = _wcsdup(VOTING_PROXY_START_ARGS); +#ifdef CHAOSDEBUG + DWORD attributes = 0; + if (DoesFeatureFlagExist("forcenovotingconsole")) + attributes = CREATE_NO_WINDOW; + + bool result = CreateProcess(NULL, str, NULL, NULL, TRUE, attributes, NULL, NULL, &startupInfo, &procInfo); +#else + bool result = CreateProcess(NULL, str, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &procInfo); +#endif + free(str); + + if (!result) + { + ErrorOutWithMsg((std::ostringstream() + << "Error while starting chaosmod/TwitchChatVotingProxy.exe (Error Code: " << GetLastError() + << "). Please verify the file exists. Reverting to normal mode.") + .str()); + + return false; + } + + return true; +} + +bool Voting::IsEnabled() const +{ + return m_EnableVoting; +} + +VotingMode Voting::GetVotingMode() const +{ + return m_VotingMode; +} + +void Voting::HandleMsg(std::string_view message) +{ + if (message == "hello") + { + if (!m_ReceivedHello) + { + m_ReceivedHello = true; + m_NoPingRuns = 0; + + LOG("Received hello from voting pipe"); + + if (ComponentExists()) + GetComponent()->SetShouldDispatchEffects(false); + + SendToPipe("hello_back"); + } + } + else if (message == "ping") + { + m_LastPing = GetTickCount64(); + m_NoPingRuns = 0; + } + else + { + auto receivedJSON = nlohmann::json::parse(message); + if (!receivedJSON.empty()) + { + std::string identifier = receivedJSON["Identifier"]; + if (identifier == "voteresult") + { + int result = receivedJSON["SelectedOption"]; + + m_HasReceivedResult = true; + + // If random effect voteable (result == 3) won, dispatch random effect later + m_ChosenEffectId = + std::make_unique(result == 3 ? EffectIdentifier() : m_EffectChoices[result]->Id); + } + else if (identifier == "currentvotes") + { + std::vector options = receivedJSON["Votes"]; + if (options.size() == m_EffectChoices.size()) + { + for (size_t idx = 0; idx < options.size(); idx++) + { + int votes = options[idx]; + m_EffectChoices[idx]->ChanceVotes = votes; + } + } + } + else if (identifier == "error") + { + std::string message = receivedJSON["Message"]; + ErrorOutWithMsg(message); + } + } + } +} + +std::string Voting::GetPipeJson(std::string_view identifier, std::vector params) +{ + nlohmann::json finalJSON; + finalJSON["Identifier"] = identifier; + finalJSON["Options"] = params; + return finalJSON.dump(); +} + +void Voting::SendToPipe(std::string_view identifier, std::vector params) +{ + auto msg = GetPipeJson(identifier, params); + msg += "\n"; + WriteFile(m_PipeHandle, msg.c_str(), msg.length(), NULL, NULL); +} + +void Voting::ErrorOutWithMsg(std::string_view message) +{ + std::wstring wStr = { message.begin(), message.end() }; + MessageBox(NULL, wStr.c_str(), L"ChaosModV Error", MB_OK | MB_ICONERROR); + + DisconnectNamedPipe(m_PipeHandle); + CloseHandle(m_PipeHandle); + m_PipeHandle = INVALID_HANDLE_VALUE; + + if (ComponentExists()) + GetComponent()->SetShouldDispatchEffects(true); + + m_EnableVoting = false; +} diff --git a/ChaosMod/Components/Voting.h b/ChaosMod/Components/Voting.h index 68fdb9c58..9e077285e 100644 --- a/ChaosMod/Components/Voting.h +++ b/ChaosMod/Components/Voting.h @@ -65,6 +65,9 @@ class Voting : public Component public: Voting(const std::array &TextColor); + virtual void OnModPauseCleanup(PauseCleanupFlags cleanupFlags = {}) override; + virtual void OnRun() override; + bool Init(); bool IsEnabled() const; VotingMode GetVotingMode() const; @@ -76,7 +79,4 @@ class Voting : public Component public: void SendToPipe(std::string_view identifier, std::vector params = {}); void ErrorOutWithMsg(std::string_view message); - - virtual void OnModPauseCleanup() override; - virtual void OnRun() override; }; \ No newline at end of file diff --git a/ChaosMod/Effects/Condition/ConditionProportionalVotingEnabled.cpp b/ChaosMod/Effects/Condition/ConditionProportionalVotingEnabled.cpp index 61d131c66..b4c36e639 100644 --- a/ChaosMod/Effects/Condition/ConditionProportionalVotingEnabled.cpp +++ b/ChaosMod/Effects/Condition/ConditionProportionalVotingEnabled.cpp @@ -5,7 +5,9 @@ static bool OnCondition() { - return ComponentExists() && GetComponent()->IsEnabled() && GetComponent()->GetVotingMode() == VotingMode::Percentage; + return ComponentExists() && GetComponent()->IsEnabled() + && GetComponent()->GetVotingMode() == VotingMode::Percentage; } -REGISTER_EFFECT_CONDITION(EffectConditionType::ProportionalVotingEnabled, OnCondition, "Proportional voting is not enabled"); \ No newline at end of file +REGISTER_EFFECT_CONDITION(EffectConditionType::ProportionalVotingEnabled, OnCondition, + "Proportional voting is not enabled"); \ No newline at end of file diff --git a/ChaosMod/Effects/Condition/EffectCondition.h b/ChaosMod/Effects/Condition/EffectCondition.h index 32801a7e1..ac3ad7911 100644 --- a/ChaosMod/Effects/Condition/EffectCondition.h +++ b/ChaosMod/Effects/Condition/EffectCondition.h @@ -3,8 +3,8 @@ #include "Util/Logging.h" #include "Util/MacroConcat.h" -#include #include +#include enum class EffectConditionType { @@ -13,9 +13,9 @@ enum class EffectConditionType ProportionalVotingEnabled }; -#define REGISTER_EFFECT_CONDITION(conditionType, condition, failReason) \ - namespace \ - { \ +#define REGISTER_EFFECT_CONDITION(conditionType, condition, failReason) \ + namespace \ + { \ EffectCondition CHAOSCONCAT(_effectCondition, __LINE__)(conditionType, condition, failReason); \ } diff --git a/ChaosMod/Effects/db/Peds/PedsSpawnCompanionBrad.cpp b/ChaosMod/Effects/db/Peds/PedsSpawnCompanionBrad.cpp index b1d883a77..dfe4a506f 100644 --- a/ChaosMod/Effects/db/Peds/PedsSpawnCompanionBrad.cpp +++ b/ChaosMod/Effects/db/Peds/PedsSpawnCompanionBrad.cpp @@ -8,10 +8,10 @@ static void OnStart() { static const Hash model = "ig_brad"_hash; - Ped playerPed = PLAYER_PED_ID(); - Vector3 playerPos = GET_ENTITY_COORDS(playerPed, false); + Ped playerPed = PLAYER_PED_ID(); + Vector3 playerPos = GET_ENTITY_COORDS(playerPed, false); - Ped ped = CreatePoolPed(4, model, playerPos.x, playerPos.y, playerPos.z, GET_ENTITY_HEADING(playerPed)); + Ped ped = CreatePoolPed(4, model, playerPos.x, playerPos.y, playerPos.z, GET_ENTITY_HEADING(playerPed)); CurrentEffect::SetEffectSoundPlayOptions( { .PlayType = EffectSoundPlayType::FollowEntity, .PlayFlags = EffectSoundPlayFlags_Looping, .Entity = ped }); if (IS_PED_IN_ANY_VEHICLE(playerPed, false)) diff --git a/ChaosMod/Effects/db/Peds/PedsSpawnCompanionChimp.cpp b/ChaosMod/Effects/db/Peds/PedsSpawnCompanionChimp.cpp index 27165aeaa..c69ed0639 100644 --- a/ChaosMod/Effects/db/Peds/PedsSpawnCompanionChimp.cpp +++ b/ChaosMod/Effects/db/Peds/PedsSpawnCompanionChimp.cpp @@ -8,8 +8,8 @@ static void OnStart() { static const Hash modelHash = "a_c_chimp"_hash; - Ped playerPed = PLAYER_PED_ID(); - Vector3 playerPos = GET_ENTITY_COORDS(playerPed, false); + Ped playerPed = PLAYER_PED_ID(); + Vector3 playerPos = GET_ENTITY_COORDS(playerPed, false); Ped ped = CreatePoolPed(28, modelHash, playerPos.x, playerPos.y, playerPos.z, GET_ENTITY_HEADING(playerPed)); CurrentEffect::SetEffectSoundPlayOptions({ .PlayType = EffectSoundPlayType::FollowEntity, .Entity = ped }); diff --git a/ChaosMod/Effects/db/Peds/PedsSpawnCompanionChop.cpp b/ChaosMod/Effects/db/Peds/PedsSpawnCompanionChop.cpp index ee82d261b..3460df3f2 100644 --- a/ChaosMod/Effects/db/Peds/PedsSpawnCompanionChop.cpp +++ b/ChaosMod/Effects/db/Peds/PedsSpawnCompanionChop.cpp @@ -8,8 +8,8 @@ static void OnStart() { static const Hash modelHash = "a_c_chop"_hash; - Ped playerPed = PLAYER_PED_ID(); - Vector3 playerPos = GET_ENTITY_COORDS(playerPed, false); + Ped playerPed = PLAYER_PED_ID(); + Vector3 playerPos = GET_ENTITY_COORDS(playerPed, false); Ped ped = CreatePoolPed(28, modelHash, playerPos.x, playerPos.y, playerPos.z, GET_ENTITY_HEADING(playerPed)); CurrentEffect::SetEffectSoundPlayOptions({ .PlayType = EffectSoundPlayType::FollowEntity, .Entity = ped }); diff --git a/ChaosMod/Main.cpp b/ChaosMod/Main.cpp index b3a9185aa..7746975ab 100644 --- a/ChaosMod/Main.cpp +++ b/ChaosMod/Main.cpp @@ -365,7 +365,7 @@ namespace Main if (!ms_ModDisabled) for (auto component : g_Components) - component->OnModPauseCleanup(); + component->OnModPauseCleanup(Component::PauseCleanupFlags_UnsafeCleanup); LOG("Mod unload complete!"); } diff --git a/ChaosMod/Memory/Entity.h b/ChaosMod/Memory/Entity.h index 224fcda35..9f3b2d563 100644 --- a/ChaosMod/Memory/Entity.h +++ b/ChaosMod/Memory/Entity.h @@ -1,8 +1,8 @@ #pragma once -#include "../Util/Logging.h" #include "Handle.h" #include "Memory.h" +#include "Util/Logging.h" #include diff --git a/ChaosMod/Memory/Hooks/ShaderHook.cpp b/ChaosMod/Memory/Hooks/ShaderHook.cpp index d06aac1ad..189f7087b 100644 --- a/ChaosMod/Memory/Hooks/ShaderHook.cpp +++ b/ChaosMod/Memory/Hooks/ShaderHook.cpp @@ -78,43 +78,44 @@ namespace Hooks auto result = shaderCache.find(hash); if (result == shaderCache.end()) { - auto future = std::async( - std::launch::async, - [&]() - { - ID3DBlob *shader; - ID3DBlob *errorMessages; - HRESULT compileResult; - if ((compileResult = D3DCompile(shaderSrc.data(), shaderSrc.size(), NULL, NULL, NULL, "main", - "ps_4_0", 0, 0, &shader, &errorMessages)) - == S_OK) - { - auto ptr = reinterpret_cast(shader->GetBufferPointer()); - std::vector shaderBytecode; - shaderBytecode.reserve(shader->GetBufferSize()); - std::copy(ptr, ptr + shader->GetBufferSize(), std::back_inserter(shaderBytecode)); - - if (shaderCache.size() > SHADER_CACHE_MAX_ENTRIES) - shaderCache.erase(shaderCache.begin()); - - shaderCache[hash] = shaderBytecode; - result = shaderCache.find(hash); - } - else - { - LOG("Error compiling shader: Error code 0x" << std::hex << std::uppercase << compileResult << std::dec); - if (errorMessages) - { - auto ptr = reinterpret_cast(errorMessages->GetBufferPointer()); - std::string buffer; - buffer.reserve(errorMessages->GetBufferSize()); - std::copy(ptr, ptr + errorMessages->GetBufferSize(), std::back_inserter(buffer)); - - LOG(buffer); - return; - } - } - }); + auto future = + std::async(std::launch::async, + [&]() + { + ID3DBlob *shader; + ID3DBlob *errorMessages; + HRESULT compileResult; + if ((compileResult = D3DCompile(shaderSrc.data(), shaderSrc.size(), NULL, NULL, NULL, + "main", "ps_4_0", 0, 0, &shader, &errorMessages)) + == S_OK) + { + auto ptr = reinterpret_cast(shader->GetBufferPointer()); + std::vector shaderBytecode; + shaderBytecode.reserve(shader->GetBufferSize()); + std::copy(ptr, ptr + shader->GetBufferSize(), std::back_inserter(shaderBytecode)); + + if (shaderCache.size() > SHADER_CACHE_MAX_ENTRIES) + shaderCache.erase(shaderCache.begin()); + + shaderCache[hash] = shaderBytecode; + result = shaderCache.find(hash); + } + else + { + LOG("Error compiling shader: Error code 0x" << std::hex << std::uppercase + << compileResult << std::dec); + if (errorMessages) + { + auto ptr = reinterpret_cast(errorMessages->GetBufferPointer()); + std::string buffer; + buffer.reserve(errorMessages->GetBufferSize()); + std::copy(ptr, ptr + errorMessages->GetBufferSize(), std::back_inserter(buffer)); + + LOG(buffer); + return; + } + } + }); using namespace std::chrono_literals; while (future.wait_for(0ms) != std::future_status::ready) diff --git a/ChaosMod/Memory/Memory.cpp b/ChaosMod/Memory/Memory.cpp index 4e67ca168..7e153aef4 100644 --- a/ChaosMod/Memory/Memory.cpp +++ b/ChaosMod/Memory/Memory.cpp @@ -196,7 +196,8 @@ namespace Memory return {}; auto resultAddr = reinterpret_cast(thePattern.get_first()); - DEBUG_LOG("Found pattern \"" << pattern << "\" at address 0x" << std::uppercase << std::hex << resultAddr << std::dec); + DEBUG_LOG("Found pattern \"" << pattern << "\" at address 0x" << std::uppercase << std::hex << resultAddr + << std::dec); return resultAddr; }; diff --git a/ChaosMod/Memory/Snow.h b/ChaosMod/Memory/Snow.h index b559b7c6a..42f9bdaf8 100644 --- a/ChaosMod/Memory/Snow.h +++ b/ChaosMod/Memory/Snow.h @@ -13,11 +13,11 @@ namespace Memory { /* Thanks to menyoo! */ - static bool init = false; + static bool init = false; static bool isPre3095 = getGameVersion() < eGameVersion::VER_1_0_3095_0; - auto snowPattern1 = isPre3095 ? "80 3D ?? ?? ?? ?? 00 74 25 B9 40 00 00 00" - : "44 38 ?? ?? ?? ?? 01 74 12 B9 40 00 00 00"; + auto snowPattern1 = + isPre3095 ? "80 3D ?? ?? ?? ?? 00 74 25 B9 40 00 00 00" : "44 38 ?? ?? ?? ?? 01 74 12 B9 40 00 00 00"; static auto handle = FindPattern(snowPattern1); if (!handle.IsValid()) return; diff --git a/ConfigApp/EffectConfig.xaml.cs b/ConfigApp/EffectConfig.xaml.cs index d5764fbde..f7f1a24a3 100644 --- a/ConfigApp/EffectConfig.xaml.cs +++ b/ConfigApp/EffectConfig.xaml.cs @@ -41,7 +41,9 @@ public EffectConfig(string? effectId, EffectData? effectData, EffectInfo effectI if (m_IsTimedEffect) { - effectconf_timer_type_enable.IsChecked = m_EffectData.TimedType.HasValue && m_EffectData.TimedType != defaultTimedType; + effectconf_timer_type_enable.IsChecked = m_EffectData.TimedType.HasValue && + m_EffectData.TimedType != defaultTimedType && + m_EffectData.TimedType != EffectTimedType.Custom; effectconf_timer_type.ItemsSource = new string[] { "Normal", diff --git a/ConfigApp/EffectsTreeMenuItem.cs b/ConfigApp/EffectsTreeMenuItem.cs index 8b0709387..3e04fe0a3 100644 --- a/ConfigApp/EffectsTreeMenuItem.cs +++ b/ConfigApp/EffectsTreeMenuItem.cs @@ -87,7 +87,7 @@ public ICommand OnConfigureCommand { get => new TreeMenuItemAction(OnConfigureClick); } - + public TreeMenuItem(string text, TreeMenuItem? parent = null) { Text = text; diff --git a/ConfigApp/Workshop/WorkshopSubmissionFileHandler.cs b/ConfigApp/Workshop/WorkshopSubmissionFileHandler.cs index 37d643181..f28421e3a 100644 --- a/ConfigApp/Workshop/WorkshopSubmissionFileHandler.cs +++ b/ConfigApp/Workshop/WorkshopSubmissionFileHandler.cs @@ -6,9 +6,9 @@ using System.Linq; using System.Text; using System.Threading.Tasks; - using Newtonsoft.Json.Linq; using System.Windows; using Newtonsoft.Json; + using Newtonsoft.Json.Linq; public class WorkshopSubmissionFileHandler { diff --git a/TwitchChatVotingProxy/ChaosPipe/ChaosPipeClient.cs b/TwitchChatVotingProxy/ChaosPipe/ChaosPipeClient.cs index b627492a5..6d1b6753f 100644 --- a/TwitchChatVotingProxy/ChaosPipe/ChaosPipeClient.cs +++ b/TwitchChatVotingProxy/ChaosPipe/ChaosPipeClient.cs @@ -256,7 +256,7 @@ private void ChangeVotingMode(List? options) m_Logger.Error("Unknown voting mode: " + modeName); return; } - + m_Logger.Information("Setting voting mode to " + modeName); OnSetVotingMode?.Invoke(this, new((EVotingMode)mode)); }