400 lines
17 KiB
C++
400 lines
17 KiB
C++
#include "Framework.h"
|
|
#include "../Framework/Input/Input.h"
|
|
#include <cstdio>
|
|
|
|
bool InScreen(const Vector2& vIn)
|
|
{
|
|
return !(vIn.x < g.GameRect.left || vIn.x > g.GameRect.right ||
|
|
vIn.y < g.GameRect.top || vIn.y > g.GameRect.bottom);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
const char* GetRefreshStatusName(GameRefreshStatus status)
|
|
{
|
|
switch (status)
|
|
{
|
|
case GameRefreshStatus::Waiting: return "WAIT";
|
|
case GameRefreshStatus::GomUnavailable: return "GOM";
|
|
case GameRefreshStatus::NodeListUnavailable: return "NODES";
|
|
case GameRefreshStatus::GameWorldUnavailable: return "GW";
|
|
case GameRefreshStatus::LocalWorldUnavailable: return "LGW";
|
|
case GameRefreshStatus::Ready: return "READY";
|
|
default: return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
ImColor GetItemDisplayColor(const CItem& item,
|
|
const ImColor& questColor, const ImColor& commonColor,
|
|
const ImColor& rareColor, const ImColor& superRareColor,
|
|
const ImColor& corpseColor)
|
|
{
|
|
if (item.m_bIsCorpse)
|
|
return corpseColor;
|
|
if (item.m_bIsQuestItem)
|
|
return questColor;
|
|
|
|
// JsonType.ELootRarity exposes member order only in the dump. These values
|
|
// assume the C# enum uses its default sequential numbering: Common=1,
|
|
// Rare=2, Superrare=3. Unknown values intentionally fall back to common.
|
|
switch (item.m_iRarity)
|
|
{
|
|
case 2: return rareColor;
|
|
case 3: return superRareColor;
|
|
case 1:
|
|
default: return commonColor;
|
|
}
|
|
}
|
|
|
|
bool HasRuntimeDisplayName(const std::string& value, const char* fallback,
|
|
const char* legacyFallback)
|
|
{
|
|
return !value.empty() && value != fallback && value != legacyFallback;
|
|
}
|
|
|
|
std::string GetChineseItemDisplayName(const CItem& item)
|
|
{
|
|
if (item.m_bIsCorpse)
|
|
return "尸体";
|
|
if (HasRuntimeDisplayName(item.m_CName, "物品", "Item"))
|
|
return item.m_CName;
|
|
if (item.m_bIsQuestItem)
|
|
return "任务物品";
|
|
|
|
switch (item.m_iRarity)
|
|
{
|
|
case 2: return "稀有物品";
|
|
case 3: return "超稀有物品";
|
|
default: return "物品";
|
|
}
|
|
}
|
|
|
|
std::string GetChineseContainerDisplayName(const CContainer& container)
|
|
{
|
|
return HasRuntimeDisplayName(container.m_CName, "容器", "Container") ?
|
|
container.m_CName : "容器";
|
|
}
|
|
|
|
std::string GetChineseExfilDisplayName(const CExfil& exfil)
|
|
{
|
|
return HasRuntimeDisplayName(exfil.m_pExfilName, "撤离点", "Exfil") ?
|
|
exfil.m_pExfilName : "撤离点";
|
|
}
|
|
|
|
ImColor GetExfilDisplayColor(const ExfilDisplayCategory category,
|
|
const ImColor& closedColor, const ImColor& openColor,
|
|
const ImColor& pendingColor, const ImColor& unknownColor)
|
|
{
|
|
switch (category)
|
|
{
|
|
case ExfilDisplayCategory::Closed: return closedColor;
|
|
case ExfilDisplayCategory::Open: return openColor;
|
|
case ExfilDisplayCategory::Pending: return pendingColor;
|
|
default: return unknownColor;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CFramework::RenderInfo()
|
|
{
|
|
if (!g.g_DebugMode)
|
|
return;
|
|
|
|
const auto snapshot = GetDynamicSnapshot();
|
|
const bool snapshotFresh = snapshot && IsSnapshotFresh(snapshot->capturedAt);
|
|
|
|
std::string infoText{ "Tarkov PvE | " + std::to_string((int)ImGui::GetIO().Framerate) + "FPS" };
|
|
StringEx(ImVec2(8.f, 8.f), ImColor(1.f, 1.f, 1.f, 1.f), ImGui::GetFontSize(), infoText.c_str());
|
|
|
|
const bool initialized = snapshot && snapshot->initialized;
|
|
const bool inRaid = snapshot && snapshot->inRaid;
|
|
const uintptr_t localAddress = snapshot ? snapshot->local.m_address : 0;
|
|
const std::size_t entityCount = snapshot ? snapshot->players.size() : 0;
|
|
const uint64_t ageMs = snapshot ? GetSnapshotAgeMs(snapshot->capturedAt) : kInvalidSnapshotAgeMs;
|
|
|
|
char ageText[32]{};
|
|
if (ageMs == kInvalidSnapshotAgeMs)
|
|
snprintf(ageText, sizeof(ageText), "N/A");
|
|
else
|
|
snprintf(ageText, sizeof(ageText), "%llums", (unsigned long long)ageMs);
|
|
|
|
char diag[320]{};
|
|
snprintf(diag, sizeof(diag),
|
|
"St:%s Init:%s Raid:%s Cam:%s Fresh:%s Age:%s Fail:%llu Try:%llu Gen:%llu",
|
|
snapshot ? GetRefreshStatusName(snapshot->refreshStatus) : "NONE",
|
|
initialized ? "OK" : "FAIL",
|
|
inRaid ? "Y" : "N",
|
|
(snapshot && snapshot->cameraValid) ? "OK" : "FAIL",
|
|
snapshotFresh ? "Y" : "N",
|
|
ageText,
|
|
(unsigned long long)(snapshot ? snapshot->consecutiveRefreshFailures : 0),
|
|
(unsigned long long)(snapshot ? snapshot->refreshAttempts : 0),
|
|
(unsigned long long)(snapshot ? snapshot->generation : 0));
|
|
StringEx(ImVec2(8.f, 24.f), ImColor(1.f, 1.f, 0.f, 1.f), ImGui::GetFontSize(), diag);
|
|
|
|
char pointers[320]{};
|
|
snprintf(pointers, sizeof(pointers),
|
|
"B:%llX GOM:%llX N:%llX GW:%llX LGW:%llX L:%llX E:%zu",
|
|
(unsigned long long)(snapshot ? snapshot->moduleBase : 0),
|
|
(unsigned long long)(snapshot ? snapshot->gameObjectManager : 0),
|
|
(unsigned long long)(snapshot ? snapshot->activeNodeList : 0),
|
|
(unsigned long long)(snapshot ? snapshot->gameWorld : 0),
|
|
(unsigned long long)(snapshot ? snapshot->localGameWorld : 0),
|
|
(unsigned long long)localAddress,
|
|
entityCount);
|
|
StringEx(ImVec2(8.f, 40.f), ImColor(0.3f, 0.9f, 1.f, 1.f), ImGui::GetFontSize(), pointers);
|
|
|
|
char aim[192]{};
|
|
snprintf(aim, sizeof(aim),
|
|
"Aim:%s M:%s F:%s Tgt:%s d=%.0fpx",
|
|
g.g_Aim ? "ON" : "OFF",
|
|
g.g_AimMethod == 0 ? "MEM" : "MOU",
|
|
g.g_AimForce ? "FORCE" : "KEY",
|
|
m_diagBestAddr ? "LOCK" : "NONE",
|
|
m_diagBestDist);
|
|
StringEx(ImVec2(8.f, 58.f), ImColor(1.f, 0.f, 0.f, 1.f), ImGui::GetFontSize(), aim);
|
|
|
|
if (g.g_Crosshair && snapshotFresh && snapshot->local.IsValid() && !snapshot->local.IsAiming())
|
|
{
|
|
switch (g.g_CrosshairType)
|
|
{
|
|
case 0:
|
|
{
|
|
ImVec2 center = ImVec2(g.GameRect.right / 2.f, g.GameRect.bottom / 2.f);
|
|
ImGui::GetBackgroundDrawList()->AddLine(ImVec2(center.x - g.g_CrosshairSize, center.y), ImVec2(center.x + g.g_CrosshairSize + 1.f, center.y), CrosshairColor, 1.f);
|
|
ImGui::GetBackgroundDrawList()->AddLine(ImVec2(center.x, center.y - g.g_CrosshairSize), ImVec2(center.x, center.y + g.g_CrosshairSize + 1.f), CrosshairColor, 1.f);
|
|
break;
|
|
}
|
|
case 1:
|
|
ImGui::GetBackgroundDrawList()->AddCircle(ImVec2((float)g.GameRect.right / 2.f, (float)g.GameRect.bottom / 2.f), g.g_CrosshairSize, CrosshairColor, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CFramework::RenderESP()
|
|
{
|
|
const auto snapshot = GetDynamicSnapshot();
|
|
if (!snapshot || !IsSnapshotFresh(snapshot->capturedAt) ||
|
|
!snapshot->inRaid || !snapshot->cameraValid || !snapshot->local.IsValid())
|
|
return;
|
|
|
|
const CPlayer& localPlayer = snapshot->local;
|
|
const Matrix& viewMatrix = snapshot->viewMatrix;
|
|
const Vector2 gameSize((float)g.GameRect.right, (float)g.GameRect.bottom);
|
|
|
|
// Rendering consumes an immutable snapshot: no player/camera RPM is performed in this loop.
|
|
for (const auto& entity : snapshot->players)
|
|
{
|
|
const CPlayer& player = entity;
|
|
if (!player.IsValid() || Vec3_Empty(player.m_vecOrigin))
|
|
continue;
|
|
|
|
const float distance = GetDistance(localPlayer.m_vecOrigin, player.m_vecOrigin);
|
|
|
|
Vector2 screen{}, head{}, neck{};
|
|
if (!WorldToScreen(viewMatrix, gameSize, player.m_vecOrigin, screen) || !InScreen(screen))
|
|
continue;
|
|
if (!WorldToScreen(viewMatrix, gameSize, player.m_vecHeadOrigin, head) ||
|
|
!WorldToScreen(viewMatrix, gameSize, player.m_vecNeckOrigin, neck))
|
|
continue;
|
|
|
|
const float headToNeck = neck.y - head.y;
|
|
const float height = screen.y - head.y + (headToNeck * 2.5f);
|
|
const float width = height / 3.5f;
|
|
if (height <= 0.f || width <= 0.f)
|
|
continue;
|
|
|
|
std::string name = "InValid";
|
|
ImColor color = ImColor(1.f, 1.f, 1.f, 1.f);
|
|
if (player.m_bSpawnTypeValid)
|
|
{
|
|
GetESPInfo(player.m_iSpawnType, name, color);
|
|
}
|
|
else if (player.IsObserved())
|
|
{
|
|
if (player.m_iSide == SIDE_SCAV)
|
|
{
|
|
name = "Observed SCAV";
|
|
color = Col_ESP_Scav;
|
|
}
|
|
else
|
|
{
|
|
name = "Observed PMC";
|
|
color = Col_ESP_PMC;
|
|
}
|
|
}
|
|
|
|
if (player.m_bSpawnTypeValid && player.m_iSpawnType == BTR_VEHICLE)
|
|
{
|
|
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(screen.x, screen.y - 2.f), 2.f, ImColor(1.f, 1.f, 1.f, 1.f), 0);
|
|
const std::string text = "BTR-82 [" + std::to_string((int)distance) + "m]";
|
|
String(ImVec2(screen.x - (ImGui::CalcTextSize(text.c_str()).x / 2.f), screen.y), ImColor(1.f, 1.f, 1.f, 1.f), text.c_str());
|
|
continue;
|
|
}
|
|
|
|
if (g.g_ESP_Line)
|
|
DrawLine(ImVec2(g.GameRect.right / 2.f, g.GameRect.bottom), ImVec2(screen.x, screen.y), color, 1.f);
|
|
|
|
if (g.g_ESP_Box)
|
|
{
|
|
static float boxScale = 1.75f;
|
|
if (g.g_ESP_BoxFilled)
|
|
ImGui::GetBackgroundDrawList()->AddRectFilled(ImVec2(screen.x - width, screen.y - height), ImVec2(screen.x + width, screen.y), Col_ESP_Filled, 1.f);
|
|
|
|
switch (g.g_ESP_BoxStyle)
|
|
{
|
|
case 0:
|
|
DrawLine(ImVec2(screen.x - width, screen.y - height), ImVec2(screen.x + width, screen.y - height), color, 1.f);
|
|
DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x - width, screen.y - height), color, 1.f);
|
|
DrawLine(ImVec2(screen.x + width, screen.y), ImVec2(screen.x + width, screen.y - height), color, 1.f);
|
|
DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x + width, screen.y), color, 1.f);
|
|
break;
|
|
case 1:
|
|
DrawLine(ImVec2(screen.x - width, screen.y - height), ImVec2(screen.x - width + (width / boxScale), screen.y - height), color, 1.f);
|
|
DrawLine(ImVec2(screen.x + width, screen.y - height), ImVec2(screen.x + width - (width / boxScale), screen.y - height), color, 1.f);
|
|
DrawLine(ImVec2(screen.x - width, screen.y - height), ImVec2(screen.x - width, screen.y - height + (width / boxScale)), color, 1.f);
|
|
DrawLine(ImVec2(screen.x + width, screen.y - height), ImVec2(screen.x + width, screen.y - height + (width / boxScale)), color, 1.f);
|
|
DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x - width, screen.y - (width / boxScale)), color, 1.f);
|
|
DrawLine(ImVec2(screen.x + width, screen.y), ImVec2(screen.x + width, screen.y - (width / boxScale)), color, 1.f);
|
|
DrawLine(ImVec2(screen.x - width, screen.y), ImVec2(screen.x - width + (width / boxScale), screen.y), color, 1.f);
|
|
DrawLine(ImVec2(screen.x + width, screen.y), ImVec2(screen.x + width - (width / boxScale), screen.y), color, 1.f);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (g.g_ESP_Skeleton && distance < 250.f && player.m_vecBoneList.size() >= 9)
|
|
{
|
|
ImGui::GetBackgroundDrawList()->AddCircle(ImVec2(head.x, head.y - (headToNeck / 2.f)), headToNeck * 1.55f, color, 0);
|
|
|
|
const Vector3 bones[][2]{
|
|
{ player.m_vecNeckOrigin, player.m_vecBoneList[CPelvis] },
|
|
{ player.m_vecNeckOrigin, player.m_vecBoneList[CLeftForearm] },
|
|
{ player.m_vecBoneList[CLeftForearm], player.m_vecBoneList[CLeftPalm] },
|
|
{ player.m_vecNeckOrigin, player.m_vecBoneList[CRightForearm] },
|
|
{ player.m_vecBoneList[CRightForearm], player.m_vecBoneList[CRightPalm] },
|
|
{ player.m_vecBoneList[CPelvis], player.m_vecBoneList[CLeftThigh] },
|
|
{ player.m_vecBoneList[CLeftThigh], player.m_vecBoneList[CLeftFoot] },
|
|
{ player.m_vecBoneList[CPelvis], player.m_vecBoneList[CRightThigh] },
|
|
{ player.m_vecBoneList[CRightThigh], player.m_vecBoneList[CRightFoot] }
|
|
};
|
|
|
|
for (const auto& pair : bones)
|
|
{
|
|
Vector2 a{}, b{};
|
|
if (!WorldToScreen(viewMatrix, gameSize, pair[0], a) || !WorldToScreen(viewMatrix, gameSize, pair[1], b))
|
|
continue;
|
|
DrawLine(ImVec2(a.x, a.y), ImVec2(b.x, b.y), color, 1.f);
|
|
}
|
|
}
|
|
|
|
if (g.g_ESP_Distance)
|
|
{
|
|
const std::string distanceText = std::to_string((int)distance) + "m";
|
|
String(ImVec2(screen.x - (ImGui::CalcTextSize(distanceText.c_str()).x / 2.f), screen.y), ImColor(1.f, 1.f, 1.f, 1.f), distanceText.c_str());
|
|
}
|
|
|
|
if (g.g_ESP_HealthBar && player.m_bHealthValid)
|
|
HealthBar(screen.x - width - 4.f, screen.y, 2.f, -height, (int)player.m_fHealth, (int)player.m_fHealthMax);
|
|
|
|
if (g.g_ESP_Name && (!player.m_bSpawnTypeValid ||
|
|
(player.m_iSpawnType != SIDE_SCAV && player.m_iSpawnType != SNIPER_SCAV && player.m_iSpawnType != NORMAL_SCAV)))
|
|
{
|
|
if (name == "InValid" && player.m_bSpawnTypeValid)
|
|
name += "[" + std::to_string(player.m_iSpawnType) + "]";
|
|
String(ImVec2(screen.x - (ImGui::CalcTextSize(name.c_str()).x / 2.f), screen.y - height - 14.f), ImColor(1.f, 1.f, 1.f, 1.f), name.c_str());
|
|
}
|
|
}
|
|
|
|
if (g.g_ESP_Grenade)
|
|
{
|
|
for (const auto& grenade : snapshot->grenades)
|
|
{
|
|
const float distance = GetDistance(localPlayer.m_vecOrigin, grenade.position);
|
|
if (distance > 100.f)
|
|
continue;
|
|
if (distance < 10.f)
|
|
String(ImVec2(g.GameRect.right / 2.f - (ImGui::CalcTextSize("[ WARNING ] Grenade!!").x / 2.f), g.GameRect.bottom / 2.f), ImColor(1.f, 0.f, 0.f, 1.f), "[ WARNING ] Grenade!!");
|
|
|
|
Vector2 screen{};
|
|
if (!WorldToScreen(viewMatrix, gameSize, grenade.position, screen))
|
|
continue;
|
|
|
|
const std::string text = "Grenade [" + std::to_string((int)distance) + "m]";
|
|
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(screen.x, screen.y), 2.f, ImColor(1.f, 0.f, 0.f, 1.f), 0);
|
|
String(ImVec2(screen.x - (ImGui::CalcTextSize(text.c_str()).x / 2.f), screen.y - 13.f), ImColor(1.f, 0.f, 0.f, 1.f), text.c_str());
|
|
}
|
|
}
|
|
|
|
const auto staticSnapshot = GetStaticSnapshot();
|
|
if (!staticSnapshot)
|
|
return;
|
|
|
|
if (g.g_ESP_Item)
|
|
{
|
|
for (const auto& item : staticSnapshot->items)
|
|
{
|
|
if (item.m_CName.empty())
|
|
continue;
|
|
if (!g.g_ESP_Corpse && item.m_bIsCorpse)
|
|
continue;
|
|
|
|
const float distance = GetDistance(localPlayer.m_vecOrigin, item.m_vecOrigin);
|
|
if (distance > g.g_ESP_MaxItemDistance)
|
|
continue;
|
|
|
|
Vector2 screen{};
|
|
if (!WorldToScreen(viewMatrix, gameSize, item.m_vecOrigin, screen) || !InScreen(screen))
|
|
continue;
|
|
|
|
std::string text = GetChineseItemDisplayName(item);
|
|
text += " [" + std::to_string((int)distance) + "m]";
|
|
const ImColor itemColor = GetItemDisplayColor(item, Col_ESP_ItemQuest, Col_ESP_ItemCommon,
|
|
Col_ESP_ItemRare, Col_ESP_ItemSuperRare, Col_ESP_Corpse);
|
|
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(screen.x, screen.y - 2.f), 2.f, itemColor, 0);
|
|
String(ImVec2(screen.x - (ImGui::CalcTextSize(text.c_str()).x / 2.f), screen.y), itemColor, text.c_str());
|
|
}
|
|
}
|
|
|
|
if (g.g_ESP_Container)
|
|
{
|
|
for (const auto& container : staticSnapshot->containers)
|
|
{
|
|
const float distance = GetDistance(localPlayer.m_vecOrigin, container.m_vecOrigin);
|
|
if (distance > g.g_ESP_MaxContainerDistance)
|
|
continue;
|
|
|
|
Vector2 screen{};
|
|
if (!WorldToScreen(viewMatrix, gameSize, container.m_vecOrigin, screen) || !InScreen(screen))
|
|
continue;
|
|
|
|
const std::string text = GetChineseContainerDisplayName(container) + " [" + std::to_string((int)distance) + "m]";
|
|
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(screen.x, screen.y - 2.f), 2.f, Col_ESP_Container, 0);
|
|
String(ImVec2(screen.x - (ImGui::CalcTextSize(text.c_str()).x / 2.f), screen.y), Col_ESP_Container, text.c_str());
|
|
}
|
|
}
|
|
|
|
if (g.g_ESP_Exfil)
|
|
{
|
|
for (const auto& exfil : staticSnapshot->exfils)
|
|
{
|
|
Vector2 screen{};
|
|
if (!WorldToScreen(viewMatrix, gameSize, exfil.m_pVecLocation, screen) || !InScreen(screen))
|
|
continue;
|
|
|
|
const ExfilDisplayCategory category = ClassifyExfilStatus(exfil.m_pExfilStatus);
|
|
const ImColor exfilColor = GetExfilDisplayColor(category, Col_ESP_ExfilClose,
|
|
Col_ESP_ExfilOpen, Col_ESP_ExfilPending, Col_ESP_ExfilUnknown);
|
|
const float distance = GetDistance(localPlayer.m_vecOrigin, exfil.m_pVecLocation);
|
|
const std::string text = GetChineseExfilDisplayName(exfil) + " [" + GetExfilStatusText(exfil.m_pExfilStatus) + "] [" +
|
|
std::to_string((int)distance) + "m]";
|
|
ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(screen.x, screen.y - 2.f), 2.f, exfilColor, 0);
|
|
String(ImVec2(screen.x - (ImGui::CalcTextSize(text.c_str()).x / 2.f), screen.y), exfilColor, text.c_str());
|
|
}
|
|
}
|
|
}
|