diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b81608b..b7c5c037 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,27 +2,27 @@ name: Build Project on: push: - tags: + tags: - v[0-9].[0-9]+.[0-9]+* # pr's will trigger this action. i think the idea here is to verify that a build is passing before merging. pull_request: - branches: + branches: - main jobs: Build_Release: - runs-on: windows-latest + runs-on: windows-2022 steps: - name: Checkout uses: actions/checkout@v2.3.4 - name: Build Release shell: bash - run: '"/c/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/MSBuild/Current/Bin/MSBuild.exe" -property:Configuration=Release' + run: '"C:/Program Files/Microsoft Visual Studio/2022/Enterprise/Msbuild/Current/Bin/MSBuild.exe" -property:Configuration=Release' - name: Build Release_Version shell: bash - run: '"/c/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/MSBuild/Current/Bin/MSBuild.exe" -property:Configuration=Release_Version' + run: '"C:/Program Files/Microsoft Visual Studio/2022/Enterprise/Msbuild/Current/Bin/MSBuild.exe" -property:Configuration=Release_Version' - name: Package Release Builds if: ${{ github.event_name == 'push' }} @@ -40,18 +40,18 @@ jobs: path: Release.zip Build_Debug: - runs-on: windows-latest + runs-on: windows-2022 steps: - name: Checkout uses: actions/checkout@v2.3.4 - name: Build Debug shell: bash - run: '"/c/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/MSBuild/Current/Bin/MSBuild.exe" -property:Configuration=Debug' + run: '"C:/Program Files/Microsoft Visual Studio/2022/Enterprise/Msbuild/Current/Bin/MSBuild.exe" -property:Configuration=Debug' - name: Build Debug_Version shell: bash - run: '"/c/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/MSBuild/Current/Bin/MSBuild.exe" -property:Configuration=Debug_Version' + run: '"C:/Program Files/Microsoft Visual Studio/2022/Enterprise/Msbuild/Current/Bin/MSBuild.exe" -property:Configuration=Debug_Version' - name: Package Debug Builds if: ${{ github.event_name == 'push' }} @@ -70,7 +70,7 @@ jobs: AutoRelease: if: ${{ github.event_name == 'push' }} - runs-on: windows-latest + runs-on: windows-2022 needs: [Build_Release, Build_Debug] steps: - name: Parse tag semver diff --git a/AmongUsMenu.vcxproj b/AmongUsMenu.vcxproj index 8d7f72e5..e86617cc 100644 --- a/AmongUsMenu.vcxproj +++ b/AmongUsMenu.vcxproj @@ -71,6 +71,7 @@ + @@ -347,6 +348,7 @@ + @@ -382,9 +384,19 @@ + + + + + + + + + + @@ -406,14 +418,14 @@ true Unicode - v142 + v143 true DynamicLibrary true Unicode - v142 + v143 true Static @@ -423,7 +435,7 @@ true Unicode - v142 + v143 Static @@ -431,7 +443,7 @@ false true Unicode - v142 + v143 @@ -475,7 +487,7 @@ pch-il2cpp.h true $(ProjectDir)appdata;$(ProjectDir)events;$(ProjectDir)framework;$(ProjectDir)gui;$(ProjectDir)hooks;$(ProjectDir)includes;$(ProjectDir)resources;$(ProjectDir)rpc;$(ProjectDir)user;$(ProjectDir) - stdcpp17 + stdcpplatest MultiThreadedDebug true false @@ -518,7 +530,7 @@ git rev-parse --abbrev-ref HEAD >> gitparams.h pch-il2cpp.h true $(ProjectDir)appdata;$(ProjectDir)events;$(ProjectDir)framework;$(ProjectDir)gui;$(ProjectDir)hooks;$(ProjectDir)includes;$(ProjectDir)resources;$(ProjectDir)rpc;$(ProjectDir)user;$(ProjectDir) - stdcpp17 + stdcpplatest MultiThreadedDebug true false @@ -563,7 +575,7 @@ git rev-parse --abbrev-ref HEAD >> gitparams.h pch-il2cpp.h true $(ProjectDir)appdata;$(ProjectDir)events;$(ProjectDir)framework;$(ProjectDir)gui;$(ProjectDir)hooks;$(ProjectDir)includes;$(ProjectDir)resources;$(ProjectDir)rpc;$(ProjectDir)user;$(ProjectDir) - stdcpp17 + stdcpplatest MultiThreaded None true @@ -611,7 +623,7 @@ git rev-parse --abbrev-ref HEAD >> gitparams.h pch-il2cpp.h true $(ProjectDir)appdata;$(ProjectDir)events;$(ProjectDir)framework;$(ProjectDir)gui;$(ProjectDir)hooks;$(ProjectDir)includes;$(ProjectDir)resources;$(ProjectDir)rpc;$(ProjectDir)user;$(ProjectDir) - stdcpp17 + stdcpplatest MultiThreaded None true diff --git a/AmongUsMenu.vcxproj.filters b/AmongUsMenu.vcxproj.filters index 54117b62..755b031b 100644 --- a/AmongUsMenu.vcxproj.filters +++ b/AmongUsMenu.vcxproj.filters @@ -283,6 +283,9 @@ gui\tabs + + user + @@ -460,6 +463,9 @@ gui\tabs + + user + @@ -474,6 +480,36 @@ resources + + resources + + + resources + + + resources + + + resources + + + resources + + + resources + + + resources + + + resources + + + resources + + + resources + diff --git a/README.md b/README.md index f46f4343..61bd9188 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,10 @@ Will automatically be loaded by the Game itself if the dll is in the game direct

-## Credits -* [void*](https://github.com/v0idp) (Maintainer & Creator) -* [std-nullptr](https://github.com/std-nullptr) (Maintainer & IL2CPP Madman) -* [OsOmE1](https://github.com/OsOmE1) (Maintainer) -* [RealMVC](https://github.com/RealMVC) (Contributor) -* [Kyreus](https://github.com/KyreusR) (Contributor) -* [manianac](https://github.com/manianac) (Contributor) +## Special Thanks +* [KulaGGin](https://github.com/KulaGGin) (Helped with some ImGui code for replay system) +* [tomsa000](https://github.com/tomsa000) (Helped with fixing memory leaks and smart pointers) +* Everyone else who contributed to the code and I couldn't list here. Thank you! ## Contributing diff --git a/appdata/il2cpp-functions.h b/appdata/il2cpp-functions.h index 49cbce2d..e377d413 100644 --- a/appdata/il2cpp-functions.h +++ b/appdata/il2cpp-functions.h @@ -25,6 +25,7 @@ DO_APP_FUNC(Camera*, Camera_get_main, (MethodInfo* method), "UnityEngine.CoreMod DO_APP_FUNC(void, Camera_set_orthographicSize, (Camera* __this, float value, MethodInfo* method), "UnityEngine.CoreModule, System.Void UnityEngine.Camera::set_orthographicSize(System.Single)"); DO_APP_FUNC(float, Camera_get_orthographicSize, (Camera* __this, MethodInfo* method), "UnityEngine.CoreModule, System.Single UnityEngine.Camera::get_orthographicSize()"); DO_APP_FUNC(Color, SpriteRenderer_get_color, (SpriteRenderer* __this, MethodInfo* method), "UnityEngine.CoreModule, UnityEngine.Color UnityEngine.SpriteRenderer::get_color()"); +DO_APP_FUNC(float, Time_get_fixedDeltaTime, (MethodInfo* method), "UnityEngine.CoreModule, System.Single UnityEngine.Time::get_fixedDeltaTime()"); DO_APP_FUNC(int32_t, Screen_get_width, (MethodInfo* method), "UnityEngine.CoreModule, System.Int32 UnityEngine.Screen::get_width()"); DO_APP_FUNC(int32_t, Screen_get_height, (MethodInfo* method), "UnityEngine.CoreModule, System.Int32 UnityEngine.Screen::get_height()"); diff --git a/events/CastVoteEvent.cpp b/events/CastVoteEvent.cpp index 035c7650..20dc903f 100644 --- a/events/CastVoteEvent.cpp +++ b/events/CastVoteEvent.cpp @@ -14,10 +14,7 @@ void CastVoteEvent::Output() { if (target.has_value()) ImGui::TextColored(AmongUsColorToImVec4(GetPlayerColor(target->colorId)), target->playerName.c_str()); else ImGui::Text("Skipped"); ImGui::SameLine(); - auto sec = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - auto min = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - if (sec < 60) ImGui::Text(" [%ds ago]", sec); - else ImGui::Text(" [%dm ago]", min); + ImGui::Text("[%s ago]", std::format("{:%OM:%OS}", (std::chrono::system_clock::now() - this->timestamp)).c_str()); } void CastVoteEvent::ColoredEventOutput() { diff --git a/events/CheatDetectedEvent.cpp b/events/CheatDetectedEvent.cpp index 7564ee00..43509430 100644 --- a/events/CheatDetectedEvent.cpp +++ b/events/CheatDetectedEvent.cpp @@ -2,7 +2,7 @@ #include "_events.h" #include "utility.h" -CheatDetectedEvent::CheatDetectedEvent(EVENT_PLAYER source, CHEAT_ACTION action) : EventInterface(source, EVENT_CHEAT) { +CheatDetectedEvent::CheatDetectedEvent(EVENT_PLAYER source, CHEAT_ACTIONS action) : EventInterface(source, EVENT_CHEAT) { this->action = action; } @@ -13,10 +13,7 @@ void CheatDetectedEvent::Output() { ImGui::SameLine(); ImGui::Text("Cheat detected: %s", CHEAT_ACTION_NAMES[this->action]); ImGui::SameLine(); - auto sec = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - auto min = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - if (sec < 60) ImGui::Text(" [%ds ago]", sec); - else ImGui::Text(" [%dm ago]", min); + ImGui::Text("[%s ago]", std::format("{:%OM:%OS}", (std::chrono::system_clock::now() - this->timestamp)).c_str()); } void CheatDetectedEvent::ColoredEventOutput() { diff --git a/events/DisconnectEvent.cpp b/events/DisconnectEvent.cpp index e8dee8c8..c5a05e0b 100644 --- a/events/DisconnectEvent.cpp +++ b/events/DisconnectEvent.cpp @@ -9,10 +9,7 @@ void DisconnectEvent::Output() { ImGui::SameLine(); ImGui::Text("has left the game"); ImGui::SameLine(); - auto sec = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - auto min = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - if (sec < 60) ImGui::Text(" [%ds ago]", sec); - else ImGui::Text(" [%dm ago]", min); + ImGui::Text("[%s ago]", std::format("{:%OM:%OS}", (std::chrono::system_clock::now() - this->timestamp)).c_str()); } void DisconnectEvent::ColoredEventOutput() { diff --git a/events/KillEvent.cpp b/events/KillEvent.cpp index 64854f7d..bbd2897e 100644 --- a/events/KillEvent.cpp +++ b/events/KillEvent.cpp @@ -2,8 +2,9 @@ #include "_events.h" #include "utility.h" -KillEvent::KillEvent(EVENT_PLAYER source, EVENT_PLAYER target, Vector2 position) : EventInterface(source, EVENT_KILL) { +KillEvent::KillEvent(EVENT_PLAYER source, EVENT_PLAYER target, Vector2 position, Vector2 targetPosition) : EventInterface(source, EVENT_KILL) { this->target = target; + this->targetPosition = targetPosition; this->position = position; this->systemType = GetSystemTypes(position); } @@ -17,10 +18,7 @@ void KillEvent::Output() { ImGui::SameLine(); ImGui::Text("(%s)", TranslateSystemTypes(systemType)); ImGui::SameLine(); - auto sec = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - auto min = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - if (sec < 60) ImGui::Text(" [%ds ago]", sec); - else ImGui::Text(" [%dm ago]", min); + ImGui::Text("[%s ago]", std::format("{:%OM:%OS}", (std::chrono::system_clock::now() - this->timestamp)).c_str()); } void KillEvent::ColoredEventOutput() { diff --git a/events/ProtectPlayerEvent.cpp b/events/ProtectPlayerEvent.cpp index 40a13974..c164296e 100644 --- a/events/ProtectPlayerEvent.cpp +++ b/events/ProtectPlayerEvent.cpp @@ -13,10 +13,7 @@ void ProtectPlayerEvent::Output() { ImGui::SameLine(); ImGui::TextColored(AmongUsColorToImVec4(GetPlayerColor(target.colorId)), target.playerName.c_str()); ImGui::SameLine(); - auto sec = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - auto min = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - if (sec < 60) ImGui::Text(" [%ds ago]", sec); - else ImGui::Text(" [%dm ago]", min); + ImGui::Text("[%s ago]", std::format("{:%OM:%OS}", (std::chrono::system_clock::now() - this->timestamp)).c_str()); } void ProtectPlayerEvent::ColoredEventOutput() { diff --git a/events/ReportDeadBodyEvent.cpp b/events/ReportDeadBodyEvent.cpp index ba58e01a..f1040b45 100644 --- a/events/ReportDeadBodyEvent.cpp +++ b/events/ReportDeadBodyEvent.cpp @@ -2,10 +2,11 @@ #include "_events.h" #include "utility.h" -ReportDeadBodyEvent::ReportDeadBodyEvent(EVENT_PLAYER source, std::optional target, Vector2 position) +ReportDeadBodyEvent::ReportDeadBodyEvent(EVENT_PLAYER source, std::optional target, Vector2 position, std::optional targetPosition) : EventInterface(source, (target.has_value() ? EVENT_REPORT : EVENT_MEETING)) { this->target = target; this->position = position; + this->targetPosition = targetPosition; this->systemType = GetSystemTypes(position); } @@ -20,10 +21,7 @@ void ReportDeadBodyEvent::Output() { } ImGui::Text("(%s)", TranslateSystemTypes(systemType)); ImGui::SameLine(); - auto sec = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - auto min = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - if (sec < 60) ImGui::Text(" [%ds ago]", sec); - else ImGui::Text(" [%dm ago]", min); + ImGui::Text("[%s ago]", std::format("{:%OM:%OS}", (std::chrono::system_clock::now() - this->timestamp)).c_str()); } void ReportDeadBodyEvent::ColoredEventOutput() { diff --git a/events/ShapeShiftEvent.cpp b/events/ShapeShiftEvent.cpp index a6c12eb6..2f0f933f 100644 --- a/events/ShapeShiftEvent.cpp +++ b/events/ShapeShiftEvent.cpp @@ -13,10 +13,7 @@ void ShapeShiftEvent::Output() { ImGui::SameLine(); ImGui::TextColored(AmongUsColorToImVec4(GetPlayerColor(target.colorId)), target.playerName.c_str()); ImGui::SameLine(); - auto sec = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - auto min = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - if (sec < 60) ImGui::Text(" [%ds ago]", sec); - else ImGui::Text(" [%dm ago]", min); + ImGui::Text("[%s ago]", std::format("{:%OM:%OS}", (std::chrono::system_clock::now() - this->timestamp)).c_str()); } void ShapeShiftEvent::ColoredEventOutput() { diff --git a/events/TaskCompletedEvent.cpp b/events/TaskCompletedEvent.cpp index 24b78a02..9a0ac506 100644 --- a/events/TaskCompletedEvent.cpp +++ b/events/TaskCompletedEvent.cpp @@ -13,10 +13,7 @@ void TaskCompletedEvent::Output() { ImGui::SameLine(); ImGui::Text("> %s (%s)", (taskType.has_value()) ? TranslateTaskTypes(*taskType) : "UNKOWN" , TranslateSystemTypes(systemType)); ImGui::SameLine(); - auto sec = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - auto min = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - if (sec < 60) ImGui::Text(" [%ds ago]", sec); - else ImGui::Text(" [%dm ago]", min); + ImGui::Text("[%s ago]", std::format("{:%OM:%OS}", (std::chrono::system_clock::now() - this->timestamp)).c_str()); } void TaskCompletedEvent::ColoredEventOutput() { diff --git a/events/VentEvent.cpp b/events/VentEvent.cpp index 2a970f3d..9f69df6c 100644 --- a/events/VentEvent.cpp +++ b/events/VentEvent.cpp @@ -1,27 +1,25 @@ #include "pch-il2cpp.h" #include "_events.h" #include "utility.h" -#include -VentEvent::VentEvent(EVENT_PLAYER source, Vector2 position, VENT_ACTION action) : EventInterface(source, EVENT_VENT) +VentEvent::VentEvent(EVENT_PLAYER source, Vector2 position, VENT_ACTIONS action) : EventInterface(source, EVENT_VENT) { this->position = position; this->systemType = GetSystemTypes(position); this->action = action; } -void VentEvent::Output() { +void VentEvent::Output() +{ ImGui::TextColored(AmongUsColorToImVec4(GetPlayerColor(source.colorId)), source.playerName.c_str()); ImGui::SameLine(); ImGui::Text("(%s)", TranslateSystemTypes(systemType)); ImGui::SameLine(); - auto sec = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - auto min = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - std::chrono::duration_cast(this->timestamp.time_since_epoch()).count(); - if (sec < 60) ImGui::Text(" [%ds ago]", sec); - else ImGui::Text(" [%dm ago]", min); + ImGui::Text("[%s ago]", std::format("{:%OM:%OS}", (std::chrono::system_clock::now() - this->timestamp)).c_str()); } -void VentEvent::ColoredEventOutput() { +void VentEvent::ColoredEventOutput() +{ ImGui::Text("[ VENT"); ImGui::SameLine(); diff --git a/events/_events.h b/events/_events.h index ac7ed6a6..b901e571 100644 --- a/events/_events.h +++ b/events/_events.h @@ -1,34 +1,34 @@ #pragma once #include #include -//#include "utility.h" +#include using namespace app; #define EVENT_TYPES_SIZE 11 enum EVENT_TYPES { - EVENT_KILL = 0, - EVENT_VENT = 1, - EVENT_TASK = 2, - EVENT_REPORT = 3, - EVENT_MEETING = 4, - EVENT_VOTE = 5, - EVENT_CHEAT = 6, - EVENT_DISCONNECT = 7, - EVENT_SHAPESHIFT = 8, - EVENT_PROTECTPLAYER = 9, - EVENT_WALK = 10 + EVENT_KILL, + EVENT_VENT, + EVENT_TASK, + EVENT_REPORT, + EVENT_MEETING, + EVENT_VOTE, + EVENT_CHEAT, + EVENT_DISCONNECT, + EVENT_SHAPESHIFT, + EVENT_PROTECTPLAYER, + EVENT_WALK }; -enum VENT_ACTION { - VENT_ENTER = 0x0, - VENT_EXIT = 0x1 +enum VENT_ACTIONS { + VENT_ENTER, + VENT_EXIT }; -enum CHEAT_ACTION { - CHEAT_TELEPORT = 0x0, - CHEAT_KILL_IMPOSTOR = 0x1 +enum CHEAT_ACTIONS { + CHEAT_TELEPORT, + CHEAT_KILL_IMPOSTOR }; const std::vector CHEAT_ACTION_NAMES = { "Teleporting", "Killed impostor" }; @@ -37,11 +37,15 @@ struct EVENT_PLAYER { uint8_t playerId; uint8_t colorId; std::string playerName; + bool isDead; + bool isAngel; EVENT_PLAYER() { playerId = 0; colorId = 0; playerName = ""; + isDead = false; + isAngel = false; } EVENT_PLAYER(GameData_PlayerInfo* playerInfo) { @@ -70,6 +74,9 @@ struct EVENT_PLAYER { colorId = 0; playerName = "ERROR"; } + + isDead = playerInfo->fields.IsDead; + isAngel = (playerInfo->fields.Role) ? playerInfo->fields.Role->fields.Role == RoleTypes__Enum::GuardianAngel : false; } }; @@ -89,28 +96,52 @@ class EventInterface { virtual void ColoredEventOutput() = 0; EVENT_TYPES getType() { return this->eventType; } EVENT_PLAYER getSource() { return this->source; } + std::chrono::system_clock::time_point GetTimeStamp() { return this->timestamp; } }; class KillEvent : public EventInterface { private: EVENT_PLAYER target; Vector2 position; + Vector2 targetPosition; SystemTypes__Enum systemType; public: - KillEvent(EVENT_PLAYER source, EVENT_PLAYER target, Vector2 position); + KillEvent(EVENT_PLAYER source, EVENT_PLAYER target, Vector2 position, Vector2 targetPosition); virtual void Output() override; virtual void ColoredEventOutput() override; + std::optional GetTarget() { return this->target; } + Vector2 GetPosition() { return this->position; } + Vector2 GetTargetPosition() { return this->targetPosition; } + SystemTypes__Enum GetSystemType() { return this->systemType; } }; class VentEvent : public EventInterface { private: Vector2 position; SystemTypes__Enum systemType; - VENT_ACTION action; + VENT_ACTIONS action; public: - VentEvent(EVENT_PLAYER source, Vector2 position, VENT_ACTION action); + VentEvent(EVENT_PLAYER source, Vector2 position, VENT_ACTIONS action); virtual void Output() override; virtual void ColoredEventOutput() override; + Vector2 GetPosition() { return this->position; } + VENT_ACTIONS GetEventActionEnum() { return this->action; } + std::string GetEventActionString() + { + switch (this->action) + { + case VENT_ACTIONS::VENT_ENTER: + return std::string("Enter"); + break; + + case VENT_ACTIONS::VENT_EXIT: + return std::string("Exit"); + break; + + default: + break; + } + } }; class TaskCompletedEvent : public EventInterface { @@ -122,17 +153,26 @@ class TaskCompletedEvent : public EventInterface { TaskCompletedEvent(EVENT_PLAYER source, std::optional taskType, Vector2 position); virtual void Output() override; virtual void ColoredEventOutput() override; + std::optional GetTaskType() { return this->taskType; } + Vector2 GetPosition() { return this->position; } + SystemTypes__Enum GetSystemType() { return this->systemType; } + }; class ReportDeadBodyEvent : public EventInterface { private: std::optional target; Vector2 position; + std::optional targetPosition; SystemTypes__Enum systemType; public: - ReportDeadBodyEvent(EVENT_PLAYER source, std::optional target, Vector2 position); + ReportDeadBodyEvent(EVENT_PLAYER source, std::optional target, Vector2 position, std::optional targetPosition); virtual void Output() override; virtual void ColoredEventOutput() override; + std::optional GetTarget() { return this->target; } + Vector2 GetPosition() { return this->position; } + std::optional GetTargetPosition() { return this->targetPosition; } + SystemTypes__Enum GetSystemType() { return this->systemType; } }; class CastVoteEvent : public EventInterface { @@ -142,15 +182,17 @@ class CastVoteEvent : public EventInterface { CastVoteEvent(EVENT_PLAYER source, std::optional target); virtual void Output() override; virtual void ColoredEventOutput() override; + std::optional GetTarget() { return this->target; } }; class CheatDetectedEvent : public EventInterface { private: - CHEAT_ACTION action; + CHEAT_ACTIONS action; public: - CheatDetectedEvent(EVENT_PLAYER source, CHEAT_ACTION action); + CheatDetectedEvent(EVENT_PLAYER source, CHEAT_ACTIONS action); virtual void Output() override; virtual void ColoredEventOutput() override; + CHEAT_ACTIONS GetCheatAction() { return this->action; } }; class DisconnectEvent : public EventInterface { @@ -167,6 +209,7 @@ class ShapeShiftEvent : public EventInterface { ShapeShiftEvent(EVENT_PLAYER source, EVENT_PLAYER target); virtual void Output() override; virtual void ColoredEventOutput() override; + EVENT_PLAYER GetTarget() { return this->target; } }; class ProtectPlayerEvent : public EventInterface { @@ -176,6 +219,7 @@ class ProtectPlayerEvent : public EventInterface { ProtectPlayerEvent(EVENT_PLAYER source, EVENT_PLAYER target); virtual void Output() override; virtual void ColoredEventOutput() override; + EVENT_PLAYER GetTarget() { return this->target; } }; class WalkEvent : public EventInterface { @@ -185,4 +229,5 @@ class WalkEvent : public EventInterface { WalkEvent(EVENT_PLAYER source, Vector2 position); virtual void Output() override; virtual void ColoredEventOutput() override; + Vector2 GetPosition() { return this->position; } }; \ No newline at end of file diff --git a/gui/console.cpp b/gui/console.cpp index 91854f45..50fb786c 100644 --- a/gui/console.cpp +++ b/gui/console.cpp @@ -3,6 +3,7 @@ #include "imgui/imgui.h" #include "gui-helpers.hpp" #include "state.hpp" +#include "logger.h" namespace ConsoleGui { @@ -56,45 +57,54 @@ namespace ConsoleGui ImGui::EndChild(); ImGui::Separator(); ImGui::BeginChild("console#scroll", ImVec2(511, 270), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); - for (int i = State.consoleEvents.size() - 1; i >= 0; i--) { - if (State.consoleEvents[i]->getType() == EVENT_WALK) - continue; + size_t i = State.liveReplayEvents.size() - 1; + if (i >= 0) { + for (std::vector>::reverse_iterator rit = State.liveReplayEvents.rbegin(); rit != State.liveReplayEvents.rend(); ++rit, --i) { + EventInterface* evt = (*rit).get(); + if (evt == NULL) + { + STREAM_ERROR("State.rawEvents[" << i << "] was NULL (rawEvents.size(): " << State.liveReplayEvents.size() << ")"); + continue; + } + if (evt->getType() == EVENT_WALK) + continue; - bool typeFound = false, anyTypeFilterSelected = false; - for (int n = 0; n < ConsoleGui::event_filter.size(); n++) { - if (ConsoleGui::event_filter[n].second - && (EVENT_TYPES)n == State.consoleEvents[i]->getType()) { - typeFound = true; - anyTypeFilterSelected = true; - break; + bool typeFound = false, anyTypeFilterSelected = false; + for (int n = 0; n < ConsoleGui::event_filter.size(); n++) { + if (ConsoleGui::event_filter[n].second + && (EVENT_TYPES)n == evt->getType()) { + typeFound = true; + anyTypeFilterSelected = true; + break; + } + else if (ConsoleGui::event_filter[n].second) + anyTypeFilterSelected = true; } - else if (ConsoleGui::event_filter[n].second) - anyTypeFilterSelected = true; - } - if (!typeFound && anyTypeFilterSelected) - continue; + if (!typeFound && anyTypeFilterSelected) + continue; - bool playerFound = false, anyPlayerFilterSelected = false; - for (auto player : ConsoleGui::player_filter) { - if (player.second - && player.first.has_value() - && player.first.get_PlayerId() == State.consoleEvents[i]->getSource().playerId) - { - playerFound = true; - anyPlayerFilterSelected = true; - break; + bool playerFound = false, anyPlayerFilterSelected = false; + for (auto player : ConsoleGui::player_filter) { + if (player.second + && player.first.has_value() + && player.first.get_PlayerId() == evt->getSource().playerId) + { + playerFound = true; + anyPlayerFilterSelected = true; + break; + } + else if (player.second) // if no player was selected we want to make sure that any filter was set in the first place before we continue + anyPlayerFilterSelected = true; } - else if (player.second) // if no player was selected we want to make sure that any filter was set in the first place before we continue - anyPlayerFilterSelected = true; - } - if (!playerFound && anyPlayerFilterSelected) - continue; + if (!playerFound && anyPlayerFilterSelected) + continue; - State.consoleEvents[i]->ColoredEventOutput(); - ImGui::SameLine(); - State.consoleEvents[i]->Output(); + evt->ColoredEventOutput(); + ImGui::SameLine(); + evt->Output(); + } } ImGui::EndChild(); ImGui::End(); diff --git a/gui/gui-helpers.cpp b/gui/gui-helpers.cpp index 02503ecd..a28d8a64 100644 --- a/gui/gui-helpers.cpp +++ b/gui/gui-helpers.cpp @@ -1,48 +1,55 @@ #include "pch-il2cpp.h" #include "gui-helpers.hpp" #include "keybinds.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif #include "imgui/imgui_internal.h" #include "state.hpp" #include "game.h" +#include "DirectX.h" +#include + +using namespace ImGui; bool CustomListBoxInt(const char* label, int* value, const std::vector list, float width, ImGuiComboFlags flags) { auto comboLabel = "##" + std::string(label); auto leftArrow = "##" + std::string(label) + "Left"; auto rightArrow = "##" + std::string(label) + "Right"; - ImGuiStyle& style = ImGui::GetStyle(); + ImGuiStyle& style = GetStyle(); float spacing = style.ItemInnerSpacing.x; - ImGui::PushItemWidth(width); - const bool response = ImGui::BeginCombo(comboLabel.c_str(), list.at(*value), ImGuiComboFlags_NoArrowButton | flags); + PushItemWidth(width); + const bool response = BeginCombo(comboLabel.c_str(), list.at(*value), ImGuiComboFlags_NoArrowButton | flags); if (response) { for (size_t i = 0; i < list.size(); i++) { bool is_selected = (*value == i); - if (ImGui::Selectable(list.at(i), is_selected)) + if (Selectable(list.at(i), is_selected)) *value = i; if (is_selected) - ImGui::SetItemDefaultFocus(); + SetItemDefaultFocus(); } - ImGui::EndCombo(); + EndCombo(); } - ImGui::PopItemWidth(); - ImGui::SameLine(0, spacing); + PopItemWidth(); + SameLine(0, spacing); - const bool LeftResponse = ImGui::ArrowButton(leftArrow.c_str(), ImGuiDir_Left); + const bool LeftResponse = ArrowButton(leftArrow.c_str(), ImGuiDir_Left); if (LeftResponse) { *value -= 1; if (*value < 0) *value = (list.size() - 1); return LeftResponse; } - ImGui::SameLine(0, spacing); - const bool RightResponse = ImGui::ArrowButton(rightArrow.c_str(), ImGuiDir_Right); + SameLine(0, spacing); + const bool RightResponse = ArrowButton(rightArrow.c_str(), ImGuiDir_Right); if (RightResponse) { *value += 1; if (*value > (int)(list.size() - 1)) *value = 0; return RightResponse; } - ImGui::SameLine(0, spacing); - ImGui::Text(label); + SameLine(0, spacing); + Text(label); return response; } @@ -50,26 +57,28 @@ bool CustomListBoxInt(const char* label, int* value, const std::vector>* list, float width, bool resetButton, ImGuiComboFlags flags) { auto comboLabel = "##" + std::string(label); auto buttonLabel = "Reset##" + std::string(label); - ImGuiStyle& style = ImGui::GetStyle(); + ImGuiStyle& style = GetStyle(); float spacing = style.ItemInnerSpacing.x; - ImGui::PushItemWidth(width); - const bool response = ImGui::BeginCombo(comboLabel.c_str(), label, flags); + PushItemWidth(width); + const bool response = BeginCombo(comboLabel.c_str(), label, flags); if (response) { for (size_t i = 0; i < list->size(); i++) { - if (ImGui::Selectable(list->at(i).first, list->at(i).second)) + if (strcmp(list->at(i).first, "") == 0) // ignore all entries with empty labels so we can create padding + continue; + if (Selectable(list->at(i).first, list->at(i).second)) list->at(i).second ^= 1; if (list->at(i).second) - ImGui::SetItemDefaultFocus(); + SetItemDefaultFocus(); } - ImGui::EndCombo(); + EndCombo(); } - ImGui::PopItemWidth(); + PopItemWidth(); if (resetButton) { - ImGui::SameLine(0, spacing); - const bool resetResponse = ImGui::Button(buttonLabel.c_str()); + SameLine(0, spacing); + const bool resetResponse = Button(buttonLabel.c_str()); if (resetResponse) { for (int i = 0; i < list->size(); i++) list->at(i).second = false; @@ -85,10 +94,10 @@ bool CustomListBoxPlayerSelectionMultiple(const char* label, std::vectorfields._playerName); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - if (ImGui::Selectable(std::string("##" + playerName + "_ConsoleName").c_str(), list->at(playerData->fields.PlayerId).second)) + PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + if (Selectable(std::string("##" + playerName + "_ConsoleName").c_str(), list->at(playerData->fields.PlayerId).second)) { list->at(playerData->fields.PlayerId).second ^= 1; if (list->at(playerData->fields.PlayerId).second @@ -110,13 +119,13 @@ bool CustomListBoxPlayerSelectionMultiple(const char* label, std::vectorat(playerData->fields.PlayerId).second) - ImGui::SetItemDefaultFocus(); - ImGui::SameLine(); - ImGui::ColorButton(std::string("##" + playerName + "_ConsoleColorButton").c_str(), AmongUsColorToImVec4(GetPlayerColor(GetPlayerOutfit(playerData)->fields.ColorId)), ImGuiColorEditFlags_NoBorder | ImGuiColorEditFlags_NoTooltip); - ImGui::SameLine(); - ImGui::PopStyleVar(2); - ImGui::Dummy(ImVec2(0, 0)); - ImGui::SameLine(); + SetItemDefaultFocus(); + SameLine(); + ColorButton(std::string("##" + playerName + "_ConsoleColorButton").c_str(), AmongUsColorToImVec4(GetPlayerColor(GetPlayerOutfit(playerData)->fields.ColorId)), ImGuiColorEditFlags_NoBorder | ImGuiColorEditFlags_NoTooltip); + SameLine(); + PopStyleVar(2); + Dummy(ImVec2(0, 0)); + SameLine(); ImVec4 nameColor = AmongUsColorToImVec4(Palette__TypeInfo->static_fields->White); if (State.RevealRoles) @@ -133,17 +142,17 @@ bool CustomListBoxPlayerSelectionMultiple(const char* label, std::vectorfields.IsDead) nameColor = AmongUsColorToImVec4(Palette__TypeInfo->static_fields->DisabledGrey); - ImGui::TextColored(nameColor, playerName.c_str()); + TextColored(nameColor, playerName.c_str()); } - ImGui::EndCombo(); + EndCombo(); } - ImGui::PopItemWidth(); + PopItemWidth(); if (resetButton) { - ImGui::SameLine(0, spacing); - const bool resetResponse = ImGui::Button(buttonLabel.c_str()); + SameLine(0, spacing); + const bool resetResponse = Button(buttonLabel.c_str()); if (resetResponse) { for (int i = 0; i < list->size(); i++) list->at(i).second = false; @@ -160,20 +169,131 @@ bool SteppedSliderFloat(const char* label, float* v, float v_min, float v_max, f const int stepCount = int((v_max - v_min) / v_step); int v_i = int((*v - v_min) / v_step); - const bool valueChanged = ImGui::SliderInt(label, &v_i, 0, stepCount, text_buf); + const bool valueChanged = SliderInt(label, &v_i, 0, stepCount, text_buf); *v = v_min + float(v_i) * v_step; return valueChanged; } +bool SliderChrono(const char* label, void* p_data, const void* p_min, const void* p_max, std::string format, ImGuiSliderFlags flags) +{ + flags |= ImGuiSliderFlags_NoInput; // disable manual inputs by default + + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + if (ImGui::ImageButton((void*)icons.at(ICON_TYPES::PLAY).iconImage.shaderResourceView, + ImVec2(icons.at(ICON_TYPES::PLAY).iconImage.imageWidth * icons.at(ICON_TYPES::PLAY).scale, + icons.at(ICON_TYPES::PLAY).iconImage.imageHeight * icons.at(ICON_TYPES::PLAY).scale))) + { + State.Replay_IsPlaying = true; + } + + ImGui::SameLine(0.0f, 1.0f); + + if (ImGui::ImageButton((void*)icons.at(ICON_TYPES::PAUSE).iconImage.shaderResourceView, + ImVec2(icons.at(ICON_TYPES::PAUSE).iconImage.imageWidth * icons.at(ICON_TYPES::PAUSE).scale, + icons.at(ICON_TYPES::PAUSE).iconImage.imageHeight * icons.at(ICON_TYPES::PAUSE).scale))) + { + State.Replay_IsPlaying = State.Replay_IsLive = false; + } + + ImGui::SameLine(0.0f, 1.0f); + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb)) + return false; + + // Tabbing or CTRL-clicking on Slider turns it into an input box + const bool hovered = ItemHoverable(frame_bb, id); + bool clicked = false;; + const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; + bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); + if (!temp_input_is_active) + { + const bool focus_requested = temp_input_allowed && FocusableItemRegister(window, id); + clicked = (hovered && g.IO.MouseClicked[0]); + if (focus_requested || clicked || g.NavActivateId == id || g.NavInputId == id) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || g.NavInputId == id)) + { + temp_input_is_active = true; + FocusableItemUnregister(window); + } + } + } + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + + // Slider behavior + ImRect grab_bb; + const bool value_changed = SliderBehavior(frame_bb, id, ImGuiDataType_S64, p_data, p_min, p_max, nullptr, flags | ImGuiSliderFlags_NoRoundToFormat, &grab_bb); + if (value_changed) + MarkItemEdited(id); + + // check if new current timestamp is matching the live timestamp + // this logic makes sure that we can switch between live and replay mode + auto newMatchCurrent = std::chrono::time_point_cast(State.MatchCurrent).time_since_epoch().count(); + auto matchLiveMs = std::chrono::time_point_cast(State.MatchLive).time_since_epoch().count(); + if (newMatchCurrent == matchLiveMs) + { + State.Replay_IsLive = true; + State.Replay_IsPlaying = false; + } + else + { + State.Replay_IsLive = false; + } + + // Render grab + if (grab_bb.Max.x > grab_bb.Min.x) + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + RenderTextClipped(frame_bb.Min, frame_bb.Max, &format[0], &format[format.size()], NULL, ImVec2(0.5f, 0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + ImGui::SameLine(0.0f, 10.0f); + + ImU32 liveColor = (State.Replay_IsLive) ? ImGui::ColorConvertFloat4ToU32(ImVec4(255.0f, 0.f, 0.f, 255.0f)) : ImGui::ColorConvertFloat4ToU32(ImVec4(128.f, 128.f, 128.f, 255.0f)); + const ImVec2 circlePos(window->DC.CursorPos.x, window->DC.CursorPos.y + 9.5f); + window->DrawList->AddCircleFilled(circlePos, 5.0f, liveColor); + ImGui::SameLine(0.0f, 18.f); + ImGui::Text("Live"); + + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags); + + return value_changed; +} + bool HotKey(uint8_t& key) { - ImGui::Text("[ %s ]", KeyBinds::ToString(key)); + Text("[ %s ]", KeyBinds::ToString(key)); - if (!ImGui::IsItemHovered()) + if (!IsItemHovered()) return false; - ImGui::SetTooltip("Press any key to change the keybind, ESC to reset"); + SetTooltip("Press any key to change the keybind, ESC to reset"); for (uint8_t vKey : KeyBinds::GetValidKeys()) { if (KeyBinds::IsKeyDown(vKey)) { key = (vKey != VK_ESCAPE ? vKey : 0x00); @@ -182,4 +302,90 @@ bool HotKey(uint8_t& key) } return false; +} + +void drawPlayerDot(PlayerControl* player, ImVec2 winPos, ImU32 color, ImU32 statusColor) +{ + ImDrawList* drawList = GetWindowDrawList(); + + Vector2 playerPos = app::PlayerControl_GetTruePosition(player, NULL); + + float xOffset = maps[State.mapType].x_offset; + float yOffset = maps[State.mapType].y_offset; + + if (State.mapType == Settings::MapType::Ship && State.FlipSkeld) { + xOffset -= 50; + } + + float radX = xOffset + (playerPos.x * maps[State.mapType].scale) + winPos.x; + float radY = yOffset - (playerPos.y * maps[State.mapType].scale) + winPos.y; + + drawList->AddCircleFilled(ImVec2(radX, radY), 4.5F, color); + drawList->AddCircle(ImVec2(radX, radY), 4.5F + 0.5F, statusColor, 0, 2.0F); +} + +void drawPlayerIcon(PlayerControl* player, ImVec2 winPos, ImU32 color) +{ + ImDrawList* drawList = GetWindowDrawList(); + + Vector2 playerPos = app::PlayerControl_GetTruePosition(player, NULL); + + float xOffset = maps[State.mapType].x_offset; + float yOffset = maps[State.mapType].y_offset; + + if (State.mapType == Settings::MapType::Ship && State.FlipSkeld) { + xOffset -= 50; + } + + IconTexture icon = icons.at(ICON_TYPES::PLAYER); + float radX = xOffset + (playerPos.x - (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[State.mapType].scale + winPos.x; + float radY = yOffset - (playerPos.y - (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[State.mapType].scale + winPos.y; + float radXMax = xOffset + (playerPos.x + (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[State.mapType].scale + winPos.x; + float radYMax = yOffset - (playerPos.y + (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[State.mapType].scale + winPos.y; + + drawList->AddImage((void*)icon.iconImage.shaderResourceView, ImVec2(radX, radY), ImVec2(radXMax, radYMax), ImVec2(0.0f, 1.0f), ImVec2(1.0f, 0.0f), color); + + if (GetPlayerData(player)->fields.IsDead) + drawList->AddImage((void*)icons.at(ICON_TYPES::CROSS).iconImage.shaderResourceView, ImVec2(radX, radY), ImVec2(radXMax, radYMax), ImVec2(0.0f, 1.0f), ImVec2(1.0f, 0.0f)); +} + +void drawDeadPlayerDot(DeadBody* deadBody, ImVec2 winPos, ImU32 color) +{ + ImDrawList* drawList = GetWindowDrawList(); + + Vector2 bodyPos = app::DeadBody_get_TruePosition(deadBody, NULL); + + float xOffset = maps[State.mapType].x_offset; + float yOffset = maps[State.mapType].y_offset; + + if (State.mapType == Settings::MapType::Ship && State.FlipSkeld) { + xOffset -= 50; + } + + float radX = xOffset + (bodyPos.x * maps[State.mapType].scale) + winPos.x; + float radY = yOffset - (bodyPos.y * maps[State.mapType].scale) + winPos.y; + + drawList->AddText(GetFont(), 16, ImVec2(radX - 5.F, radY - 6.75F), color, "X"); +} + +void drawDeadPlayerIcon(DeadBody* deadBody, ImVec2 winPos, ImU32 color) +{ + ImDrawList* drawList = GetWindowDrawList(); + + Vector2 bodyPos = app::DeadBody_get_TruePosition(deadBody, NULL); + + float xOffset = maps[State.mapType].x_offset; + float yOffset = maps[State.mapType].y_offset; + + if (State.mapType == Settings::MapType::Ship && State.FlipSkeld) { + xOffset -= 50; + } + + IconTexture icon = icons.at(ICON_TYPES::DEAD); + float radX = xOffset + (bodyPos.x - (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[State.mapType].scale + winPos.x; + float radY = yOffset - (bodyPos.y - (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[State.mapType].scale + winPos.y; + float radXMax = xOffset + (bodyPos.x + (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[State.mapType].scale + winPos.x; + float radYMax = yOffset - (bodyPos.y + (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[State.mapType].scale + winPos.y; + + drawList->AddImage((void*)icon.iconImage.shaderResourceView, ImVec2(radX, radY), ImVec2(radXMax, radYMax), ImVec2(0.0f, 1.0f), ImVec2(1.0f, 0.0f), color); } \ No newline at end of file diff --git a/gui/gui-helpers.hpp b/gui/gui-helpers.hpp index 530e3e52..18c64548 100644 --- a/gui/gui-helpers.hpp +++ b/gui/gui-helpers.hpp @@ -7,4 +7,9 @@ bool CustomListBoxInt(const char* label, int* value, const std::vector>* list, float width, bool resetButton = true, ImGuiComboFlags flags = ImGuiComboFlags_None); bool CustomListBoxPlayerSelectionMultiple(const char* label, std::vector>* list, float width, bool resetButton = true, ImGuiComboFlags flags = ImGuiComboFlags_None); bool SteppedSliderFloat(const char* label, float* v, float v_min, float v_max, float v_step, const char* format, ImGuiSliderFlags flags); -bool HotKey(uint8_t& key); \ No newline at end of file +bool SliderChrono(const char* label, void* p_data, const void* p_min, const void* p_max, std::string format, ImGuiSliderFlags flags = ImGuiSliderFlags_None); +bool HotKey(uint8_t& key); +void drawPlayerDot(PlayerControl* player, ImVec2 winPos, ImU32 color, ImU32 statusColor); +void drawPlayerIcon(PlayerControl* player, ImVec2 winPos, ImU32 color); +void drawDeadPlayerDot(DeadBody* deadBody, ImVec2 winPos, ImU32 color); +void drawDeadPlayerIcon(DeadBody* deadBody, ImVec2 winPos, ImU32 color); \ No newline at end of file diff --git a/gui/menu.cpp b/gui/menu.cpp index e8b4a3da..5df21c16 100644 --- a/gui/menu.cpp +++ b/gui/menu.cpp @@ -37,7 +37,7 @@ namespace Menu { GameTab::Render(); SelfTab::Render(); RadarTab::Render(); - //ReplayTab::Render(); + ReplayTab::Render(); EspTab::Render(); PlayersTab::Render(); TasksTab::Render(); diff --git a/gui/radar.cpp b/gui/radar.cpp index d96253a1..31760e0f 100644 --- a/gui/radar.cpp +++ b/gui/radar.cpp @@ -3,6 +3,7 @@ #include "DirectX.h" #include "utility.h" #include "state.hpp" +#include "gui-helpers.hpp" namespace Radar { ImU32 GetRadarPlayerColor(GameData_PlayerInfo* playerData) { @@ -12,7 +13,9 @@ namespace Radar { ImU32 GetRadarPlayerColorStatus(GameData_PlayerInfo* playerData) { if (playerData->fields.IsDead) return ImGui::ColorConvertFloat4ToU32(AmongUsColorToImVec4(app::Palette__TypeInfo->static_fields->HalfWhite)); - else if (State.RevealRoles && playerData->fields.Role != nullptr && playerData->fields.Role->fields.StringName != StringNames__Enum::Crewmate) + else if (State.RevealRoles + && playerData->fields.Role != nullptr + && playerData->fields.Role->fields.TeamType == RoleTeamTypes__Enum::Impostor) return ImGui::ColorConvertFloat4ToU32(AmongUsColorToImVec4(GetRoleColor(playerData->fields.Role))); else return ImGui::ColorConvertFloat4ToU32(ImVec4(0, 0, 0, 0)); @@ -62,20 +65,19 @@ namespace Radar { if (!init) Radar::Init(); - int MapType = State.mapType; - ImGui::SetNextWindowSize(ImVec2((float)maps[MapType].mapImage.imageWidth * 0.5F + 10, (float)maps[MapType].mapImage.imageHeight * 0.5F + 10), ImGuiCond_None); + Settings::MapType MapType = State.mapType; + ImGui::SetNextWindowSize(ImVec2((float)maps[MapType].mapImage.imageWidth * 0.5f + 10.f, (float)maps[MapType].mapImage.imageHeight * 0.5f + 10.f), ImGuiCond_None); if(State.LockRadar) ImGui::Begin("Radar", &State.ShowRadar, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); else ImGui::Begin("Radar", &State.ShowRadar, ImGuiWindowFlags_NoDecoration); - ImDrawList* drawList = ImGui::GetWindowDrawList(); - ImVec2 winpos = ImGui::GetWindowPos(); ImGui::Image((void*)maps[MapType].mapImage.shaderResourceView, ImVec2((float)maps[MapType].mapImage.imageWidth * 0.5F, (float)maps[MapType].mapImage.imageHeight * 0.5F), + ImVec2(0.0f, 0.0f), (State.FlipSkeld && MapType == 0) ? ImVec2(1.0f, 0.0f) : ImVec2(0.0f, 0.0f), (State.FlipSkeld && MapType == 0) ? ImVec2(0.0f, 1.0f) : ImVec2(1.0f, 1.0f), State.SelectedColor); @@ -86,32 +88,20 @@ namespace Radar { if (!playerData || (!State.ShowRadar_Ghosts && playerData->fields.IsDead)) continue; - Vector2 playerPos = app::PlayerControl_GetTruePosition(player, NULL); - - float xOffset = maps[MapType].x_offset; - float yOffset = maps[MapType].y_offset; - - if (MapType == 0 && State.FlipSkeld) { - xOffset -= 50; - } - - float radX = xOffset + (playerPos.x * maps[MapType].scale) + winpos.x; - float radY = yOffset - (playerPos.y * maps[MapType].scale) + winpos.y; - - drawList->AddCircleFilled(ImVec2(radX, radY), 4.5F, GetRadarPlayerColor(playerData)); - drawList->AddCircle(ImVec2(radX, radY), 4.5F + 0.5F, GetRadarPlayerColorStatus(playerData), 0, 2.0F); + if (State.RadarDrawIcons) + drawPlayerIcon(player, winpos, GetRadarPlayerColor(playerData)); + else + drawPlayerDot(player, winpos, GetRadarPlayerColor(playerData), GetRadarPlayerColorStatus(playerData)); } if (State.ShowRadar_DeadBodies) { for (auto deadBody : GetAllDeadBodies()) { auto playerData = GetPlayerDataById(deadBody->fields.ParentId); - Vector2 bodyPos = app::DeadBody_get_TruePosition(deadBody, NULL); - - float radX = maps[MapType].x_offset + (bodyPos.x * maps[MapType].scale) + winpos.x; - float radY = maps[MapType].y_offset - (bodyPos.y * maps[MapType].scale) + winpos.y; - - drawList->AddText(ImGui::GetFont(), 16, ImVec2(radX - 5.F, radY - 6.75F), GetRadarPlayerColor(playerData), "X"); + if (State.RadarDrawIcons) + drawDeadPlayerIcon(deadBody, winpos, GetRadarPlayerColor(playerData)); + else + drawDeadPlayerDot(deadBody, winpos, GetRadarPlayerColor(playerData)); } } diff --git a/gui/replay.cpp b/gui/replay.cpp index a42b7e63..39b9a1eb 100644 --- a/gui/replay.cpp +++ b/gui/replay.cpp @@ -3,9 +3,17 @@ #include "DirectX.h" #include "state.hpp" #include "gui-helpers.hpp" +#include "profiler.h" +#include "logger.h" +#include "utility.h" +#include +#include + namespace Replay { + std::mutex replayEventMutex; + // TODO: improve this by building it dynamically based on the EVENT_TYPES enum std::vector> event_filter = { @@ -14,16 +22,20 @@ namespace Replay {"Task", false}, {"Report", false}, {"Meeting", false}, - {"Vote", false}, - {"Cheat", false}, - {"Disconnect", false}, - {"Shapeshift", false}, - {"Protect", false}, + {"", false}, + {"", false}, + {"", false}, + {"", false}, + {"", false}, {"Walk", false} }; std::vector> player_filter; + ImU32 GetReplayPlayerColor(uint8_t colorId) { + return ImGui::ColorConvertFloat4ToU32(AmongUsColorToImVec4(GetPlayerColor(colorId))); + } + void SquareConstraint(ImGuiSizeCallbackData* data) { data->DesiredSize = ImVec2(data->DesiredSize.x, data->DesiredSize.y); @@ -45,17 +57,403 @@ namespace Replay } } + void Reset() + { + for (auto& e : State.liveReplayEvents) + e.reset(); + State.liveReplayEvents.clear(); + for (auto& pair : State.replayWalkPolylineByPlayer) + { + pair.second.playerId = 0; + pair.second.colorId = 0; + pair.second.pendingPoints.clear(); + pair.second.pendingTimeStamps.clear(); + pair.second.simplifiedPoints.clear(); + pair.second.simplifiedTimeStamps.clear(); + } + + for (int plyIdx = 0; plyIdx < MAX_PLAYERS; plyIdx++) + { + State.lastWalkEventPosPerPlayer[plyIdx] = ImVec2(0.f, 0.f); + } + + // Set this to true as the default value + // Everytime we start a new match it will actually be in live mode + State.Replay_IsLive = true; + + State.Replay_IsPlaying = false; + } + + void RenderPolyline(ImDrawList* drawList, float cursorPosX, float cursorPosY, + std::vector& points, std::vector& timeStamps, uint8_t colorId, + bool isUsingMinTimeFilter, std::chrono::system_clock::time_point& minTimeFilter, bool isUsingMaxTimeFilter, std::chrono::system_clock::time_point& maxTimeFilter) + { + if ((isUsingMinTimeFilter == true) && (isUsingMaxTimeFilter == true) + && (minTimeFilter >= maxTimeFilter)) + { + STREAM_ERROR("Min time filter is greater than max time filter (min: " << std::format("{:%OH:%OM:%OS}", minTimeFilter) << " max: " << std::format("{:%OH:%OM:%OS}", maxTimeFilter) << ")"); + return; + } + // this is annoying, but we have to transform the points, render, then untransform + // if we store the transformed points then moving the replay window will cause everything to break.. + for (auto& point : points) + { + point.x = getMapXOffsetSkeld(point.x) + cursorPosX; + point.y += cursorPosY; + } + + // earliestTimeIndex will be the very first event to be shown, lastTimeIndex will be the very last even to be shown + int earliestTimeIndex = 0, lastTimeIndex = 0; + bool collectionHasElementsToFilterMin = false, collectionHasElementsToFilterMax = false; + if ((isUsingMinTimeFilter == true) || (isUsingMaxTimeFilter)) + { + // now we figure out the last index that matches the minTimeFilter + // then we'll do some quik pointer mafs to pass to the AddPolyline call + for (int index = 0; index < timeStamps.size() - 1; index++) + { + const std::chrono::system_clock::time_point& timestamp = timeStamps.at(index); + if ((timestamp > minTimeFilter) && (collectionHasElementsToFilterMin == false)) + { + // the first element that *matches* the minTimeFilter is where we begin drawing from + earliestTimeIndex = index; + collectionHasElementsToFilterMin = true; + } + if ((timestamp > maxTimeFilter) && (collectionHasElementsToFilterMax == false)) + { + // the first element that *exceeds* the maxTimeFilter is where we stop drawing + lastTimeIndex = index; + collectionHasElementsToFilterMax = true; + } + } + + uintptr_t startPtr = 0, endPtr = (points.size() - 1) * sizeof(ImVec2); + if (collectionHasElementsToFilterMin == true) + { + // some events occurred before the specified time filter + // so we want to draw only a portion of the total collection + // this portion starts from the index of the last matching event and should continue to the end of the collection + // since we're modifying the *pointer*, we have to multiply by the size of each element in the collection. + startPtr = earliestTimeIndex * sizeof(ImVec2); + } + if (collectionHasElementsToFilterMax == true) + { + endPtr = lastTimeIndex * sizeof(ImVec2); + } + int numPoints = (endPtr - startPtr) / sizeof(ImVec2); + drawList->AddPolyline((ImVec2*)((uintptr_t)points.data() + startPtr), numPoints, GetReplayPlayerColor(colorId), false, 1.f); + } + else + { + // we're not using any time filter, so just draw the polyline normally. + drawList->AddPolyline(points.data(), points.size(), GetReplayPlayerColor(colorId), false, 1.f); + } + + // untransform the points before returning + for (auto& point : points) + { + float xPoint = getMapXOffsetSkeld(point.x); + if (xPoint != point.x) + xPoint += 100.f; + + point.x = xPoint - cursorPosX; + point.y -= cursorPosY; + } + } + + void RenderWalkPaths(ImDrawList* drawList, float cursorPosX, float cursorPosY, int MapType, bool isUsingEventFilter, bool isUsingPlayerFilter, + bool isUsingMinTimeFilter, std::chrono::system_clock::time_point& minTimeFilter, bool isUsingMaxTimeFilter, std::chrono::system_clock::time_point& maxTimeFilter) + { + Profiler::BeginSample("ReplayPolyline"); + if ((isUsingMinTimeFilter == true) && (isUsingMaxTimeFilter == true) + && (minTimeFilter >= maxTimeFilter)) + { + STREAM_ERROR("Min time filter is greater than max time filter (min: " << std::format("{:%OH:%OM:%OS}", minTimeFilter) << " max: " << std::format("{:%OH:%OM:%OS}", maxTimeFilter) << ")"); + return; + } + for (auto& playerPolylinePair : State.replayWalkPolylineByPlayer) + { + // first we check if the player has enough points pending simplification + // we want to do the simplification regardless of filters so that if the filters change + // and we start showing the walk path for that player we don't have to simplify tens of thousands of points on that first frame + Replay::WalkEvent_LineData& plrLineData = playerPolylinePair.second; + size_t numPendingPoints = plrLineData.pendingPoints.size(); + if (numPendingPoints >= 100) + { + DoPolylineSimplification(plrLineData.pendingPoints, plrLineData.pendingTimeStamps, plrLineData.simplifiedPoints, plrLineData.simplifiedTimeStamps, 50.f, true); + } + + // now the actual rendering, which should be filtered + // player filter + if ((isUsingPlayerFilter == true) && + ((playerPolylinePair.first < 0) || (playerPolylinePair.first > Replay::player_filter.size() - 1) || + (Replay::player_filter[playerPolylinePair.first].second == false) || + (Replay::player_filter[playerPolylinePair.first].first.has_value() == false))) + continue; + + // event filter + if ((isUsingEventFilter == true) && (Replay::event_filter[(int)EVENT_TYPES::EVENT_WALK].second == false)) + continue; + + if (plrLineData.simplifiedPoints.size() > 0) + RenderPolyline(drawList, cursorPosX, cursorPosY, plrLineData.simplifiedPoints, plrLineData.simplifiedTimeStamps, plrLineData.colorId, isUsingMinTimeFilter, minTimeFilter, isUsingMaxTimeFilter, maxTimeFilter); + // pendingPoints picks up where simplifiedPoints leaves off. there should only ever be 100 or less pendingPoints at any one time, so this is fine. + if (plrLineData.pendingPoints.size() > 0) + RenderPolyline(drawList, cursorPosX, cursorPosY, plrLineData.pendingPoints, plrLineData.pendingTimeStamps, plrLineData.colorId, isUsingMinTimeFilter, minTimeFilter, isUsingMaxTimeFilter, maxTimeFilter); + } + Profiler::EndSample("ReplayPolyline"); + } + + void RenderPlayerIcons(ImDrawList* drawList, float cursorPosX, float cursorPosY, int MapType, bool isUsingEventFilter, bool isUsingPlayerFilter, + bool isUsingMinTimeFilter, std::chrono::system_clock::time_point& minTimeFilter, bool isUsingMaxTimeFilter, std::chrono::system_clock::time_point& maxTimeFilter) + { + Profiler::BeginSample("ReplayPlayerIcons"); + if ((isUsingMinTimeFilter == true) && (isUsingMaxTimeFilter == true) + && (minTimeFilter >= maxTimeFilter)) + { + STREAM_ERROR("Min time filter is greater than max time filter (min: " << std::format("{:%OH:%OM:%OS}", minTimeFilter) << " max: " << std::format("{:%OH:%OM:%OS}", maxTimeFilter) << ")"); + return; + } + // event filter + if ((isUsingEventFilter == true) && (Replay::event_filter[(int)EVENT_TYPES::EVENT_WALK].second == false)) + return; + + for (auto& playerPolylinePair : State.replayWalkPolylineByPlayer) + { + const int& plrIdx = playerPolylinePair.first; + const Replay::WalkEvent_LineData& plrLineData = playerPolylinePair.second; + + // player filter + if ((isUsingPlayerFilter == true) && + ((plrIdx < 0) || (plrIdx > Replay::player_filter.size() - 1) || + (Replay::player_filter[plrIdx].second == false) || + (Replay::player_filter[plrIdx].first.has_value() == false))) + continue; + + // we get the player's position from the line data which is constructed from WalkEvents + // pendingPoints will have the absolute freshest data, while simplifiedPoints will be behind by ~100 points or so (depends on how often we run the simplification) + // if the player has not moved at all then we'll have zero line data (since there are zero WalkEvents) and we continue to the next player + bool foundMatchingPlayerPos = false; + ImVec2 playerPos; + if (isUsingMaxTimeFilter == true) + { + // now we have to loop through and find the last position that matches the time filters + int lastTimeIndex = plrLineData.pendingTimeStamps.size() - 1; + for (std::vector::const_reverse_iterator riter = plrLineData.pendingTimeStamps.rbegin(); riter != plrLineData.pendingTimeStamps.rend(); riter++, lastTimeIndex--) + { + const std::chrono::system_clock::time_point& timestamp = *riter; + if (timestamp < maxTimeFilter) + { + playerPos = plrLineData.pendingPoints[lastTimeIndex]; + if ((isUsingMinTimeFilter == true) && (timestamp < minTimeFilter)) + { + STREAM_DEBUG("(not critical) Found a point matching maxTimeFilter, but does not match minTimeFilter. Add check that min < max once free time available."); + } + foundMatchingPlayerPos = true; + break; + } + } + if (foundMatchingPlayerPos == false) + { + // if we couldn't find a match using pendingPoints then we must try again using simplifiedPoints. + lastTimeIndex = plrLineData.simplifiedTimeStamps.size() - 1; + for (std::vector::const_reverse_iterator riter = plrLineData.simplifiedTimeStamps.rbegin(); riter != plrLineData.simplifiedTimeStamps.rend(); riter++, lastTimeIndex--) + { + const std::chrono::system_clock::time_point& timestamp = *riter; + if (timestamp < maxTimeFilter) + { + playerPos = plrLineData.simplifiedPoints[lastTimeIndex]; + if ((isUsingMinTimeFilter == true) && (timestamp < minTimeFilter)) + { + STREAM_DEBUG("(not critical) Found a point matching maxTimeFilter, but does not match minTimeFilter. Add check that min < max once free time available."); + } + foundMatchingPlayerPos = true; + break; + } + } + } + } + else + { + // if we're not using a max time filter we can just use the absolute latest position + // this holds true even if we're using a min time filter + if (plrLineData.pendingPoints.size() > 0) + playerPos = plrLineData.pendingPoints.back(); + else if (plrLineData.simplifiedPoints.size() > 0) + playerPos = plrLineData.simplifiedPoints.back(); + foundMatchingPlayerPos = true; + } + if (foundMatchingPlayerPos == false) + { + STREAM_DEBUG("Could not find replay position for player#" << plrIdx << ". Time filter was likely too strict or player hasn't moved yet."); + continue; + } + + IconTexture icon = icons.at(ICON_TYPES::PLAYER); + // the latestPos variable is already pre-transformed for line rendering, so we have to un-transform and then re-transform for proper icon positioning + // existing transformation: + // latestPos.x = maps[State.mapType].x_offset + (position.x * maps[State.mapType].scale); + // void*'s original transformation for icon positioning: + // float player_mapX = maps[MapType].x_offset + (position.x - (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosX; + // i'm not mathematically inclined, so i don't really know what i'm doing... + // but this is what i got for transforming from the existing to void*'s original: + float halfImageWidth = (icon.iconImage.imageWidth * icon.scale * 0.5f) * maps[MapType].scale, halfImageHeight = (icon.iconImage.imageHeight * icon.scale * 0.5f) * maps[MapType].scale; + float player_mapX = (playerPos.x - halfImageWidth) + cursorPosX; + float player_mapY = (playerPos.y - halfImageHeight) + cursorPosY; + float player_mapXMax = (playerPos.x + halfImageWidth) + cursorPosX; + float player_mapYMax = (playerPos.y + halfImageHeight) + cursorPosY; + + drawList->AddImage((void*)icon.iconImage.shaderResourceView, + ImVec2(getMapXOffsetSkeld(player_mapX), player_mapY), + ImVec2(getMapXOffsetSkeld(player_mapXMax), player_mapYMax), + ImVec2(0.0f, 0.0f), + ImVec2(1.0f, 1.0f), + GetReplayPlayerColor(plrLineData.colorId)); + + app::GameData_PlayerInfo* plrInfo = GetPlayerDataById(plrLineData.playerId); + if ((plrInfo != NULL) && + ((plrInfo->fields.IsDead) || + ((plrInfo->fields.Role != NULL) && + (plrInfo->fields.Role->fields.Role == RoleTypes__Enum::GuardianAngel)))) + drawList->AddImage((void*)icons.at(ICON_TYPES::CROSS).iconImage.shaderResourceView, + ImVec2(getMapXOffsetSkeld(player_mapX), player_mapY), + ImVec2(getMapXOffsetSkeld(player_mapXMax), player_mapYMax), + ImVec2(0.0f, 0.0f), + ImVec2(1.0f, 1.0f)); + } + Profiler::EndSample("ReplayPlayerIcons"); + } + + void RenderEventIcons(ImDrawList* drawList, float cursorPosX, float cursorPosY, int MapType, bool isUsingEventFilter, bool isUsingPlayerFilter, + bool isUsingMinTimeFilter, std::chrono::system_clock::time_point& minTimeFilter, bool isUsingMaxTimeFilter, std::chrono::system_clock::time_point& maxTimeFilter) + { + Profiler::BeginSample("ReplayEventIcons"); + if ((isUsingMinTimeFilter == true) && (isUsingMaxTimeFilter == true) + && (minTimeFilter >= maxTimeFilter)) + { + STREAM_ERROR("Min time filter is greater than max time filter (min: " << std::format("{:%OH:%OM:%OS}", minTimeFilter) << " max: " << std::format("{:%OH:%OM:%OS}", maxTimeFilter) << ")"); + return; + } + + for (std::vector>::iterator it = State.liveReplayEvents.begin(); it != State.liveReplayEvents.end(); ++it) + { + EventInterface* curEvent = (*it).get(); + EVENT_TYPES evtType = curEvent->getType(); + std::chrono::system_clock::time_point evtTime = curEvent->GetTimeStamp(); + EVENT_PLAYER evtPlayerSource = curEvent->getSource(); + + // filters + if ((isUsingMaxTimeFilter == true) && (evtTime > maxTimeFilter)) + continue; + if ((isUsingMinTimeFilter == true) && (evtTime < minTimeFilter)) + continue; + if ((isUsingEventFilter == true) && (Replay::event_filter[(int)evtType].second == false)) + continue; + if ((isUsingPlayerFilter == true) && + ((evtPlayerSource.playerId < 0) || (evtPlayerSource.playerId > Replay::player_filter.size() - 1) || + (Replay::player_filter[evtPlayerSource.playerId].second == false) || + (Replay::player_filter[evtPlayerSource.playerId].first.has_value() == false))) + continue; + + // processing + if (evtType == EVENT_TYPES::EVENT_KILL) + { + auto kill_event = dynamic_cast(curEvent); + auto position = kill_event->GetTargetPosition(); + IconTexture icon = icons.at(ICON_TYPES::KILL); + float mapX = maps[MapType].x_offset + (position.x - (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosX; + float mapY = maps[MapType].y_offset - (position.y - (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosY; + float mapXMax = maps[MapType].x_offset + (position.x + (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosX; + float mapYMax = maps[MapType].y_offset - (position.y + (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosY; + + drawList->AddImage((void*)icon.iconImage.shaderResourceView, + ImVec2(getMapXOffsetSkeld(mapX), mapY), + ImVec2(getMapXOffsetSkeld(mapXMax), mapYMax), + ImVec2(0.0f, 1.0f), + ImVec2(1.0f, 0.0f)); + } + else if (evtType == EVENT_TYPES::EVENT_VENT) + { + auto vent_event = dynamic_cast(curEvent); + auto position = vent_event->GetPosition(); + ICON_TYPES iconType; + + switch (vent_event->GetEventActionEnum()) + { + case VENT_ACTIONS::VENT_ENTER: + iconType = ICON_TYPES::VENT_IN; + break; + + case VENT_ACTIONS::VENT_EXIT: + iconType = ICON_TYPES::VENT_OUT; + break; + + default: + iconType = ICON_TYPES::VENT_IN; + break; + } + + IconTexture icon = icons.at(iconType); + float mapX = maps[MapType].x_offset + (position.x - (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosX; + float mapY = maps[MapType].y_offset - (position.y - (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosY; + float mapXMax = maps[MapType].x_offset + (position.x + (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosX; + float mapYMax = maps[MapType].y_offset - (position.y + (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosY; + + drawList->AddImage((void*)icon.iconImage.shaderResourceView, + ImVec2(getMapXOffsetSkeld(mapX), mapY), + ImVec2(getMapXOffsetSkeld(mapXMax), mapYMax), + ImVec2(0.0f, 1.0f), + ImVec2(1.0f, 0.0f)); + } + else if (evtType == EVENT_TYPES::EVENT_TASK) + { + auto task_event = dynamic_cast(curEvent); + auto position = task_event->GetPosition(); + IconTexture icon = icons.at(ICON_TYPES::TASK); + float mapX = maps[MapType].x_offset + (position.x - (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosX; + float mapY = maps[MapType].y_offset - (position.y - (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosY; + float mapXMax = maps[MapType].x_offset + (position.x + (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosX; + float mapYMax = maps[MapType].y_offset - (position.y + (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosY; + + drawList->AddImage((void*)icon.iconImage.shaderResourceView, + ImVec2(getMapXOffsetSkeld(mapX), mapY), + ImVec2(getMapXOffsetSkeld(mapXMax), mapYMax), + ImVec2(0.0f, 1.0f), + ImVec2(1.0f, 0.0f)); + } + else if (evtType == EVENT_TYPES::EVENT_REPORT || evtType == EVENT_TYPES::EVENT_MEETING) + { + auto report_event = dynamic_cast(curEvent); + auto position = report_event->GetPosition(); + auto targetPos = report_event->GetTargetPosition(); + if (targetPos.has_value()) + position = targetPos.value(); + IconTexture icon = icons.at(ICON_TYPES::REPORT); + float mapX = maps[MapType].x_offset + (position.x - (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosX; + float mapY = maps[MapType].y_offset - (position.y - (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosY; + float mapXMax = maps[MapType].x_offset + (position.x + (icon.iconImage.imageWidth * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosX; + float mapYMax = maps[MapType].y_offset - (position.y + (icon.iconImage.imageHeight * icon.scale * 0.5f)) * maps[MapType].scale + cursorPosY; + + drawList->AddImage((void*)icon.iconImage.shaderResourceView, + ImVec2(getMapXOffsetSkeld(mapX), mapY), + ImVec2(getMapXOffsetSkeld(mapXMax), mapYMax), + ImVec2(0.0f, 1.0f), + ImVec2(1.0f, 0.0f)); + } + } + Profiler::EndSample("ReplayEventIcons"); + } void Render() { + Profiler::BeginSample("ReplayRender"); Replay::Init(); int MapType = State.mapType; - ImGui::SetNextWindowSize(ImVec2(560, 400), ImGuiCond_None); + ImGui::SetNextWindowSize(ImVec2((maps[MapType].mapImage.imageWidth * 0.5f) + 50.0f, (maps[MapType].mapImage.imageHeight * 0.5f) + 90.f), ImGuiCond_None); ImGui::Begin("Replay", &State.ShowReplay, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse); - ImGui::BeginChild("console#filter", ImVec2(560, 20), true); + ImGui::BeginChild("replay#filter", ImVec2(0, 20), true); ImGui::Text("Event Filter: "); ImGui::SameLine(); CustomListBoxIntMultiple("Event Types", &Replay::event_filter, 100.f); @@ -68,36 +466,78 @@ namespace Replay ImGui::EndChild(); ImGui::Separator(); - // TODO: Size it for map and calculate center of the parent window for proper map placement - ImGui::BeginChild("ReplayMap"); - + ImGui::BeginChild("replay#map", ImVec2((maps[MapType].mapImage.imageWidth * 0.5f) + 50.f, (maps[MapType].mapImage.imageHeight * 0.5f) + 15.f)); ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 winSize = ImGui::GetWindowSize(); + ImVec2 winPos = ImGui::GetWindowPos(); - ImGui::Image((void*)maps[MapType].mapImage.shaderResourceView, - ImVec2((float)maps[MapType].mapImage.imageWidth * 0.5F, (float)maps[MapType].mapImage.imageHeight * 0.5F), + // calculate proper cursorPosition for centerered rendering + float cursorPosX = winPos.x + 15.f; + float cursorPosY = winPos.y + (winSize.y * 0.15f) - ((float)(maps[MapType].mapImage.imageHeight * 0.15f) * 0.5f); + + // TODO: Center image in childwindow and calculate new cursorPos + drawList->AddImage((void*)maps[MapType].mapImage.shaderResourceView, + ImVec2(cursorPosX + 5.0f, cursorPosY + 5.0f), + ImVec2(cursorPosX + 5.0f + ((float)maps[MapType].mapImage.imageWidth * 0.5f), cursorPosY + 5.0f + ((float)maps[MapType].mapImage.imageHeight * 0.5f)), (State.FlipSkeld && MapType == 0) ? ImVec2(1.0f, 0.0f) : ImVec2(0.0f, 0.0f), (State.FlipSkeld && MapType == 0) ? ImVec2(0.0f, 1.0f) : ImVec2(1.0f, 1.0f), - State.SelectedReplayMapColor); + ImGui::GetColorU32(State.SelectedReplayMapColor)); - // for each player - for (int n = 0; n < MAX_PLAYERS; n++) + // pre-processing of filters + bool isUsingEventFilter = false; + for (size_t evtFiltIdx = 0; evtFiltIdx < Replay::event_filter.size(); evtFiltIdx++) { - // include player filter - - // for each event - for (int x = 0; x < EVENT_TYPES_SIZE; x++) + if (Replay::event_filter[evtFiltIdx].second == true) { - // work with event vector here - // include event filter here - // visualize each event here (maybe create own class for drawing map related data ?) + isUsingEventFilter = true; + break; + } + } + bool isUsingPlayerFilter = false; + for (size_t plyFiltIdx = 0; plyFiltIdx < Replay::player_filter.size(); plyFiltIdx++) + { + if ((Replay::player_filter[plyFiltIdx].second == true) && (Replay::player_filter[plyFiltIdx].first.has_value() == true)) + { + isUsingPlayerFilter = true; + break; } } + std::chrono::system_clock::time_point minTimeFilter = State.MatchStart; + if (State.Replay_ShowOnlyLastSeconds) + { + std::chrono::seconds seconds(State.Replay_LastSecondsValue); + minTimeFilter = State.MatchCurrent - seconds; + } + + std::lock_guard replayLock(Replay::replayEventMutex); + RenderWalkPaths(drawList, cursorPosX, cursorPosY, MapType, isUsingEventFilter, isUsingPlayerFilter, State.Replay_ShowOnlyLastSeconds, minTimeFilter, true, State.MatchCurrent); + RenderPlayerIcons(drawList, cursorPosX, cursorPosY, MapType, isUsingEventFilter, isUsingPlayerFilter, State.Replay_ShowOnlyLastSeconds, minTimeFilter, true, State.MatchCurrent); + RenderEventIcons(drawList, cursorPosX, cursorPosY, MapType, isUsingEventFilter, isUsingPlayerFilter, State.Replay_ShowOnlyLastSeconds, minTimeFilter, true, State.MatchCurrent); + ImGui::EndChild(); - // new child (control) - // slider based on chronos timestamp from beginning of round until now (live) + ImGui::Separator(); + ImGui::Dummy(ImVec2(1.0f, 5.0f)); + + ImGui::BeginChild("replay#control"); + + std::string fmt("placeholder"); + State.MatchLive = std::chrono::system_clock::now(); + if (State.Replay_IsLive && !State.Replay_IsPlaying) + { + State.MatchCurrent = State.MatchLive; + fmt = std::format("{:%OH:%OM:%OS}", State.MatchLive - State.MatchStart); + } + else + { + fmt = std::format("{:%OH:%OM:%OS}", State.MatchCurrent - State.MatchLive); + } + SliderChrono("##replay_slider", &State.MatchCurrent, &State.MatchStart, &State.MatchLive, fmt, ImGuiSliderFlags_None); + + ImGui::EndChild(); ImGui::End(); + Profiler::EndSample("ReplayRender"); } } \ No newline at end of file diff --git a/gui/replay.hpp b/gui/replay.hpp index f45453da..16ece53c 100644 --- a/gui/replay.hpp +++ b/gui/replay.hpp @@ -1,7 +1,23 @@ #pragma once +#include "imgui/imgui.h" +#include +#include namespace Replay { + extern std::mutex replayEventMutex; void Init(); + void Reset(); void Render(); + + struct WalkEvent_LineData + { + uint8_t playerId; + uint8_t colorId; + std::vector pendingPoints; + std::vector pendingTimeStamps; + + std::vector simplifiedPoints; + std::vector simplifiedTimeStamps; + }; } \ No newline at end of file diff --git a/gui/tabs/debug_tab.cpp b/gui/tabs/debug_tab.cpp index 09de55ab..540adba7 100644 --- a/gui/tabs/debug_tab.cpp +++ b/gui/tabs/debug_tab.cpp @@ -4,7 +4,10 @@ #include "state.hpp" #include "main.h" #include "game.h" +#include "profiler.h" +#include "logger.h" #include +#include namespace DebugTab { @@ -34,6 +37,55 @@ namespace DebugTab { ImGui::Checkbox("Log Unity Debug Messages", &State.ShowUnityLogs); + ImGui::Dummy(ImVec2(4, 4)); + + ImGui::Text("Num Raw Events: %d", State.rawEvents.size()); + ImGui::Text("Num Live Events: %d", State.liveReplayEvents.size()); + + ImGui::Text("ReplayMatchStart: %s", std::format("{:%OH:%OM:%OS}", State.MatchStart).c_str()); + ImGui::Text("ReplayMatchCurrent: %s", std::format("{:%OH:%OM:%OS}", State.MatchCurrent).c_str()); + ImGui::Text("ReplayMatchLive: %s", std::format("{:%OH:%OM:%OS}", std::chrono::system_clock::now()).c_str()); + ImGui::Text("ReplayIsLive: %s", (State.Replay_IsLive) ? "True" : "False"); + ImGui::Text("ReplayIsPlaying: %s", (State.Replay_IsPlaying) ? "True" : "False"); + + if (ImGui::Button("Re-simplify polylines (check console)")) + { + for (auto& playerPolylinePair : State.replayWalkPolylineByPlayer) + { + std::vector resimplifiedPoints; + std::vector resimplifiedTimeStamps; + Replay::WalkEvent_LineData& plrLineData = playerPolylinePair.second; + size_t numOldSimpPoints = plrLineData.simplifiedPoints.size(); + DoPolylineSimplification(plrLineData.simplifiedPoints, plrLineData.simplifiedTimeStamps, resimplifiedPoints, resimplifiedTimeStamps, 50.f, false); + STREAM_DEBUG("Player[" << playerPolylinePair.first << "]: Re-simplification could reduce " << numOldSimpPoints << " points to " << resimplifiedPoints.size()); + } + } + + if (ImGui::CollapsingHeader("Profiler")) + { + if (ImGui::Button("Clear Stats")) + { + Profiler::ClearStats(); + } + + ImGui::BeginChild("debug#profiler", ImVec2(0, 0), true); + + std::stringstream statStream; + Profiler::AppendStatStringStream("WalkEventCreation", statStream); + Profiler::AppendStatStringStream("ReplayRender", statStream); + Profiler::AppendStatStringStream("ReplayPolyline", statStream); + Profiler::AppendStatStringStream("PolylineSimplification", statStream); + Profiler::AppendStatStringStream("ReplayPlayerIcons", statStream); + Profiler::AppendStatStringStream("ReplayEventIcons", statStream); + // NOTE: + // can also just do this to dump all stats, but i like doing them individually so i can control the order better: + // Profiler::WriteStatsToStream(statStream); + + ImGui::TextUnformatted(statStream.str().c_str()); + + ImGui::EndChild(); + } + ImGui::EndTabItem(); } } diff --git a/gui/tabs/radar_tab.cpp b/gui/tabs/radar_tab.cpp index b9a1059a..76c32585 100644 --- a/gui/tabs/radar_tab.cpp +++ b/gui/tabs/radar_tab.cpp @@ -37,6 +37,9 @@ namespace RadarTab { if (ImGui::Checkbox("Hide Radar During Meetings", &State.HideRadar_During_Meetings)) { State.Save(); } + if (ImGui::Checkbox("Draw Player Icons", &State.RadarDrawIcons)) { + State.Save(); + } if (ImGui::Checkbox("Lock Radar Position", &State.LockRadar)) { State.Save(); } diff --git a/gui/tabs/replay_tab.cpp b/gui/tabs/replay_tab.cpp index 60ef2a8c..a6c3d107 100644 --- a/gui/tabs/replay_tab.cpp +++ b/gui/tabs/replay_tab.cpp @@ -1,6 +1,7 @@ #include "pch-il2cpp.h" #include "replay_tab.h" #include "state.hpp" +#include namespace ReplayTab { void Render() { @@ -9,6 +10,21 @@ namespace ReplayTab { if (ImGui::Checkbox("Show Replay", &State.ShowReplay)) { State.Save(); } + if (ImGui::Checkbox("Show only last", &State.Replay_ShowOnlyLastSeconds)) + { + State.Save(); + } + ImGui::SameLine(); + if (ImGui::SliderInt("seconds", &State.Replay_LastSecondsValue, 1, 600, "%d", ImGuiSliderFlags_AlwaysClamp)) + { + State.Save(); + } + + if (ImGui::Checkbox("Clear after meeting", &State.Replay_ClearAfterMeeting)) + { + State.Save(); + } + if (ImGui::ColorEdit4("Replay Map Color", (float*)&State.SelectedReplayMapColor, ImGuiColorEditFlags__OptionsDefault diff --git a/hooks/DirectX.cpp b/hooks/DirectX.cpp index badeec49..22da8be2 100644 --- a/hooks/DirectX.cpp +++ b/hooks/DirectX.cpp @@ -17,6 +17,7 @@ #include "resource_data.h" #include "game.h" #include "console.hpp" +#include "profiler.h" #include @@ -33,6 +34,7 @@ HANDLE DirectX::hRenderSemaphore; constexpr DWORD MAX_RENDER_THREAD_COUNT = 5; //Should be overkill for our purposes std::vector maps = std::vector(); +std::unordered_map icons; typedef struct Cache { @@ -120,6 +122,17 @@ bool ImGuiInitialization(IDXGISwapChain* pSwapChain) { maps.push_back({ D3D11Image(Resource(IDB_PNG3), pDevice), 8.F, 21.F, 10.F }); maps.push_back({ D3D11Image(Resource(IDB_PNG4), pDevice), 162.F, 107.F, 6.F }); + icons.insert({ ICON_TYPES::VENT_IN, { D3D11Image(Resource(IDB_PNG5), pDevice), 0.02f }}); + icons.insert({ ICON_TYPES::VENT_OUT, { D3D11Image(Resource(IDB_PNG6), pDevice), 0.02f }}); + icons.insert({ ICON_TYPES::KILL, { D3D11Image(Resource(IDB_PNG7), pDevice), 0.02f } }); + icons.insert({ ICON_TYPES::REPORT, { D3D11Image(Resource(IDB_PNG8), pDevice), 0.02f } }); + icons.insert({ ICON_TYPES::TASK, { D3D11Image(Resource(IDB_PNG9), pDevice), 0.02f } }); + icons.insert({ ICON_TYPES::PLAYER, { D3D11Image(Resource(IDB_PNG10), pDevice), 0.02f } }); + icons.insert({ ICON_TYPES::CROSS, { D3D11Image(Resource(IDB_PNG11), pDevice), 0.02f } }); + icons.insert({ ICON_TYPES::DEAD, { D3D11Image(Resource(IDB_PNG12), pDevice), 0.02f } }); + icons.insert({ ICON_TYPES::PLAY, { D3D11Image(Resource(IDB_PNG13), pDevice), 0.55f } }); + icons.insert({ ICON_TYPES::PAUSE, { D3D11Image(Resource(IDB_PNG14), pDevice), 0.55f } }); + DirectX::hRenderSemaphore = CreateSemaphore( NULL, // default security attributes MAX_RENDER_THREAD_COUNT, // initial count @@ -151,6 +164,11 @@ HRESULT __stdcall dPresent(IDXGISwapChain* __this, UINT SyncInterval, UINT Flags } } + if (!Profiler::HasInitialized) + { + Profiler::InitProfiling(); + } + WaitForSingleObject(DirectX::hRenderSemaphore, INFINITE); il2cpp_gc_disable(); diff --git a/hooks/DirectX.h b/hooks/DirectX.h index f3089d4f..036a5fa4 100644 --- a/hooks/DirectX.h +++ b/hooks/DirectX.h @@ -2,8 +2,22 @@ #include "resources.h" #include "directx11.h" #include +#include #include +enum ICON_TYPES { + VENT_IN, + VENT_OUT, + KILL, + REPORT, + TASK, + PLAYER, + CROSS, + DEAD, + PLAY, + PAUSE +}; + struct MapTexture { D3D11Image mapImage; float x_offset; @@ -11,7 +25,13 @@ struct MapTexture { float scale; }; +struct IconTexture { + D3D11Image iconImage; + float scale; +}; + extern std::vector maps; +extern std::unordered_map icons; extern D3D_PRESENT_FUNCTION oPresent; HRESULT __stdcall dPresent(IDXGISwapChain* __this, UINT SyncInterval, UINT Flags); diff --git a/hooks/Dleks.cpp b/hooks/Dleks.cpp index 4e3119a5..0dc2d809 100644 --- a/hooks/Dleks.cpp +++ b/hooks/Dleks.cpp @@ -3,8 +3,13 @@ #include "state.hpp" bool dConstants_ShouldFlipSkeld(MethodInfo* method) { + bool orig_return = Constants_ShouldFlipSkeld(method); if (State.FlipSkeld) { return true; } - return Constants_ShouldFlipSkeld(method); + else if (orig_return) + { + State.FlipSkeld = true; + } + return orig_return; } \ No newline at end of file diff --git a/hooks/InnerNetClient.cpp b/hooks/InnerNetClient.cpp index 69acd5e7..ebccb4b3 100644 --- a/hooks/InnerNetClient.cpp +++ b/hooks/InnerNetClient.cpp @@ -5,6 +5,8 @@ #include "game.h" #include "logger.h" #include "utility.h" +#include "replay.hpp" +#include "profiler.h" #include void dInnerNetClient_Update(InnerNetClient* __this, MethodInfo* method) @@ -47,7 +49,6 @@ void dInnerNetClient_Update(InnerNetClient* __this, MethodInfo* method) if (!IsInLobby()) { State.selectedPlayer = PlayerSelection(); - State.FlipSkeld = false; State.NoClip = false; State.HotkeyNoClip = false; State.originalName = "-"; @@ -131,8 +132,8 @@ void dAmongUsClient_OnPlayerLeft(AmongUsClient* __this, ClientData* data, Discon if (it != State.aumUsers.end()) State.aumUsers.erase(it); - State.events[data->fields.Character->fields.PlayerId][EVENT_DISCONNECT].push_back(new DisconnectEvent(GetEventPlayer(data->fields.Character->fields._cachedData).value())); - State.consoleEvents.push_back(new DisconnectEvent(GetEventPlayer(data->fields.Character->fields._cachedData).value())); + State.rawEvents.push_back(std::make_unique(GetEventPlayer(data->fields.Character->fields._cachedData).value())); + State.liveReplayEvents.push_back(std::make_unique(GetEventPlayer(data->fields.Character->fields._cachedData).value())); } AmongUsClient_OnPlayerLeft(__this, data, reason, method); @@ -195,10 +196,13 @@ void dCustomNetworkTransform_SnapTo(CustomNetworkTransform* __this, Vector2 posi void dInnerNetClient_StartEndGame(InnerNetClient* __this, MethodInfo* method) { State.aumUsers.clear(); - State.consoleEvents.clear(); - for (int i = 0; i < 10; i++) - for (int j = 0; j < EVENT_TYPES_SIZE; j++) - State.events[i][j].clear(); + Replay::Reset(); + + for (auto& e : State.rawEvents) + e.reset(); + State.rawEvents.clear(); + + State.MatchEnd = std::chrono::system_clock::now(); InnerNetClient_StartEndGame(__this, method); } diff --git a/hooks/MeetingHud.cpp b/hooks/MeetingHud.cpp index 3a0fedd7..6f1f95ba 100644 --- a/hooks/MeetingHud.cpp +++ b/hooks/MeetingHud.cpp @@ -3,6 +3,7 @@ #include "state.hpp" #include "game.h" #include "logger.h" +#include void dMeetingHud_Awake(MeetingHud* __this, MethodInfo* method) { State.voteMonitor.reset(); @@ -14,6 +15,11 @@ void dMeetingHud_Awake(MeetingHud* __this, MethodInfo* method) { void dMeetingHud_Close(MeetingHud* __this, MethodInfo* method) { State.InMeeting = false; + if (State.Replay_ClearAfterMeeting) + { + Replay::Reset(); + } + MeetingHud_Close(__this, method); } @@ -57,8 +63,8 @@ void dMeetingHud_Update(MeetingHud* __this, MethodInfo* method) { // votedFor will either contain the id of the person they voted for, -1 if they skipped, or -2 if they didn't vote. We don't want to record people who didn't vote if (isVotingState && didVote && playerVoteArea->fields.VotedFor != -2 && !State.voteMonitor[playerData->fields.PlayerId]) { - State.events[playerVoteArea->fields.TargetPlayerId][EVENT_VOTE].push_back(new CastVoteEvent(GetEventPlayer(playerData).value(), GetEventPlayer(GetPlayerDataById(playerVoteArea->fields.VotedFor)))); - State.consoleEvents.push_back(new CastVoteEvent(GetEventPlayer(playerData).value(), GetEventPlayer(GetPlayerDataById(playerVoteArea->fields.VotedFor)))); + State.rawEvents.push_back(std::make_unique(GetEventPlayer(playerData).value(), GetEventPlayer(GetPlayerDataById(playerVoteArea->fields.VotedFor)))); + State.liveReplayEvents.push_back(std::make_unique(GetEventPlayer(playerData).value(), GetEventPlayer(GetPlayerDataById(playerVoteArea->fields.VotedFor)))); State.voteMonitor[playerData->fields.PlayerId] = true; STREAM_DEBUG("Id " << +playerData->fields.PlayerId << " voted for " << +playerVoteArea->fields.VotedFor); } diff --git a/hooks/PlayerControl.cpp b/hooks/PlayerControl.cpp index fa8c39d6..87662dd0 100644 --- a/hooks/PlayerControl.cpp +++ b/hooks/PlayerControl.cpp @@ -4,6 +4,8 @@ #include "state.hpp" #include "esp.hpp" #include "_rpc.h" +#include "replay.hpp" +#include "profiler.h" #include #include @@ -14,15 +16,16 @@ void dPlayerControl_CompleteTask(PlayerControl* __this, uint32_t idx, MethodInfo for (auto normalPlayerTask : normalPlayerTasks) if (normalPlayerTask->fields._._Id_k__BackingField == idx) taskType = normalPlayerTask->fields._.TaskType; - State.events[__this->fields.PlayerId][EVENT_TASK].push_back(new TaskCompletedEvent(GetEventPlayerControl(__this).value(), taskType, PlayerControl_GetTruePosition(__this, NULL))); - State.consoleEvents.push_back(new TaskCompletedEvent(GetEventPlayerControl(__this).value(), taskType, PlayerControl_GetTruePosition(__this, NULL))); + State.rawEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), taskType, PlayerControl_GetTruePosition(__this, NULL))); + State.liveReplayEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), taskType, PlayerControl_GetTruePosition(__this, NULL))); PlayerControl_CompleteTask(__this, idx, method); } -int dPlayerControl_fixedUpdateTimer = 50; -int dPlayerControl_fixedUpdateCount = 0; +float dPlayerControl_fixedUpdateTimer = 50; +float dPlayerControl_fixedUpdateCount = 0; void dPlayerControl_FixedUpdate(PlayerControl* __this, MethodInfo* method) { + dPlayerControl_fixedUpdateTimer = round(1.f / Time_get_fixedDeltaTime(nullptr)); if (__this == *Game::pLocalPlayer) { if (State.rpcCooldown == 0) { MessageWriter* rpcMessage = InnerNetClient_StartRpc((InnerNetClient*)(*Game::pAmongUsClient), __this->fields._.NetId, (uint8_t)42069, (SendOption__Enum)1, NULL); @@ -147,12 +150,53 @@ void dPlayerControl_FixedUpdate(PlayerControl* __this, MethodInfo* method) { ImVec2 localScreenPosition = WorldToScreen(localPos); Vector2 playerPos = PlayerControl_GetTruePosition(__this, nullptr); + Vector2 prevPlayerPos = {State.lastWalkEventPosPerPlayer[__this->fields.PlayerId].x, State.lastWalkEventPosPerPlayer[__this->fields.PlayerId].y}; - // Use UpdateTimer and UpdateCount to execute this part every second - dPlayerControl_fixedUpdateCount++; - if (dPlayerControl_fixedUpdateCount >= dPlayerControl_fixedUpdateTimer) { + State.lastWalkEventPosPerPlayer[__this->fields.PlayerId].x = playerPos.x; + State.lastWalkEventPosPerPlayer[__this->fields.PlayerId].y = playerPos.y; + + // only update our counter if fixedUpdate is executed on local player + if (__this == *Game::pLocalPlayer) + dPlayerControl_fixedUpdateCount++; + + if (State.Replay_IsPlaying + && !State.Replay_IsLive + && dPlayerControl_fixedUpdateCount >= dPlayerControl_fixedUpdateTimer) + { dPlayerControl_fixedUpdateCount = 0; - State.events[__this->fields.PlayerId][EVENT_WALK].push_back(new WalkEvent(GetEventPlayerControl(__this).value(), localPos)); + State.MatchCurrent += std::chrono::seconds(1); + } + + if (!State.InMeeting) + { + Profiler::BeginSample("WalkEventCreation"); + std::lock_guard replayLock(Replay::replayEventMutex); + float dist = GetDistanceBetweenPoints_Unity(playerPos, prevPlayerPos); + // NOTE: + // the localplayer moves even while standing still, by the tiniest amount. + // hopefully 0.01 will be big enough to filter that out but small enough to catch every real movement + if (dist > 0.01f) + { + // NOTE: + // we do not add walkevents to liveReplayEvents. linedata contains everything we need for live visualization. + State.rawEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), playerPos)); + ImVec2 mapPos_pre = {maps[State.mapType].x_offset + (playerPos.x * maps[State.mapType].scale), maps[State.mapType].y_offset - (playerPos.y * maps[State.mapType].scale)}; + if (State.replayWalkPolylineByPlayer.find(__this->fields.PlayerId) == State.replayWalkPolylineByPlayer.end()) + { + // first-time init + State.replayWalkPolylineByPlayer[__this->fields.PlayerId] = {}; + State.replayWalkPolylineByPlayer[__this->fields.PlayerId].pendingPoints = {}; + State.replayWalkPolylineByPlayer[__this->fields.PlayerId].pendingTimeStamps = {}; + State.replayWalkPolylineByPlayer[__this->fields.PlayerId].simplifiedPoints = {}; + State.replayWalkPolylineByPlayer[__this->fields.PlayerId].simplifiedTimeStamps = {}; + } + State.replayWalkPolylineByPlayer[__this->fields.PlayerId].playerId = __this->fields.PlayerId; + State.replayWalkPolylineByPlayer[__this->fields.PlayerId].colorId = State.rawEvents.back().get()->getSource().colorId; + + State.replayWalkPolylineByPlayer[__this->fields.PlayerId].pendingPoints.push_back(mapPos_pre); + State.replayWalkPolylineByPlayer[__this->fields.PlayerId].pendingTimeStamps.push_back(State.rawEvents.back().get()->GetTimeStamp()); + } + Profiler::EndSample("WalkEventCreation"); } PlayerData espPlayerData; @@ -172,7 +216,8 @@ void dPlayerControl_FixedUpdate(PlayerControl* __this, MethodInfo* method) { app::PlayerControl_FixedUpdate(__this, method); } -void dPlayerControl_RpcSyncSettings(PlayerControl* __this, GameOptionsData* gameOptions, MethodInfo* method) { +void dPlayerControl_RpcSyncSettings(PlayerControl* __this, GameOptionsData* gameOptions, MethodInfo* method) +{ State.PrevPlayerSpeed = gameOptions->fields.PlayerSpeedMod; State.PlayerSpeed = gameOptions->fields.PlayerSpeedMod; State.PrevKillDistance = gameOptions->fields.KillDistance; @@ -183,25 +228,34 @@ void dPlayerControl_RpcSyncSettings(PlayerControl* __this, GameOptionsData* game PlayerControl_RpcSyncSettings(__this, gameOptions, method); } -void dPlayerControl_MurderPlayer(PlayerControl* __this, PlayerControl* target, MethodInfo* method) { - +void dPlayerControl_MurderPlayer(PlayerControl* __this, PlayerControl* target, MethodInfo* method) +{ if (PlayerIsImpostor(GetPlayerData(__this)) && PlayerIsImpostor(GetPlayerData(target))) { - State.events[__this->fields.PlayerId][EVENT_CHEAT].push_back(new CheatDetectedEvent(GetEventPlayerControl(__this).value(), CHEAT_KILL_IMPOSTOR)); - State.consoleEvents.push_back(new CheatDetectedEvent(GetEventPlayerControl(__this).value(), CHEAT_KILL_IMPOSTOR)); + State.rawEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), CHEAT_KILL_IMPOSTOR)); + State.liveReplayEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), CHEAT_KILL_IMPOSTOR)); } - State.events[__this->fields.PlayerId][EVENT_KILL].push_back(new KillEvent(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value(), PlayerControl_GetTruePosition(__this, NULL))); - State.consoleEvents.push_back(new KillEvent(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value(), PlayerControl_GetTruePosition(__this, NULL))); + State.rawEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value(), PlayerControl_GetTruePosition(__this, NULL), PlayerControl_GetTruePosition(target, NULL))); + State.liveReplayEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value(), PlayerControl_GetTruePosition(__this, NULL), PlayerControl_GetTruePosition(target, NULL))); PlayerControl_MurderPlayer(__this, target, method); } -void dPlayerControl_ReportDeadBody(PlayerControl*__this, GameData_PlayerInfo* target, MethodInfo *method) { - - State.events[__this->fields.PlayerId][(GetEventPlayer(target).has_value() ? EVENT_REPORT : EVENT_MEETING)].push_back(new ReportDeadBodyEvent(GetEventPlayerControl(__this).value(), GetEventPlayer(target), PlayerControl_GetTruePosition(__this, NULL))); - State.consoleEvents.push_back(new ReportDeadBodyEvent(GetEventPlayerControl(__this).value(), GetEventPlayer(target), PlayerControl_GetTruePosition(__this, NULL))); +void dPlayerControl_CmdReportDeadBody(PlayerControl* __this, GameData_PlayerInfo* target, MethodInfo* method) +{ + State.rawEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), GetEventPlayer(target), PlayerControl_GetTruePosition(__this, NULL), GetTargetPosition(target))); + State.liveReplayEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), GetEventPlayer(target), PlayerControl_GetTruePosition(__this, NULL), GetTargetPosition(target))); + PlayerControl_CmdReportDeadBody(__this, target, method); +} +void dPlayerControl_ReportDeadBody(PlayerControl*__this, GameData_PlayerInfo* target, MethodInfo *method) +{ + if (!IsHost()) + { + State.rawEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), GetEventPlayer(target), PlayerControl_GetTruePosition(__this, NULL), GetTargetPosition(target))); + State.liveReplayEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), GetEventPlayer(target), PlayerControl_GetTruePosition(__this, NULL), GetTargetPosition(target))); + } PlayerControl_ReportDeadBody(__this, target, method); } @@ -243,13 +297,13 @@ void dGameObject_SetActive(GameObject* __this, bool value, MethodInfo* method) } void dPlayerControl_Shapeshift(PlayerControl* __this, PlayerControl* target, bool animate, MethodInfo* method) { - State.events[__this->fields.PlayerId][EVENT_SHAPESHIFT].push_back(new ShapeShiftEvent(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value())); - State.consoleEvents.push_back(new ShapeShiftEvent(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value())); + State.rawEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value())); + State.liveReplayEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value())); PlayerControl_Shapeshift(__this, target, animate, method); } void dPlayerControl_ProtectPlayer(PlayerControl* __this, PlayerControl* target, int32_t colorId, MethodInfo* method) { - State.events[__this->fields.PlayerId][EVENT_PROTECTPLAYER].push_back(new ProtectPlayerEvent(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value())); - State.consoleEvents.push_back(new ProtectPlayerEvent(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value())); + State.rawEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value())); + State.liveReplayEvents.push_back(std::make_unique(GetEventPlayerControl(__this).value(), GetEventPlayerControl(target).value())); PlayerControl_ProtectPlayer(__this, target, colorId, method); } \ No newline at end of file diff --git a/hooks/PolusShipStatus.cpp b/hooks/PolusShipStatus.cpp index 480f8a0a..ef701fc8 100644 --- a/hooks/PolusShipStatus.cpp +++ b/hooks/PolusShipStatus.cpp @@ -3,15 +3,22 @@ #include "state.hpp" #include "logger.h" #include "utility.h" +#include "replay.hpp" +#include "profiler.h" void dPolusShipStatus_OnEnable(PolusShipStatus* __this, MethodInfo* method) { PolusShipStatus_OnEnable(__this, method); - State.consoleEvents.clear(); - for (int i = 0; i < 10; i++) - for (int j = 0; j < EVENT_TYPES_SIZE; j++) - State.events[i][j].clear(); + Replay::Reset(); + + if (Constants_ShouldFlipSkeld(NULL)) + State.FlipSkeld = true; + else + State.FlipSkeld = false; + + State.MatchStart = std::chrono::system_clock::now(); + State.MatchCurrent = State.MatchStart; State.selectedDoor = SystemTypes__Enum::Hallway; State.mapDoors.clear(); diff --git a/hooks/ShipStatus.cpp b/hooks/ShipStatus.cpp index b8c9e5ac..6554cee4 100644 --- a/hooks/ShipStatus.cpp +++ b/hooks/ShipStatus.cpp @@ -3,6 +3,8 @@ #include "state.hpp" #include "logger.h" #include "utility.h" +#include "replay.hpp" +#include "profiler.h" float dShipStatus_CalculateLightRadius(ShipStatus* __this, GameData_PlayerInfo* player, MethodInfo* method) { if (State.MaxVision || State.EnableZoom || State.FreeCam) @@ -14,10 +16,15 @@ float dShipStatus_CalculateLightRadius(ShipStatus* __this, GameData_PlayerInfo* void dShipStatus_OnEnable(ShipStatus* __this, MethodInfo* method) { ShipStatus_OnEnable(__this, method); - State.consoleEvents.clear(); - for (int i = 0; i < 10; i++) - for (int j = 0; j < EVENT_TYPES_SIZE; j++) - State.events[i][j].clear(); + Replay::Reset(); + + if (Constants_ShouldFlipSkeld(NULL)) + State.FlipSkeld = true; + else + State.FlipSkeld = false; + + State.MatchStart = std::chrono::system_clock::now(); + State.MatchCurrent = State.MatchStart; State.selectedDoor = SystemTypes__Enum::Hallway; State.mapDoors.clear(); diff --git a/hooks/Vent.cpp b/hooks/Vent.cpp index 9feacb38..9b8ff5ed 100644 --- a/hooks/Vent.cpp +++ b/hooks/Vent.cpp @@ -2,6 +2,8 @@ #include "_hooks.h" #include "state.hpp" +#include + float dVent_CanUse(Vent* __this, GameData_PlayerInfo* pc, bool* canUse, bool* couldUse, MethodInfo* method) { if (State.UnlockVents) { auto ventTransform = app::Component_get_transform((Component_1*)__this, NULL); @@ -27,8 +29,9 @@ float dVent_CanUse(Vent* __this, GameData_PlayerInfo* pc, bool* canUse, bool* co void dVent_EnterVent(Vent* __this, PlayerControl* pc, MethodInfo * method) { auto ventVector = app::Transform_get_position(app::Component_get_transform((Component_1*)__this, NULL), NULL); - State.events[pc->fields.PlayerId][EVENT_VENT].push_back(new VentEvent(GetEventPlayerControl(pc).value(), {ventVector.x, ventVector.y}, VENT_ENTER)); - State.consoleEvents.push_back(new VentEvent(GetEventPlayerControl(pc).value(), {ventVector.x, ventVector.y}, VENT_ENTER)); + app::Vector2 ventVector2D = {ventVector.x, ventVector.y}; + State.rawEvents.push_back(std::make_unique(GetEventPlayerControl(pc).value(), ventVector2D, VENT_ENTER)); + State.liveReplayEvents.push_back(std::make_unique(GetEventPlayerControl(pc).value(), ventVector2D, VENT_ENTER)); Vent_EnterVent(__this, pc, method); } @@ -36,8 +39,9 @@ void dVent_EnterVent(Vent* __this, PlayerControl* pc, MethodInfo * method) { void dVent_ExitVent(Vent* __this, PlayerControl* pc, MethodInfo * method) { auto ventVector = app::Transform_get_position(app::Component_get_transform((Component_1*)__this, NULL), NULL); - State.events[pc->fields.PlayerId][EVENT_VENT].push_back(new VentEvent(GetEventPlayerControl(pc).value(), {ventVector.x, ventVector.y}, VENT_EXIT)); - State.consoleEvents.push_back(new VentEvent(GetEventPlayerControl(pc).value(), {ventVector.x, ventVector.y}, VENT_EXIT)); + app::Vector2 ventVector2D = {ventVector.x, ventVector.y}; + State.rawEvents.push_back(std::make_unique(GetEventPlayerControl(pc).value(), ventVector2D, VENT_EXIT)); + State.liveReplayEvents.push_back(std::make_unique(GetEventPlayerControl(pc).value(), ventVector2D, VENT_EXIT)); Vent_ExitVent(__this, pc, method); } \ No newline at end of file diff --git a/hooks/_hooks.cpp b/hooks/_hooks.cpp index 4e8a996d..105bb9f7 100644 --- a/hooks/_hooks.cpp +++ b/hooks/_hooks.cpp @@ -93,6 +93,7 @@ void DetourInitilization() { HOOKFUNC(GameOptionsData_Deserialize_1); HOOKFUNC(PlayerControl_MurderPlayer); HOOKFUNC(PlayerControl_CompleteTask); + HOOKFUNC(PlayerControl_CmdReportDeadBody); HOOKFUNC(PlayerControl_ReportDeadBody); HOOKFUNC(RoleManager_AssignRolesFromList); HOOKFUNC(PlayerControl_HandleRpc); @@ -173,6 +174,7 @@ void DetourUninitialization() UNHOOKFUNC(GameOptionsData_Deserialize_1); UNHOOKFUNC(PlayerControl_MurderPlayer); UNHOOKFUNC(PlayerControl_CompleteTask); + UNHOOKFUNC(PlayerControl_CmdReportDeadBody); UNHOOKFUNC(PlayerControl_ReportDeadBody); UNHOOKFUNC(RoleManager_AssignRolesFromList); UNHOOKFUNC(PlayerControl_HandleRpc); diff --git a/hooks/_hooks.h b/hooks/_hooks.h index 0519c543..1cf96e8f 100644 --- a/hooks/_hooks.h +++ b/hooks/_hooks.h @@ -33,6 +33,7 @@ void dPlainDoor_SetDoorway(PlainDoor* __this, bool open, MethodInfo* method); void dPlayerControl_CompleteTask(PlayerControl* __this, uint32_t idx, MethodInfo* method); void dPlayerControl_FixedUpdate(PlayerControl* __this, MethodInfo* method); void dPlayerControl_MurderPlayer(PlayerControl* __this, PlayerControl* target, MethodInfo* method); +void dPlayerControl_CmdReportDeadBody(PlayerControl* __this, GameData_PlayerInfo* target, MethodInfo* method); void dPlayerControl_ReportDeadBody(PlayerControl*__this, GameData_PlayerInfo* target, MethodInfo *method); //void dPlayerControl_RpcSetRole(PlayerControl* __this, RoleTypes__Enum roleType, MethodInfo* method); void dRoleManager_AssignRolesForTeam(List_1_GameData_PlayerInfo_* players, RoleOptionsData* opts, RoleTeamTypes__Enum team, int32_t teamMax, Nullable_1_RoleTypes_ defaultRole, MethodInfo* method); diff --git a/includes/imgui/imgui.h b/includes/imgui/imgui.h index 798ae065..31e9f8ee 100644 --- a/includes/imgui/imgui.h +++ b/includes/imgui/imgui.h @@ -441,7 +441,7 @@ namespace ImGui IMGUI_API bool SmallButton(const char* label); // button with FramePadding=(0,0) to easily embed within text IMGUI_API bool InvisibleButton(const char* str_id, const ImVec2& size, ImGuiButtonFlags flags = 0); // flexible button behavior without the visuals, frequently useful to build custom behaviors using the public api (along with IsItemActive, IsItemHovered, etc.) IMGUI_API bool ArrowButton(const char* str_id, ImGuiDir dir); // square button with an arrow shape - IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0)); + IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& pos_offset = ImVec2(0, 0), const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0)); IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding IMGUI_API bool Checkbox(const char* label, bool* v); IMGUI_API bool CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value); diff --git a/includes/imgui/imgui_widgets.cpp b/includes/imgui/imgui_widgets.cpp index fe5d2ba3..41415927 100644 --- a/includes/imgui/imgui_widgets.cpp +++ b/includes/imgui/imgui_widgets.cpp @@ -970,13 +970,13 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, floa return held; } -void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& pos_offset, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; - ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ImRect bb(window->DC.CursorPos + pos_offset, window->DC.CursorPos + size + pos_offset); if (border_col.w > 0.0f) bb.Max += ImVec2(2, 2); ItemSize(bb); diff --git a/profiler.cpp b/profiler.cpp new file mode 100644 index 00000000..373b0b36 --- /dev/null +++ b/profiler.cpp @@ -0,0 +1,189 @@ +#include "pch-il2cpp.h" +#include "profiler.h" +#include "logger.h" + +std::map Profiler::StatMap; +LARGE_INTEGER Profiler::QPCFrequency; +bool Profiler::HasInitialized = false; + +void Profiler::InitProfiling() +{ +#if _DEBUG + QueryPerformanceFrequency(&QPCFrequency); + StatMap = std::map(); + HasInitialized = true; + LOG_DEBUG("Initialized profiler"); +#endif +} + +void Profiler::BeginSample(const char* Stat_Name) +{ +#if _DEBUG + std::map::iterator it = StatMap.find(Stat_Name); + StatObject* stat = nullptr; + if (it == StatMap.end()) + { + STREAM_DEBUG("Creating new stat object for '" << Stat_Name << "'"); + std::pair::iterator, bool> result = StatMap.insert(std::map::value_type(Stat_Name, StatObject())); + if (result.second) + stat = &result.first->second; + } + else + { + stat = &it->second; + } + if (stat != nullptr) + QueryPerformanceCounter(&stat->QPCStart); +#endif +} + +void Profiler::EndSample(const char* Stat_Name) +{ +#if _DEBUG + std::map::iterator it = StatMap.find(Stat_Name); + if (it != StatMap.end()) + { + StatObject* stat = &it->second; + + QueryPerformanceCounter(&stat->QPCEnd); + + stat->TotalSamples++; + stat->ElapsedMicroseconds = (((stat->QPCEnd.QuadPart - stat->QPCStart.QuadPart) * 1000000ll) / Profiler::QPCFrequency.QuadPart); + stat->CumulativeTotal += stat->ElapsedMicroseconds; + stat->AverageSample_Microseconds = stat->CumulativeTotal / stat->TotalSamples; + if (stat->ElapsedMicroseconds > stat->LongestSample_Microseconds) stat->LongestSample_Microseconds = stat->ElapsedMicroseconds; + //printf("Ended sample for stat '%s', took %llu ticks\n", Stat_Name, &stat->QPCEnd.QuadPart); + } + else + { + STREAM_DEBUG("Could not find stat '" << Stat_Name << "'"); + } +#endif +} + +void Profiler::GetStat(const char* Stat_Name, __int64& AverageSample_Microseconds, __int64& LongestSample_Microseconds, __int64& TotalSamples) +{ +#if _DEBUG + // NOTE: + // if it doesn't exist, the [] operator should create it and return the newly created instance + // so this shouldn't give any errors + StatObject* stat = &StatMap[Stat_Name]; + AverageSample_Microseconds = stat->AverageSample_Microseconds; + LongestSample_Microseconds = stat->LongestSample_Microseconds; + TotalSamples = stat->TotalSamples; +#endif +} + +const char* Profiler::GetFormattedStatString(const char* Stat_Name) +{ +#if _DEBUG + StatObject* stat = &StatMap[Stat_Name]; + std::stringstream profilingSS; + profilingSS << stat->AverageSample_Microseconds << " us (" << (stat->AverageSample_Microseconds / 1000ll) << " ms)" + << "[longest: " << stat->LongestSample_Microseconds << " us][" << stat->TotalSamples << "]"; + + return profilingSS.str().c_str(); +#else + return "ERROR"; +#endif +} + +const wchar_t* Profiler::GetFormattedStatStringWide(const char* Stat_Name) +{ +#if _DEBUG + StatObject* stat = &StatMap[Stat_Name]; + std::wstringstream profilingWSS; + profilingWSS << stat->AverageSample_Microseconds << L" us (" << (stat->AverageSample_Microseconds / 1000ll) << L" ms)" + << L"[longest: " << stat->LongestSample_Microseconds << L" us][" << stat->TotalSamples << L"]"; + + return profilingWSS.str().c_str(); +#else + return L"ERROR"; +#endif +} + +void Profiler::AppendStatStringStream(const char* Stat_Name, std::stringstream& ss) +{ +#if _DEBUG + StatObject* stat = &StatMap[Stat_Name]; + ss << Stat_Name << ": " << stat->AverageSample_Microseconds << " us (" << (stat->AverageSample_Microseconds / 1000ll) << " ms)" + << "[longest: " << stat->LongestSample_Microseconds << " us][" << stat->TotalSamples << "]\n"; +#endif +} + +void Profiler::AppendStatStringStreamWide(const char* Stat_Name, std::wstringstream& wss) +{ +#if _DEBUG + StatObject* stat = &StatMap[Stat_Name]; + wss << Stat_Name << L": " << stat->AverageSample_Microseconds << L" us (" << (stat->AverageSample_Microseconds / 1000ll) << L" ms)" + << L"[longest: " << stat->LongestSample_Microseconds << L" us][" << stat->TotalSamples << L"]\n"; +#endif +} + +void Profiler::WriteStatsToStream(std::stringstream& ss) +{ +#if _DEBUG + //printf("Writing stats to stream\n"); + std::map::iterator it; + for (it = StatMap.begin(); it != StatMap.end(); ++it) + { + StatObject* stat = &it->second; + ss << it->first.c_str() << ": " << stat->AverageSample_Microseconds << " us (" << (stat->AverageSample_Microseconds / 1000ll) << " ms)" + << "[longest: " << stat->LongestSample_Microseconds << " us][" << stat->TotalSamples << "]\n"; + //printf("----Appended stat '%s' (%lld - %lld - %lld - %lld)\n", it->first, stat->AverageSample_Microseconds, (stat->AverageSample_Microseconds / 1000ll), stat->LongestSample_Microseconds, stat->TotalSamples); + } + //printf("Done writing stats to stream\n"); +#endif +} + +void Profiler::WriteStatsToStreamWide(std::wstringstream& wss) +{ +#if _DEBUG + //printf("Writing stats to stream\n"); + std::map::iterator it; + for (it = StatMap.begin(); it != StatMap.end(); ++it) + { + StatObject* stat = &it->second; + wss << it->first.c_str() << L": " << stat->AverageSample_Microseconds << L" us (" << (stat->AverageSample_Microseconds / 1000ll) << L" ms)" + << L"[longest: " << stat->LongestSample_Microseconds << L" us][" << stat->TotalSamples << L"]\n"; + //printf("----Appended stat '%s' (%lld - %lld - %lld - %lld)\n", it->first, stat->AverageSample_Microseconds, (stat->AverageSample_Microseconds / 1000ll), stat->LongestSample_Microseconds, stat->TotalSamples); + } + //printf("Done writing stats to stream\n"); +#endif +} + +void Profiler::ClearStat(const char* Stat_Name) +{ +#if _DEBUG + auto it = StatMap.find(Stat_Name); + if (it != StatMap.end()) + { + StatObject* stat = &it->second; + stat->TotalSamples = 0; + stat->CumulativeTotal = 0; + stat->AverageSample_Microseconds = 0; + stat->LongestSample_Microseconds = 0; + stat->QPCStart.QuadPart = 0; + stat->QPCEnd.QuadPart = 0; + stat->ElapsedMicroseconds = 0; + } +#endif +} + +void Profiler::ClearStats() +{ +#if _DEBUG + std::map::iterator it; + for (it = StatMap.begin(); it != StatMap.end(); ++it) + { + StatObject* stat = &it->second; + stat->TotalSamples = 0; + stat->CumulativeTotal = 0; + stat->AverageSample_Microseconds = 0; + stat->LongestSample_Microseconds = 0; + stat->QPCStart.QuadPart = 0; + stat->QPCEnd.QuadPart = 0; + stat->ElapsedMicroseconds = 0; + } +#endif +} \ No newline at end of file diff --git a/profiler.h b/profiler.h new file mode 100644 index 00000000..ffca0013 --- /dev/null +++ b/profiler.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +class Profiler +{ +public: + static void InitProfiling(); + static void BeginSample(const char* Stat_Name); + static void EndSample(const char* Stat_Name); + static void GetStat(const char* Stat_Name, __int64& AverageSample_Microseconds, __int64& LongestSample_Microseconds, __int64& TotalSamples); + static const char* GetFormattedStatString(const char* Stat_Name); + static const wchar_t* GetFormattedStatStringWide(const char* Stat_Name); + static void AppendStatStringStream(const char* Stat_Name, std::stringstream& ss); + static void AppendStatStringStreamWide(const char* Stat_Name, std::wstringstream& wss); + static void WriteStatsToStream(std::stringstream& ss); + static void WriteStatsToStreamWide(std::wstringstream& wss); + static void ClearStat(const char* Stat_Name); + static void ClearStats(); + + static bool HasInitialized; + +private: + struct StatObject + { + __int64 AverageSample_Microseconds = 0; + __int64 LongestSample_Microseconds = 0; + __int64 TotalSamples = 0; + // + LARGE_INTEGER QPCStart, QPCEnd; + __int64 CumulativeTotal = 0; + __int64 ElapsedMicroseconds = 0; + }; + Profiler() {}; + static LARGE_INTEGER QPCFrequency; + static std::map StatMap; +}; \ No newline at end of file diff --git a/resources/cross.png b/resources/cross.png new file mode 100644 index 00000000..df4dcda2 Binary files /dev/null and b/resources/cross.png differ diff --git a/resources/dead_body.png b/resources/dead_body.png new file mode 100644 index 00000000..614672a9 Binary files /dev/null and b/resources/dead_body.png differ diff --git a/resources/kill.png b/resources/kill.png new file mode 100644 index 00000000..f35552c3 Binary files /dev/null and b/resources/kill.png differ diff --git a/resources/pause.png b/resources/pause.png new file mode 100644 index 00000000..f366a442 Binary files /dev/null and b/resources/pause.png differ diff --git a/resources/play.png b/resources/play.png new file mode 100644 index 00000000..c5c794f2 Binary files /dev/null and b/resources/play.png differ diff --git a/resources/player.png b/resources/player.png new file mode 100644 index 00000000..7e14291a Binary files /dev/null and b/resources/player.png differ diff --git a/resources/report.png b/resources/report.png new file mode 100644 index 00000000..2e4e23b9 Binary files /dev/null and b/resources/report.png differ diff --git a/resources/resource_data.h b/resources/resource_data.h index 2500d473..d8760042 100644 --- a/resources/resource_data.h +++ b/resources/resource_data.h @@ -6,12 +6,22 @@ #define IDB_PNG2 102 #define IDB_PNG3 103 #define IDB_PNG4 104 +#define IDB_PNG5 105 +#define IDB_PNG6 106 +#define IDB_PNG7 107 +#define IDB_PNG8 108 +#define IDB_PNG9 109 +#define IDB_PNG10 110 +#define IDB_PNG11 111 +#define IDB_PNG12 112 +#define IDB_PNG13 113 +#define IDB_PNG14 114 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 105 +#define _APS_NEXT_RESOURCE_VALUE 115 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 diff --git a/resources/resource_data.rc b/resources/resource_data.rc index f8a43cb7..52d6986b 100644 --- a/resources/resource_data.rc +++ b/resources/resource_data.rc @@ -58,6 +58,26 @@ IDB_PNG3 PNG "polus.png" IDB_PNG4 PNG "airship.png" +IDB_PNG5 PNG "vent_in.png" + +IDB_PNG6 PNG "vent_out.png" + +IDB_PNG7 PNG "kill.png" + +IDB_PNG8 PNG "report.png" + +IDB_PNG9 PNG "tick.png" + +IDB_PNG10 PNG "player.png" + +IDB_PNG11 PNG "cross.png" + +IDB_PNG12 PNG "dead_body.png" + +IDB_PNG13 PNG "play.png" + +IDB_PNG14 PNG "pause.png" + #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/resources/tick.png b/resources/tick.png new file mode 100644 index 00000000..2b270c73 Binary files /dev/null and b/resources/tick.png differ diff --git a/resources/vent_in.png b/resources/vent_in.png new file mode 100644 index 00000000..96836087 Binary files /dev/null and b/resources/vent_in.png differ diff --git a/resources/vent_out.png b/resources/vent_out.png new file mode 100644 index 00000000..43025964 Binary files /dev/null and b/resources/vent_out.png differ diff --git a/screenshot.png b/screenshot.png index f0c3da9f..4f32b85a 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/user/state.cpp b/user/state.cpp index 3c102f32..fb492dba 100644 --- a/user/state.cpp +++ b/user/state.cpp @@ -36,12 +36,16 @@ void Settings::Load() { j.at("RadarColor_G").get_to(this->SelectedColor.y); j.at("RadarColor_B").get_to(this->SelectedColor.z); j.at("RadarColor_A").get_to(this->SelectedColor.w); + j.at("RadarDrawIcons").get_to(this->RadarDrawIcons); j.at("ShowReplay").get_to(this->ShowReplay); j.at("ReplayColor_R").get_to(this->SelectedReplayMapColor.x); j.at("ReplayColor_G").get_to(this->SelectedReplayMapColor.y); j.at("ReplayColor_B").get_to(this->SelectedReplayMapColor.z); j.at("ReplayColor_A").get_to(this->SelectedReplayMapColor.w); + j.at("ReplayShowOnlyLastSeconds").get_to(this->Replay_ShowOnlyLastSeconds); + j.at("ReplayLastSecondsValue").get_to(this->Replay_LastSecondsValue); + j.at("ReplayClearAfterMeeting").get_to(this->Replay_ClearAfterMeeting); j.at("ShowEsp").get_to(this->ShowEsp); j.at("ShowEsp_Ghosts").get_to(this->ShowEsp_Ghosts); @@ -92,12 +96,16 @@ void Settings::Save() { {"RadarColor_G", this->SelectedColor.y}, {"RadarColor_B", this->SelectedColor.z}, {"RadarColor_A", this->SelectedColor.w}, + {"RadarDrawIcons", this->RadarDrawIcons}, {"ShowReplay", this->ShowReplay}, {"ReplayColor_R", this->SelectedReplayMapColor.x}, {"ReplayColor_G", this->SelectedReplayMapColor.y}, {"ReplayColor_B", this->SelectedReplayMapColor.z}, {"ReplayColor_A", this->SelectedReplayMapColor.w}, + {"ReplayShowOnlyLastSeconds", this->Replay_ShowOnlyLastSeconds}, + {"ReplayLastSecondsValue", this->Replay_LastSecondsValue}, + {"ReplayClearAfterMeeting", this->Replay_ClearAfterMeeting}, {"ShowEsp", this->ShowEsp}, {"ShowEsp_Ghosts", this->ShowEsp_Ghosts}, diff --git a/user/state.hpp b/user/state.hpp index f03e6853..07e393ff 100644 --- a/user/state.hpp +++ b/user/state.hpp @@ -5,6 +5,7 @@ #include "_rpc.h" #include "keybinds.h" #include "game.h" +#include "replay.hpp" class Settings { public: @@ -61,8 +62,7 @@ class Settings { bool HideRadar_During_Meetings = false; bool ShowRadar_RightClick_Teleport = false; bool LockRadar = false; - - bool ShowReplay = false; + bool RadarDrawIcons = true; bool ShowEsp = false; bool ShowEsp_Ghosts = true; @@ -84,8 +84,20 @@ class Settings { bool CloseAllDoors = false; bool ShowConsole = false; - std::vector consoleEvents; - std::vector events[MAX_PLAYERS][EVENT_TYPES_SIZE]; + bool ShowReplay = false; + bool Replay_ShowOnlyLastSeconds = false; + int Replay_LastSecondsValue = 1; + bool Replay_ClearAfterMeeting = false; + std::chrono::system_clock::time_point MatchStart; + std::chrono::system_clock::time_point MatchCurrent; + std::chrono::system_clock::time_point MatchEnd; + std::chrono::system_clock::time_point MatchLive; + std::vector> rawEvents; + std::vector> liveReplayEvents; + std::vector lastWalkEventPosPerPlayer; + std::map replayWalkPolylineByPlayer; + bool Replay_IsPlaying = true; + bool Replay_IsLive = true; std::bitset<0xFF> voteMonitor; @@ -145,12 +157,19 @@ class Settings { Ship = 0, Hq = 1, Pb = 2, - Airship = 3, - NotSet = 0xFF + Airship = 3 } mapType; bool AutoOpenDoors = false; + Settings() + { + for (int plyIdx = 0; plyIdx < MAX_PLAYERS; plyIdx++) + { + this->lastWalkEventPosPerPlayer.push_back(ImVec2(0.f, 0.f)); + } + } + void Load(); void Save(); }; diff --git a/user/utility.cpp b/user/utility.cpp index 78b678ba..d51f5c44 100644 --- a/user/utility.cpp +++ b/user/utility.cpp @@ -4,6 +4,7 @@ #include "game.h" #include "gitparams.h" #include "logger.h" +#include "profiler.h" EXTERN_C IMAGE_DOS_HEADER __ImageBase; @@ -431,6 +432,12 @@ std::optional GetEventPlayerControl(PlayerControl* player) return EVENT_PLAYER(playerInfo); } +std::optional GetTargetPosition(GameData_PlayerInfo* playerInfo) +{ + if (!playerInfo) return std::nullopt; + return PlayerControl_GetTruePosition(playerInfo->fields._object, NULL); +} + std::vector GetAllCameras() { auto cameras = std::vector(); @@ -682,4 +689,67 @@ RoleTypes__Enum GetRoleTypesEnum(RoleType role) return RoleTypes__Enum::Scientist; } return RoleTypes__Enum::Crewmate; +} + +float GetDistanceBetweenPoints_Unity(Vector2 p1, Vector2 p2) +{ + float dx = p1.x - p2.x, dy = p1.y - p2.y; + return sqrtf(dx * dx + dy * dy); +} + +float GetDistanceBetweenPoints_ImGui(ImVec2 p1, ImVec2 p2) +{ + float dx = p1.x - p2.x, dy = p1.y - p2.y; + return sqrtf(dx * dx + dy * dy); +} + +void DoPolylineSimplification(std::vector& inPoints, std::vector& inTimeStamps, std::vector& outPoints, std::vector& outTimeStamps, float sqDistanceThreshold, bool clearInputs) +{ + sqDistanceThreshold = sqDistanceThreshold - FLT_EPSILON; + size_t numPendingPoints = inPoints.size(); + if (numPendingPoints < 2) + return; + + Profiler::BeginSample("PolylineSimplification"); + ImVec2 prevPoint = inPoints[0], point = inPoints[0]; + std::chrono::system_clock::time_point timestamp = inTimeStamps[0]; + size_t numNewPointsAdded = 0; + + // always add the first point + outPoints.push_back(point); + outTimeStamps.push_back(timestamp); + numNewPointsAdded++; + for (size_t index = 1; index < numPendingPoints; index++) + { + point = inPoints[index]; + timestamp = inTimeStamps[index]; + float diffX = point.x - prevPoint.x, diffY = point.y - prevPoint.y; + if ((diffX * diffX + diffY * diffY) >= sqDistanceThreshold) + { + prevPoint = point; + // add the point if it's beyond the distance threshold of prev point. + outPoints.push_back(point); + outTimeStamps.push_back(timestamp); + numNewPointsAdded++; + } + } + // add the last point if it's not also the first point nor has already been added as the last point + if ((point.x != prevPoint.x) && (point.y != prevPoint.y)) + { + outPoints.push_back(point); + outTimeStamps.push_back(timestamp); + numNewPointsAdded++; + } + + if (clearInputs) + { + inPoints.clear(); + inTimeStamps.clear(); + } + Profiler::EndSample("PolylineSimplification"); +} + +float getMapXOffsetSkeld(float x) +{ + return (State.mapType == Settings::MapType::Ship && State.FlipSkeld) ? x - 50.0f : x; } \ No newline at end of file diff --git a/user/utility.h b/user/utility.h index c4a48fb2..1235aaf2 100644 --- a/user/utility.h +++ b/user/utility.h @@ -118,6 +118,7 @@ SystemTypes__Enum GetSystemTypes(Vector2 vector); // some C++ wizardry to allow overloading on pointer types w/ different base type (then we can rename both to just GetEventPlayer) std::optional GetEventPlayer(GameData_PlayerInfo* playerInfo); std::optional GetEventPlayerControl(PlayerControl* player); +std::optional GetTargetPosition(GameData_PlayerInfo* playerInfo); std::vector GetAllCameras(); std::vector GetAllClients(); Vector2 GetSpawnLocation(int playerId, int numPlayer, bool initialSpawn); @@ -135,4 +136,19 @@ bool PlayerIsImpostor(GameData_PlayerInfo* player); GameData_PlayerOutfit* GetPlayerOutfit(GameData_PlayerInfo* player, bool includeShapeshifted = false); Color GetRoleColor(RoleBehaviour* roleBehaviour); std::string GetRoleName(RoleBehaviour* roleBehaviour, bool abbreviated = false); -RoleTypes__Enum GetRoleTypesEnum(RoleType role); \ No newline at end of file +RoleTypes__Enum GetRoleTypesEnum(RoleType role); +float GetDistanceBetweenPoints_Unity(Vector2 p1, Vector2 p2); +float GetDistanceBetweenPoints_ImGui(ImVec2 p1, ImVec2 p2); + +/// +/// Simplifies a list of points by ensuring the distance between consecutive points is greater than the squared distance threshold; all other points are discarded. +/// +/// Collection of points pending simplification +/// Collection of timestamps associated with each point pending simplification +/// Contains only the points that meet the distance filter +/// The original timestamp associated with each point +/// The squared distance between two consecutive points. We use squared distance to avoid a costly sqrtf operation in the distance calculation +/// Whether both input collections should be cleared after processing. If no work is done they will not be cleared. +void DoPolylineSimplification(std::vector& inPoints, std::vector& inTimeStamps, std::vector& outPoints, std::vector& outTimeStamps, float sqDistanceThreshold, bool clearInputs); + +float getMapXOffsetSkeld(float x); \ No newline at end of file