diff --git a/Code/client/CrashHandler.cpp b/Code/client/CrashHandler.cpp index d6c113d97..092fc64c8 100644 --- a/Code/client/CrashHandler.cpp +++ b/Code/client/CrashHandler.cpp @@ -2,6 +2,7 @@ #include "CrashHandler.h" #include #include +#include #include #include #include @@ -23,16 +24,12 @@ std::string SerializeTimePoint(const time_point& time, const std::string& format LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { - static int alreadyCrashed = 0; + static std::atomic_bool s_alreadyCrashed = false; auto retval = EXCEPTION_CONTINUE_SEARCH; - // Serialize - static std::mutex singleThreaded; - const std::lock_guard lock{singleThreaded}; - - // Check for severe, not continuable and not software-originated exception + // Handle only the first access violation. Later or nested ones pass through instead of blocking. if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && - alreadyCrashed++ == 0) + !s_alreadyCrashed.exchange(true)) { spdlog::critical (__FUNCTION__ ": crash occurred!"); @@ -115,13 +112,13 @@ LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { spdlog::critical(__FUNCTION__ ": UnhandledExceptionFilter() workaround triggered."); - singleThreaded.unlock(); // Might reenter, but is safe at this point. if ((*pCurrentUnhandledExceptionFilter)(pExceptionInfo) == EXCEPTION_CONTINUE_EXECUTION) retval = EXCEPTION_CONTINUE_EXECUTION; - singleThreaded.lock(); } - spdlog::shutdown(); + // The exception may still be handled, and logging after spdlog::shutdown() crashes + if (auto pLogger = spdlog::default_logger()) + pLogger->flush(); } return retval; } diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 7d3ac21af..9763e1433 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -576,6 +576,13 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) return; } + // Enabling and killing it here would run its death scripts again, e.g. repeating quest stages and rewards + if (acMessage.IsDead && pActor->IsDisabled() && pActor->GetExtension()->Reconciliation != ActorExtension::ReconciliationStage::WaitingForDisable) + { + spdlog::info("Skipped spawn for dead actor {:X} that is disabled locally, server id: {:X}", cActorId, acMessage.ServerId); + return; + } + const auto view = m_world.view(); const auto itor = std::find_if(std::begin(view), std::end(view), [cActorId, view](entt::entity entity) { return view.get(entity).Id == cActorId; }); @@ -1286,6 +1293,15 @@ void CharacterService::MoveActor(const Actor* apActor, const GameId& acWorldSpac { const uint32_t cWorldSpaceId = m_world.GetModSystem().GetGameId(acWorldSpaceId); TESWorldSpace* const pWorldSpace = Cast(TESForm::GetById(cWorldSpaceId)); + + // Loading a cell outside the player's worldspace can crash the reference promote tasks and strands the actor + // there in our save. It's placed when it spawns instead. + if (pWorldSpace && pWorldSpace != PlayerCharacter::Get()->GetWorldSpace()) + { + spdlog::info(__FUNCTION__ ": skipped moving actor {:X} to worldspace {:X}, the player isn't in it", apActor->formID, cWorldSpaceId); + return; + } + if (pWorldSpace) { GridCellCoords coordinates = GridCellCoords::CalculateGridCellCoords(acPosition); diff --git a/Code/client/Services/Generic/QuestService.cpp b/Code/client/Services/Generic/QuestService.cpp index f6a5d39b8..94c644e55 100644 --- a/Code/client/Services/Generic/QuestService.cpp +++ b/Code/client/Services/Generic/QuestService.cpp @@ -517,6 +517,14 @@ void QuestService::OnQuestUpdate(const NotifyQuestUpdate& aUpdate) noexcept return; } + // Like the game, never restart a run once quest that's already done here, which would hand out its rewards again + const bool cIsStartOrStage = aUpdate.Status == NotifyQuestUpdate::Started || aUpdate.Status == NotifyQuestUpdate::StageUpdate; + if (cIsStartOrStage && (pQuest->flags & TESQuest::Flags::RunOnce) && (pQuest->flags & TESQuest::Flags::Completed)) + { + spdlog::info("Remote quest {:X} is a completed run once quest here, ignoring stage {}", formId, aUpdate.Stage); + return; + } + if (aUpdate.Status == NotifyQuestUpdate::StageUpdate && pQuest->IsStageReached(aUpdate.Stage)) { spdlog::info("Remote quest {:X} already reached stage {}, current stage: {}", formId, aUpdate.Stage, pQuest->currentStage);