238 lines
8.2 KiB
C++
238 lines
8.2 KiB
C++
#include "CPlayer.h"
|
|
|
|
std::vector<int> PlayerBoneList{ BONE_PELVIS, BONE_LEFT_THIGH, BONE_LEFT_FOOT, BONE_RIGHT_THIGH, BONE_RIGHT_FOOT, BONE_LEFT_FOREARM, BONE_LEFT_PALM, BONE_RIGHT_FOREARM, BONE_RIGHT_PALM };
|
|
|
|
namespace
|
|
{
|
|
uintptr_t ReadBifacialOriginal(uintptr_t playerBones, uintptr_t fieldOffset)
|
|
{
|
|
const uintptr_t bifacialTransform = m.Read<uintptr_t>(playerBones + fieldOffset);
|
|
return bifacialTransform
|
|
? m.Read<uintptr_t>(bifacialTransform + offset::BifacialTransform::Original)
|
|
: 0;
|
|
}
|
|
|
|
uintptr_t ReadTransformArrayElement(uintptr_t array, int index)
|
|
{
|
|
if (!array || index < 0 || index > 1)
|
|
return 0;
|
|
|
|
return m.Read<uintptr_t>(array + offset::PlayerBones::TransformArrayData + static_cast<uintptr_t>(index) * sizeof(uintptr_t));
|
|
}
|
|
|
|
Vector3 ReadTransformPositionWithFallback(uintptr_t transform)
|
|
{
|
|
if (!transform)
|
|
return {};
|
|
|
|
Vector3 position = GetTransformPosition(transform);
|
|
if (!Vec3_Empty(position))
|
|
return position;
|
|
|
|
// Keep compatibility with the previous Transform wrapper layout.
|
|
uintptr_t wrappedTransform = m.Read<uintptr_t>(transform + 0x10);
|
|
if (!wrappedTransform || wrappedTransform == transform)
|
|
return {};
|
|
|
|
return GetTransformPosition(wrappedTransform);
|
|
}
|
|
}
|
|
|
|
bool CPlayer::Update()
|
|
{
|
|
m_vecOrigin = GetBonePosition(BONE_ORIGIN);
|
|
|
|
return !Vec3_Empty(m_vecOrigin);
|
|
}
|
|
|
|
void CPlayer::UpdateStatic()
|
|
{
|
|
m_pHealthController = 0;
|
|
m_pProfile = 0;
|
|
m_pInfo = 0;
|
|
m_pSetting = 0;
|
|
m_pPlayerBody = 0;
|
|
m_pPlayerBones = 0;
|
|
m_pWeaponAnimation = 0;
|
|
m_iSpawnType = 0;
|
|
m_pBoneArray = {};
|
|
m_vecBoneList.clear();
|
|
|
|
if (!m_address)
|
|
return;
|
|
|
|
// EFT.Player fields from dump.txt.
|
|
m_pHealthController = m.Read<uintptr_t>(m_address + offset::Player::pHealthController);
|
|
m_pWeaponAnimation = m.Read<uintptr_t>(m_address + offset::Player::pWeaponAnimation);
|
|
|
|
m_pPlayerBody = m.Read<uintptr_t>(m_address + offset::Player::pPlayerBody);
|
|
if (m_pPlayerBody)
|
|
m_pPlayerBones = m.Read<uintptr_t>(m_pPlayerBody + offset::PlayerBody::PlayerBones);
|
|
|
|
m_pProfile = m.Read<uintptr_t>(m_address + offset::Player::pProfile);
|
|
if (m_pProfile)
|
|
{
|
|
m_pInfo = m.Read<uintptr_t>(m_pProfile + offset::Profile::pInfoClass);
|
|
if (m_pInfo)
|
|
{
|
|
m_pSetting = m.Read<uintptr_t>(m_pInfo + offset::InfoClass::profileInfoSettingsClass);
|
|
if (m_pSetting)
|
|
m_iSpawnType = m.Read<int>(m_pSetting + 0x10); // ProfileInfoSettingsClass.Role
|
|
}
|
|
}
|
|
|
|
// PlayerBody +0x30 = SkeletonRootJoint; Skeleton +0x30 = values List;
|
|
// List +0x10 = Transform[] data pointer, as described by dump.txt.
|
|
const uintptr_t boneMatrix = m.ReadChain(m_address, { offset::Player::pPlayerBody, offset::PlayerBody::SkeletonRootJoint, 0x30, 0x10 });
|
|
if (boneMatrix)
|
|
m_pBoneArray = m.Read<CBoneArray>(boneMatrix + 0x20);
|
|
}
|
|
|
|
void CPlayer::UpdateHealth()
|
|
{
|
|
m_fHealth = 0.f;
|
|
m_fHealthMax = 0.f;
|
|
|
|
if (!m_address)
|
|
return;
|
|
|
|
const uintptr_t healthController = m_pHealthController != 0
|
|
? m_pHealthController
|
|
: m.Read<uintptr_t>(m_address + offset::Player::pHealthController);
|
|
if (!healthController)
|
|
return;
|
|
|
|
// GClass3009<T>.Dictionary_0_1 contains BodyPartState<T> entries.
|
|
const uintptr_t bodyParts = m.Read<uintptr_t>(healthController + offset::HealthController::BodyParts);
|
|
if (!bodyParts)
|
|
return;
|
|
|
|
const uintptr_t entries = m.Read<uintptr_t>(bodyParts + offset::HealthController::DictionaryEntries);
|
|
const int entryCount = m.Read<int>(bodyParts + offset::HealthController::DictionaryCount);
|
|
if (!entries || entryCount <= 0 || entryCount > 64)
|
|
return;
|
|
|
|
for (int i = 0; i < entryCount; ++i)
|
|
{
|
|
const uintptr_t entry = entries + offset::HealthController::ArrayData
|
|
+ static_cast<uintptr_t>(i) * offset::HealthController::EntryStride;
|
|
const uintptr_t bodyPartState = m.Read<uintptr_t>(entry + offset::HealthController::EntryValue);
|
|
if (!bodyPartState)
|
|
continue;
|
|
|
|
const uintptr_t healthValue = m.Read<uintptr_t>(bodyPartState + offset::HealthController::BodyPartHealth);
|
|
if (!healthValue)
|
|
continue;
|
|
|
|
m_fHealth += m.Read<float>(healthValue + offset::HealthController::HealthValue + offset::HealthController::Current);
|
|
m_fHealthMax += m.Read<float>(healthValue + offset::HealthController::HealthValue + offset::HealthController::Maximum);
|
|
}
|
|
}
|
|
|
|
void CPlayer::UpdateBone()
|
|
{
|
|
// Refresh the direct PlayerBody -> PlayerBones link before reading the selected aim bone.
|
|
// The list thread is intentionally slow, while animation/weapon objects can be replaced sooner.
|
|
if (m_address)
|
|
{
|
|
uintptr_t playerBody = 0;
|
|
uintptr_t playerBones = 0;
|
|
if (m.TryRead(m_address + offset::Player::pPlayerBody, playerBody) && playerBody &&
|
|
m.TryRead(playerBody + offset::PlayerBody::PlayerBones, playerBones) && playerBones)
|
|
{
|
|
m_pPlayerBody = playerBody;
|
|
m_pPlayerBones = playerBones;
|
|
}
|
|
}
|
|
|
|
m_vecHeadOrigin = GetBonePosition(BONE_HEAD);
|
|
m_vecNeckOrigin = GetBonePosition(BONE_NECK);
|
|
m_vecBoneList.clear();
|
|
|
|
if (!g.g_ESP_Skeleton)
|
|
return;
|
|
|
|
m_vecBoneList.reserve(PlayerBoneList.size());
|
|
for (const auto id : PlayerBoneList)
|
|
{
|
|
const Vector3 bone = GetBonePosition(id);
|
|
if (Vec3_Empty(bone))
|
|
{
|
|
m_vecBoneList.clear();
|
|
return;
|
|
}
|
|
|
|
m_vecBoneList.push_back(bone);
|
|
}
|
|
}
|
|
|
|
bool CPlayer::IsAiming()
|
|
{
|
|
return m_pWeaponAnimation != 0 && m.Read<bool>(m_pWeaponAnimation + offset::WeaponAnimations::isAiming);
|
|
}
|
|
|
|
Vector3 CPlayer::GetBonePosition(const int BoneId)
|
|
{
|
|
constexpr int kBoneCount = static_cast<int>(sizeof(m_pBoneArray.address) / sizeof(m_pBoneArray.address[0]));
|
|
if (BoneId < 0 || BoneId >= kBoneCount)
|
|
return {};
|
|
|
|
uintptr_t transform = 0;
|
|
if (m_pPlayerBones)
|
|
{
|
|
switch (BoneId)
|
|
{
|
|
case BONE_ORIGIN:
|
|
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::RootJoint);
|
|
break;
|
|
case BONE_NECK:
|
|
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::Neck);
|
|
break;
|
|
case BONE_HEAD:
|
|
transform = ReadBifacialOriginal(m_pPlayerBones, offset::PlayerBones::Head);
|
|
break;
|
|
case BONE_PELVIS:
|
|
transform = ReadBifacialOriginal(m_pPlayerBones, offset::PlayerBones::Pelvis);
|
|
break;
|
|
case BONE_LEFT_THIGH:
|
|
transform = ReadBifacialOriginal(m_pPlayerBones, offset::PlayerBones::LeftThigh);
|
|
break;
|
|
case BONE_RIGHT_THIGH:
|
|
transform = ReadBifacialOriginal(m_pPlayerBones, offset::PlayerBones::RightThigh);
|
|
break;
|
|
case BONE_LEFT_FOREARM:
|
|
transform = ReadTransformArrayElement(m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::Forearms), 0);
|
|
break;
|
|
case BONE_RIGHT_FOREARM:
|
|
transform = ReadTransformArrayElement(m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::Forearms), 1);
|
|
break;
|
|
case BONE_LEFT_PALM:
|
|
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::LeftPalm);
|
|
break;
|
|
case BONE_RIGHT_PALM:
|
|
transform = m.Read<uintptr_t>(m_pPlayerBones + offset::PlayerBones::RightPalm);
|
|
break;
|
|
case BONE_SPINE_3:
|
|
transform = ReadBifacialOriginal(m_pPlayerBones, offset::PlayerBones::Ribcage);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Feet and any unmapped legacy IDs fall back to the current Skeleton.values array.
|
|
// The array elements are Transform references; do not dereference +0x10 here.
|
|
if (!transform)
|
|
transform = m_pBoneArray.address[BoneId];
|
|
|
|
const Vector3 position = ReadTransformPositionWithFallback(transform);
|
|
if (!Vec3_Empty(position))
|
|
return position;
|
|
|
|
const uintptr_t legacyTransform = m_pBoneArray.address[BoneId];
|
|
if (!legacyTransform || legacyTransform == transform)
|
|
return {};
|
|
|
|
return ReadTransformPositionWithFallback(legacyTransform);
|
|
}
|