#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern thread_local bool g_overrideFormId; constexpr char kBuildTag[] = "Build: " BUILD_COMMIT " " BUILD_BRANCH " EVO\nBuilt: " __TIMESTAMP__; static void DrawBuildTag() { auto* pWindow = BSGraphics::GetMainWindow(); const ImVec2 coord{50.f, static_cast((pWindow->uiWindowHeight + 25) - 100)}; ImGui::GetBackgroundDrawList()->AddText(ImGui::GetFont(), ImGui::GetFontSize(), coord, ImColor::ImColor(255.f, 0.f, 0.f), kBuildTag); } void __declspec(noinline) DebugService::PlaceActorInWorld() noexcept { if (m_actors.size()) return; const auto pPlayerBaseForm = static_cast(PlayerCharacter::Get()->baseForm); auto pActor = Actor::Create(pPlayerBaseForm); const Inventory inventory = PlayerCharacter::Get()->GetActorInventory(); pActor->SetActorInventory(inventory); pActor->GetExtension()->SetPlayer(true); m_actors.emplace_back(pActor); } DebugService::DebugService(entt::dispatcher& aDispatcher, World& aWorld, TransportService& aTransport, ImguiService& aImguiService) : m_dispatcher(aDispatcher) , m_transport(aTransport) , m_world(aWorld) { m_updateConnection = m_dispatcher.sink().connect<&DebugService::OnUpdate>(this); m_drawImGuiConnection = aImguiService.OnDraw.connect<&DebugService::OnDraw>(this); m_dialogueConnection = m_dispatcher.sink().connect<&DebugService::OnDialogue>(this); m_dispatcher.sink().connect<&DebugService::OnSubtitle>(this); m_dispatcher.sink().connect<&DebugService::OnMoveActor>(this); } void DebugService::OnDialogue(const DialogueEvent& acEvent) noexcept { if (ActorID) return; ActorID = acEvent.ActorID; VoiceFile = acEvent.VoiceFile; } void DebugService::OnSubtitle(const SubtitleEvent& acEvent) noexcept { if (SubActorID) return; SubActorID = acEvent.SpeakerID; SubtitleText = acEvent.Text; TopicID = acEvent.TopicFormID; } // TODO: yeah, i'm aware of how dumb this looks, but things crash if // you do it directly by adding an event to the queue, no symbols for tiltedcore when debugging, // so this'll do for now struct MoveData { Actor* pActor = nullptr; TESObjectCELL* pCell = nullptr; NiPoint3 position; } moveData; void DebugService::OnMoveActor(const MoveActorEvent& acEvent) noexcept { Actor* pActor = Cast(TESForm::GetById(acEvent.FormId)); TESObjectCELL* pCell = Cast(TESForm::GetById(acEvent.CellId)); if (!pActor || !pCell) return; moveData.pActor = pActor; moveData.pCell = pCell; moveData.position = acEvent.Position; } extern thread_local bool g_forceAnimation; void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { if (!BSGraphics::GetMainWindow()->IsForeground()) return; if (moveData.pActor) { moveData.pActor->MoveTo(moveData.pCell, moveData.position); moveData.pActor = nullptr; } static std::atomic s_f8Pressed = false; static std::atomic s_f7Pressed = false; static std::atomic s_f6Pressed = false; if (GetAsyncKeyState(VK_F3) & 0x01) { m_showDebugStuff = !m_showDebugStuff; } #if (!IS_MASTER) if (GetAsyncKeyState(VK_F6)) { if (!s_f6Pressed) { s_f6Pressed = true; static char s_address[256] = "127.0.0.1:10578"; if (!m_transport.IsOnline()) m_transport.Connect(s_address); else m_transport.Close(); } } else s_f6Pressed = false; if (GetAsyncKeyState(VK_F7)) { if (!s_f7Pressed) { s_f7Pressed = true; if (!m_world.GetPartyService().IsInParty()) m_transport.Send(PartyCreateRequest{}); else m_transport.Send(PartyLeaveRequest{}); } } else s_f7Pressed = false; if (GetAsyncKeyState(VK_F8) & 0x01) { if (!s_f8Pressed) { s_f8Pressed = true; //PlaceActorInWorld(); } } else s_f8Pressed = false; #endif } static bool g_enableServerWindow{false}; static bool g_enableAnimWindow{false}; static bool g_enableEntitiesWindow{false}; static bool g_enableInventoryWindow{false}; static bool g_enableNetworkWindow{false}; static bool g_enableFormsWindow{false}; static bool g_enablePlayerWindow{false}; static bool g_enableSkillsWindow{false}; static bool g_enablePartyWindow{false}; static bool g_enableActorValuesWindow{false}; static bool g_enableQuestWindow{false}; static bool g_enableCellWindow{false}; static bool g_enableProcessesWindow{false}; static bool g_enableWeatherWindow{false}; static bool g_enableCombatWindow{false}; static bool g_enableCalendarWindow{false}; static bool g_enableDragonSpawnerWindow{false}; void DebugService::DrawServerView() noexcept { ImGui::SetNextWindowSize(ImVec2(250, 440), ImGuiCond_FirstUseEver); ImGui::Begin("Server"); static char s_address[1024] = "127.0.0.1:10578"; static char s_password[1024] = ""; ImGui::InputText("Address", s_address, std::size(s_address)); ImGui::InputText("Password", s_password, std::size(s_password)); if (m_transport.IsOnline()) { if (ImGui::Button("Disconnect")) m_transport.Close(); } else { if (ImGui::Button("Connect")) { m_transport.SetServerPassword(s_password); m_transport.Connect(s_address); } } ImGui::End(); } void DebugService::OnDraw() noexcept { const auto view = m_world.view(); if (view.empty() || !m_showDebugStuff) return; ImGui::BeginMainMenuBar(); if (ImGui::BeginMenu("Helpers")) { if (ImGui::Button("Unstuck player")) { auto* pPlayer = PlayerCharacter::Get(); pPlayer->currentProcess->KnockExplosion(pPlayer, &pPlayer->position, 0.f); } if (ImGui::Button("Stop all combat")) { auto* pPlayer = PlayerCharacter::Get(); pPlayer->PayCrimeGoldToAllFactions(); ProcessLists* const pProcessLists = ProcessLists::Get(); if (pProcessLists) { for (uint32_t i = 0; i < pProcessLists->highActorHandleArray.length; ++i) { Actor* const pRefr = Cast(TESObjectREFR::GetByHandle(pProcessLists->highActorHandleArray[i])); if (pRefr && pRefr->GetNiNode()) pRefr->StopCombat(); } } } ImGui::EndMenu(); } #if (!IS_MASTER) if (ImGui::BeginMenu("Components")) { ImGui::MenuItem("Show selected entity in world", nullptr, &m_drawComponentsInWorldSpace); ImGui::EndMenu(); } if (ImGui::BeginMenu("UI")) { ImGui::MenuItem("Show build tag", nullptr, &m_showBuildTag); if (ImGui::Button("Log all open windows")) { UI* pUI = UI::Get(); for (const auto& it : pUI->menuMap) { if (pUI->GetMenuOpen(it.key)) spdlog::info("{}", it.key.AsAscii()); } } if (ImGui::Button("Close all menus")) { UI::Get()->CloseAllMenus(); } ImGui::EndMenu(); } #endif if (ImGui::BeginMenu("Debuggers")) { ImGui::MenuItem("Quests", nullptr, &g_enableQuestWindow); ImGui::MenuItem("Entities", nullptr, &g_enableEntitiesWindow); ImGui::MenuItem("Server", nullptr, &g_enableServerWindow); ImGui::MenuItem("Party", nullptr, &g_enablePartyWindow); ImGui::MenuItem("Dragon spawner", nullptr, &g_enableDragonSpawnerWindow); #if (!IS_MASTER) ImGui::MenuItem("Network", nullptr, &g_enableNetworkWindow); ImGui::MenuItem("Forms", nullptr, &g_enableFormsWindow); ImGui::MenuItem("Inventory", nullptr, &g_enableInventoryWindow); ImGui::MenuItem("Animations", nullptr, &g_enableAnimWindow); ImGui::MenuItem("Player", nullptr, &g_enablePlayerWindow); ImGui::MenuItem("Skills", nullptr, &g_enableSkillsWindow); ImGui::MenuItem("Cell", nullptr, &g_enableCellWindow); ImGui::MenuItem("Processes", nullptr, &g_enableProcessesWindow); ImGui::MenuItem("Weather", nullptr, &g_enableWeatherWindow); ImGui::MenuItem("Combat", nullptr, &g_enableCombatWindow); ImGui::MenuItem("Calendar", nullptr, &g_enableCalendarWindow); #endif ImGui::EndMenu(); } if (ImGui::BeginMenu("Misc")) { if (ImGui::Button("Crash Client")) { #if (!IS_MASTER) spdlog::error("Crash client"); int* m = 0; *m = 1338; #else ConnectionErrorEvent errorEvent{}; errorEvent.ErrorDetail = "Skyrim Together never crashes ;) With love, Yamashi, Force, Dragonisser, Cosideci."; m_world.GetRunner().Trigger(errorEvent); #endif } ImGui::EndMenu(); } ImGui::EndMainMenuBar(); if (g_enableQuestWindow) DrawQuestDebugView(); if (g_enableEntitiesWindow) DrawEntitiesView(); if (g_enableServerWindow) DrawServerView(); if (g_enablePartyWindow) DrawPartyView(); if (g_enableDragonSpawnerWindow) DrawDragonSpawnerView(); #if (!IS_MASTER) if (g_enableNetworkWindow) DrawNetworkView(); if (g_enableFormsWindow) DrawFormDebugView(); if (g_enableInventoryWindow) DrawContainerDebugView(); if (g_enableAnimWindow) DrawAnimDebugView(); if (g_enablePlayerWindow) DrawPlayerDebugView(); if (g_enableSkillsWindow) DrawSkillView(); if (g_enableActorValuesWindow) DrawActorValuesView(); if (g_enableCellWindow) DrawCellView(); if (g_enableProcessesWindow) DrawProcessView(); if (g_enableWeatherWindow) DrawWeatherView(); if (g_enableCombatWindow) DrawCombatView(); if (g_enableCalendarWindow) DrawCalendarView(); if (m_drawComponentsInWorldSpace) DrawComponentDebugView(); #endif if (m_showBuildTag) DrawBuildTag(); } void DebugService::ArrangeGameWindows(HWND aThisWindow) noexcept { // This function conveniently arranges multiple game windows (multi-monitor window rearrangement is // not implemented) #if (!IS_MASTER) const uint32_t screenWidth = GetSystemMetrics(SM_CXSCREEN); const uint32_t screenHeight = GetSystemMetrics(SM_CYSCREEN); RECT rect{}; GetWindowRect(aThisWindow, &rect); const uint32_t gameWindowWidth = rect.right - rect.left; const uint32_t gameWindowHeight = rect.bottom - rect.top; const bool shouldArrangeWindows = (static_cast(gameWindowWidth) / screenWidth) < 0.76f; if (!shouldArrangeWindows) return; // Too little room for that SetWindowPos(GetConsoleWindow(), nullptr, 0, gameWindowHeight, 0, 0, SWP_NOSIZE); CreateMutexA(0, FALSE, "SkyrimTogetherWindowMutex"); if (GetLastError() == ERROR_ALREADY_EXISTS) { // One game instance is already open, arrange secondary game window... const bool arrangeSideBySide = (static_cast(gameWindowWidth) / screenWidth) <= 0.51f; RECT conRect{}; GetWindowRect(GetConsoleWindow(), &conRect); const uint32_t conY = arrangeSideBySide ? gameWindowHeight : 0; const uint32_t conX = arrangeSideBySide ? (gameWindowWidth - 16) : screenWidth - (conRect.right - conRect.left); SetWindowPos(GetConsoleWindow(), nullptr, conX, conY, 0, 0, SWP_NOSIZE); const uint32_t x = arrangeSideBySide ? (gameWindowWidth - 16) : (screenWidth - gameWindowWidth); const uint32_t y = arrangeSideBySide ? 0 : (screenHeight - gameWindowHeight - 48); SetWindowPos(aThisWindow, nullptr, x, y, 0, 0, SWP_NOSIZE); } #endif }