F4MP/tiltedcode/Code/client/Services/Debug/DebugService.cpp

447 lines
14 KiB
C++
Raw Normal View History

#include <BranchInfo.h>
#include <Havok/hkbStateMachine.h>
#include <Structs/AnimationGraphDescriptorManager.h>
#include <Havok/BShkbAnimationGraph.h>
#include <Havok/BShkbHkxDB.h>
#include <Havok/hkbBehaviorGraph.h>
#include <Services/ImguiService.h>
#include <Services/DebugService.h>
#include <Services/TransportService.h>
#include <Services/PapyrusService.h>
#include <Services/QuestService.h>
#include <Events/UpdateEvent.h>
#include <Events/DialogueEvent.h>
#include <Events/SubtitleEvent.h>
#include <Events/MoveActorEvent.h>
#include <Events/ConnectionErrorEvent.h>
#include <Games/References.h>
#include <BSAnimationGraphManager.h>
#include <Forms/TESFaction.h>
#include <Forms/TESQuest.h>
#include <Forms/BGSAction.h>
#include <Forms/TESIdleForm.h>
#include <Forms/TESNPC.h>
#include <Games/Animation/ActorMediator.h>
#include <Games/Animation/TESActionData.h>
#include <Magic/ActorMagicCaster.h>
#include <Misc/BSFixedString.h>
#include <Structs/ActionEvent.h>
#include <Components.h>
#include <World.h>
#include <Forms/TESObjectCELL.h>
#include <Forms/TESWorldSpace.h>
#include <Games/TES.h>
#include <AI/AIProcess.h>
#include <Messages/RequestRespawn.h>
#include <Messages/PartyCreateRequest.h>
#include <Messages/PartyLeaveRequest.h>
#include <Games/Misc/SubtitleManager.h>
#include <Games/Overrides.h>
#include <OverlayApp.hpp>
#include <EquipManager.h>
#include <Forms/TESAmmo.h>
#include <BSGraphics/BSGraphicsRenderer.h>
#include <Interface/UI.h>
#include <Combat/CombatController.h>
#include <Camera/PlayerCamera.h>
#include <AI/Movement/PlayerControls.h>
#include <Interface/IMenu.h>
#include <Camera/PlayerCamera.h>
#include <DefaultObjectManager.h>
#include <Misc/InventoryEntry.h>
#include <Misc/MiddleProcess.h>
#include <imgui.h>
#include <inttypes.h>
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<float>((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<TESNPC*>(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<UpdateEvent>().connect<&DebugService::OnUpdate>(this);
m_drawImGuiConnection = aImguiService.OnDraw.connect<&DebugService::OnDraw>(this);
m_dialogueConnection = m_dispatcher.sink<DialogueEvent>().connect<&DebugService::OnDialogue>(this);
m_dispatcher.sink<SubtitleEvent>().connect<&DebugService::OnSubtitle>(this);
m_dispatcher.sink<MoveActorEvent>().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<Actor>(TESForm::GetById(acEvent.FormId));
TESObjectCELL* pCell = Cast<TESObjectCELL>(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<bool> s_f8Pressed = false;
static std::atomic<bool> s_f7Pressed = false;
static std::atomic<bool> 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<FormIdComponent>();
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<Actor>(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<float>(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<float>(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
}