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