mirror of
https://github.com/Jous99/F4MP.git
synced 2026-01-12 14:00:53 +01:00
447 lines
14 KiB
C++
447 lines
14 KiB
C++
|
|
#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
|
||
|
|
}
|