Files
EGameTools/EGameSDK/src/GamePH/PlayerVariables.cpp

589 lines
21 KiB
C++
Raw Normal View History

#include <Windows.h>
#include <algorithm>
#include <spdlog\spdlog.h>
#include <EGSDK\Offsets.h>
#include <EGSDK\GamePH\PlayerState.h>
#include <EGSDK\GamePH\PlayerVariables.h>
#include <EGSDK\ClassHelpers.h>
namespace EGSDK::GamePH {
static constexpr int STRING_SIZE_OFFSET = 3;
static constexpr int FLOAT_SIZE_OFFSET = 3;
static constexpr int BOOL_SIZE_OFFSET = 2;
std::unordered_map<PlayerVariable*, std::string> PlayerVariable::playerVarNames{};
std::unordered_map<PlayerVariable*, PlayerVarType> PlayerVariable::playerVarTypes{};
PlayerVariable::PlayerVariable(const std::string& name) {
playerVarNames[this] = name;
playerVarTypes[this] = PlayerVarType::NONE;
}
const char* PlayerVariable::GetName() {
auto it = playerVarNames.find(this);
if (it != playerVarNames.end()) {
return it->second.c_str();
}
return nullptr;
}
void PlayerVariable::SetName(const std::string& newName) {
playerVarNames[this] = newName;
}
PlayerVarType PlayerVariable::GetType() {
auto it = playerVarTypes.find(this);
if (it != playerVarTypes.end()) {
return it->second;
}
return PlayerVarType::NONE;
}
void PlayerVariable::SetType(PlayerVarType newType) {
playerVarTypes[this] = newType;
}
StringPlayerVariable::StringPlayerVariable(const std::string& name) : PlayerVariable(name) {
SetType(PlayerVarType::String);
}
FloatPlayerVariable::FloatPlayerVariable(const std::string& name) : PlayerVariable(name) {
SetType(PlayerVarType::Float);
}
2025-01-07 00:28:09 +02:00
void FloatPlayerVariable::SetValues(float value) {
this->value = value;
this->defaultValue = value;
}
BoolPlayerVariable::BoolPlayerVariable(const std::string& name) : PlayerVariable(name) {
SetType(PlayerVarType::Bool);
}
2025-01-07 00:28:09 +02:00
void BoolPlayerVariable::SetValues(bool value) {
this->value = value;
this->defaultValue = value;
}
2025-01-07 04:42:06 +02:00
std::unique_ptr<PlayerVariable>& PlayerVarVector::emplace_back(std::unique_ptr<PlayerVariable> playerVar) {
std::lock_guard<std::mutex> lock(_mutex);
_playerVars.emplace_back(std::move(playerVar));
return _playerVars.back();
}
auto PlayerVarVector::begin() {
std::lock_guard<std::mutex> lock(_mutex);
return _playerVars.begin();
}
auto PlayerVarVector::end() {
std::lock_guard<std::mutex> lock(_mutex);
return _playerVars.end();
}
bool PlayerVarVector::none_of(const std::string& name) {
std::lock_guard<std::mutex> lock(_mutex);
return std::none_of(_playerVars.begin(), _playerVars.end(), [&name](const auto& playerVar) {
return playerVar->GetName() == name;
});
}
auto PlayerVarVector::FindIter(const std::string& name) {
std::lock_guard<std::mutex> lock(_mutex);
auto playerVarIt = std::find_if(_playerVars.begin(), _playerVars.end(), [&name](const auto& playerVar) {
return playerVar->GetName() == name;
});
return playerVarIt;
}
std::unique_ptr<PlayerVariable>* PlayerVarVector::FindPtr(const std::string& name) {
std::lock_guard<std::mutex> lock(_mutex);
auto playerVarIt = FindIter(name);
return playerVarIt == _playerVars.end() ? nullptr : &*playerVarIt;
}
PlayerVariable* PlayerVarVector::Find(const std::string& name) {
std::lock_guard<std::mutex> lock(_mutex);
auto playerVarPtr = FindPtr(name);
return !playerVarPtr ? nullptr : playerVarPtr->get();
}
auto PlayerVarVector::Erase(const std::string& name) {
std::lock_guard<std::mutex> lock(_mutex);
auto playerVarIt = FindIter(name);
if (playerVarIt != _playerVars.end())
return _playerVars.erase(playerVarIt);
return _playerVars.end();
}
2025-01-07 00:28:09 +02:00
PlayerVarVector PlayerVariables::playerVars{};
PlayerVarVector PlayerVariables::customPlayerVars{};
PlayerVarVector PlayerVariables::defaultPlayerVars{};
PlayerVarVector PlayerVariables::customDefaultPlayerVars{};
bool PlayerVariables::gotPlayerVars = false;
static bool sortedPlayerVars = false;
std::unordered_map<std::string, std::any> PlayerVariables::prevPlayerVarValueMap{};
std::unordered_map<std::string, bool> PlayerVariables::prevBoolValueMap{};
template <typename T>
2025-01-07 00:28:09 +02:00
static void updateDefaultVar(PlayerVarVector& defaultVars, const std::string& name, T value, T defaultValue) {
static_assert(std::is_same_v<T, std::string> || std::is_same_v<T, float> || std::is_same_v<T, bool>, "Invalid type: value must be string, float or bool");
2025-01-07 00:28:09 +02:00
auto playerVar = defaultVars.Find(name);
if (!playerVar) {
if constexpr (std::is_same_v<T, std::string>) {
auto stringPlayerVar = std::make_unique<StringPlayerVariable>(name);
defaultVars.emplace_back(std::move(stringPlayerVar));
}
else if constexpr (std::is_same_v<T, float>) {
auto floatPlayerVar = std::make_unique<FloatPlayerVariable>(name);
2025-01-07 00:28:09 +02:00
floatPlayerVar->SetValues(value);
defaultVars.emplace_back(std::move(floatPlayerVar));
}
else if constexpr (std::is_same_v<T, bool>) {
auto boolPlayerVar = std::make_unique<BoolPlayerVariable>(name);
2025-01-07 00:28:09 +02:00
boolPlayerVar->SetValues(value);
defaultVars.emplace_back(std::move(boolPlayerVar));
}
} else {
if constexpr (std::is_same_v<T, std::string>) {
// TO IMPLEMENT
return;
} else if constexpr (std::is_same_v<T, float>) {
2025-01-07 00:28:09 +02:00
auto floatPlayerVar = reinterpret_cast<FloatPlayerVariable*>(playerVar);
floatPlayerVar->SetValues(value);
} else if constexpr (std::is_same_v<T, bool>) {
auto boolPlayerVar = reinterpret_cast<BoolPlayerVariable*>(playerVar);
2025-01-07 00:28:09 +02:00
boolPlayerVar->SetValues(value);
}
}
}
2025-01-07 00:28:09 +02:00
static void processPlayerVar(DWORD64*(*playerVarsGetter)(), std::unique_ptr<PlayerVariable>& playerVarPtr) {
static int offset = 0;
int offsetDif = 0;
2025-01-04 04:53:31 +02:00
while (true) {
std::string vTableName = Utils::RTTI::GetVTableName(playerVarsGetter() + offset);
if (vTableName != "StringPlayerVariable" && vTableName != "FloatPlayerVariable" && vTableName != "BoolPlayerVariable") {
if (offsetDif > 150)
return;
2025-01-04 04:53:31 +02:00
offset += 1;
offsetDif += 1;
continue;
}
2025-01-07 00:28:09 +02:00
std::string varName = playerVarPtr->GetName();
PlayerVarType varType = playerVarPtr->GetType();
2025-01-07 00:28:09 +02:00
switch (playerVarPtr->GetType()) {
case PlayerVarType::String:
{
if (vTableName != "StringPlayerVariable")
return;
StringPlayerVariable* stringPlayerVar = reinterpret_cast<StringPlayerVariable*>(playerVarsGetter() + offset);
2025-01-07 00:28:09 +02:00
playerVarPtr.reset(stringPlayerVar);
playerVarPtr->SetName(varName);
playerVarPtr->SetType(varType);
// TO IMPLEMENT
offset += STRING_SIZE_OFFSET;
return;
}
2025-01-07 00:28:09 +02:00
case PlayerVarType::Float:
{
if (vTableName != "FloatPlayerVariable")
return;
FloatPlayerVariable* floatPlayerVar = reinterpret_cast<FloatPlayerVariable*>(playerVarsGetter() + offset);
2025-01-07 00:28:09 +02:00
playerVarPtr.reset(floatPlayerVar);
playerVarPtr->SetName(varName);
playerVarPtr->SetType(varType);
updateDefaultVar(PlayerVariables::customDefaultPlayerVars, varName, floatPlayerVar->value.data, floatPlayerVar->defaultValue.data);
offset += FLOAT_SIZE_OFFSET;
return;
}
2025-01-07 00:28:09 +02:00
case PlayerVarType::Bool:
{
if (vTableName != "BoolPlayerVariable")
return;
BoolPlayerVariable* boolPlayerVar = reinterpret_cast<BoolPlayerVariable*>(playerVarsGetter() + offset);
2025-01-07 00:28:09 +02:00
playerVarPtr.reset(boolPlayerVar);
playerVarPtr->SetName(varName);
playerVarPtr->SetType(varType);
updateDefaultVar(PlayerVariables::customDefaultPlayerVars, varName, boolPlayerVar->value.data, boolPlayerVar->defaultValue.data);
offset += BOOL_SIZE_OFFSET;
return;
}
default:
offset += 1;
return;
}
}
}
static void processPlayerVarSafe(DWORD64*(*playerVarsGetter)(), std::unique_ptr<PlayerVariable>& playerVarPtr) {
__try {
processPlayerVar(playerVarsGetter, playerVarPtr);
} __except (EXCEPTION_EXECUTE_HANDLER) {
SPDLOG_ERROR("Failed to process player variable: {}", playerVarPtr->GetName());
}
}
void PlayerVariables::GetPlayerVars() {
if (gotPlayerVars)
return;
if (!sortedPlayerVars)
return;
if (!Get())
return;
for (auto& playerVarPtr : playerVars)
processPlayerVarSafe(reinterpret_cast<DWORD64*(*)()>(&Get), playerVarPtr);
gotPlayerVars = true;
}
#pragma region Player Variables Sorting
struct VarTypeFieldMeta {
PlayerVarType type;
std::string_view className;
};
const std::vector<VarTypeFieldMeta> varTypeFields = {
{ PlayerVarType::String, "constds::FieldsCollection<PlayerVariables>::TypedFieldMeta<StringPlayerVariable>" },
{ PlayerVarType::Float, "constds::FieldsCollection<PlayerVariables>::TypedFieldMeta<FloatPlayerVariable>" },
{ PlayerVarType::Bool, "constds::FieldsCollection<PlayerVariables>::TypedFieldMeta<BoolPlayerVariable>" }
};
2025-01-04 04:53:31 +02:00
static bool isRetInstruction(BYTE* address) {
//return address[0] == 0xC3 && address[1] == 0xCC;
return address[0] == 0x00 && address[1] == 0x00 && address[2] == 0xC3 && address[3] == 0xCC;
}
2025-01-04 04:53:31 +02:00
static bool isLeaInstruction(BYTE* address, BYTE REX, BYTE ModRM) {
return address[0] == REX && address[1] == 0x8D && address[2] == ModRM;
}
2025-01-04 04:53:31 +02:00
static bool isCallInstruction(BYTE* address) {
return address[0] == 0xE8 && address[4] != 0xE8;
}
2025-01-04 04:53:31 +02:00
static bool isBelowFuncSizeLimit(BYTE* address, DWORD64 startOfFunc, size_t sizeLimit) {
return (reinterpret_cast<DWORD64>(address) - startOfFunc) < sizeLimit;
}
// to prevent infinite loops, assuming function is no longer than 500000 bytes LMAO Techland... why is your function even like 250000 bytes to begin with? bad code...
static const size_t MAX_FUNC_SIZE = 500000;
static const size_t MAX_LOAD_VAR_FUNC_SIZE = 2000;
static const char* getPlayerVarName(BYTE*& funcAddress, DWORD64 startOfFunc) {
const char* playerVarName = nullptr;
while (!playerVarName && !isRetInstruction(funcAddress) && isBelowFuncSizeLimit(funcAddress, startOfFunc, MAX_FUNC_SIZE)) {
// lea r8, varNameString
if (!isLeaInstruction(funcAddress, 0x4C, 0x05)) {
funcAddress++;
continue;
}
playerVarName = reinterpret_cast<const char*>(Utils::Memory::CalcTargetAddrOfRelativeInstr(reinterpret_cast<DWORD64>(funcAddress), 3));
if (!playerVarName) {
funcAddress++;
continue;
}
// add the size of the instruction, so we skip this instruction because this instruction is the name
funcAddress += 0x7;
}
return playerVarName;
}
static PlayerVarType getPlayerVarType(BYTE*& funcAddress, DWORD64 startOfFunc) {
PlayerVarType playerVarType = PlayerVarType::NONE;
while (!playerVarType && !isRetInstruction(funcAddress) && isBelowFuncSizeLimit(funcAddress, startOfFunc, MAX_FUNC_SIZE)) {
// call LoadPlayerXVariable
if (!isCallInstruction(funcAddress)) {
funcAddress++;
continue;
}
DWORD64 startOfLoadVarFunc = Utils::Memory::CalcTargetAddrOfRelativeInstr(reinterpret_cast<DWORD64>(funcAddress), 1);
BYTE* loadVarFuncAddress = reinterpret_cast<BYTE*>(startOfLoadVarFunc);
DWORD64 metaVTAddrFromFunc = 0;
while (!metaVTAddrFromFunc && !isRetInstruction(loadVarFuncAddress) && isBelowFuncSizeLimit(loadVarFuncAddress, startOfLoadVarFunc, MAX_LOAD_VAR_FUNC_SIZE)) {
// lea rax, typedFieldMetaVT
if (!isLeaInstruction(loadVarFuncAddress, 0x48, 0x05)) {
loadVarFuncAddress++;
continue;
}
metaVTAddrFromFunc = Utils::Memory::CalcTargetAddrOfRelativeInstr(reinterpret_cast<DWORD64>(loadVarFuncAddress), 3);
std::string vTableName = Utils::RTTI::GetVTableNameFromVTPtr(reinterpret_cast<DWORD64*>(metaVTAddrFromFunc));
auto varTypeIt = std::find_if(varTypeFields.begin(), varTypeFields.end(), [&vTableName](const auto& varType) {
return varType.className == vTableName;
});
if (varTypeIt == varTypeFields.end()) {
metaVTAddrFromFunc = 0;
loadVarFuncAddress++;
continue;
}
playerVarType = varTypeIt->type;
break;
}
// if it's still NONE after seeing the function doesnt reference any of the variables, break so the loop stops
if (playerVarType == PlayerVarType::NONE)
break;
}
return playerVarType;
}
bool PlayerVariables::SortPlayerVars() {
DWORD64 startOfFunc = 0;
while (!startOfFunc)
startOfFunc = reinterpret_cast<DWORD64>(Offsets::Get_LoadPlayerVars());
BYTE* funcAddress = reinterpret_cast<BYTE*>(startOfFunc);
while (!isRetInstruction(funcAddress) && (reinterpret_cast<DWORD64>(funcAddress) - startOfFunc) < MAX_FUNC_SIZE) {
const char* playerVarName = getPlayerVarName(funcAddress, startOfFunc);
if (!playerVarName)
continue;
PlayerVarType playerVarType = getPlayerVarType(funcAddress, startOfFunc);
switch (playerVarType) {
case PlayerVarType::String:
playerVars.emplace_back(std::make_unique<StringPlayerVariable>(playerVarName));
break;
case PlayerVarType::Float:
playerVars.emplace_back(std::make_unique<FloatPlayerVariable>(playerVarName));
break;
case PlayerVarType::Bool:
playerVars.emplace_back(std::make_unique<BoolPlayerVariable>(playerVarName));
break;
default:
//playerVars.emplace_back(std::make_unique<PlayerVariable>(playerVarName));
break;
}
}
sortedPlayerVars = true;
return true;
}
#pragma endregion
2025-01-07 04:42:06 +02:00
template <typename T>
static T getDefaultValue() {
static_assert(std::is_same_v<T, std::string> || std::is_same_v<T, float> || std::is_same_v<T, bool>, "Invalid type: value must be string, float or bool");
if constexpr (std::is_same_v<T, std::string>)
return {};
else if constexpr (std::is_same_v<T, float>)
return -404.0f;
else if constexpr (std::is_same_v<T, bool>)
return false;
else
return T();
}
template <typename T>
static T GetPlayerVarValue(const std::string& name) {
static_assert(std::is_same_v<T, std::string> || std::is_same_v<T, float> || std::is_same_v<T, bool>, "Invalid type: value must be string, float or bool");
if (!gotPlayerVars)
return getDefaultValue<T>();
auto playerVar = playerVars.Find(name);
if (!playerVar)
return getDefaultValue<T>();
if constexpr (std::is_same_v<T, std::string>) {
StringPlayerVariable* stringPlayerVar = reinterpret_cast<StringPlayerVariable*>(playerVar);
return stringPlayerVar->value.data;
} else if constexpr (std::is_same_v<T, float>) {
FloatPlayerVariable* floatPlayerVar = reinterpret_cast<FloatPlayerVariable*>(playerVar);
return floatPlayerVar->value.data;
} else if constexpr (std::is_same_v<T, bool>) {
BoolPlayerVariable* boolPlayerVar = reinterpret_cast<BoolPlayerVariable*>(playerVar);
return boolPlayerVar->value.data;
}
}
template <typename T>
static void ChangePlayerVar(const std::string& name, const T value) {
static_assert(std::is_same_v<T, std::string> || std::is_same_v<T, float> || std::is_same_v<T, bool>, "Invalid type: value must be string, float or bool");
if (!gotPlayerVars)
return;
auto playerVar = playerVars.Find(name);
if (!playerVar)
return;
if constexpr (std::is_same_v<T, std::string>) {
switch (playerVar->GetType()) {
case PlayerVarType::String:
// TO IMPLEMENT
break;
case PlayerVarType::Float:
{
std::string valueStr = Utils::Values::to_string(value);
float actualValue = std::stof(valueStr);
FloatPlayerVariable* floatPlayerVar = reinterpret_cast<FloatPlayerVariable*>(playerVar);
floatPlayerVar->SetValues(actualValue);
break;
}
case PlayerVarType::Bool:
{
std::string valueStr = Utils::Values::to_string(value);
bool actualValue = valueStr == "true";
BoolPlayerVariable* boolPlayerVar = reinterpret_cast<BoolPlayerVariable*>(playerVar);
boolPlayerVar->SetValues(actualValue);
break;
}
default:
break;
}
} else if constexpr (std::is_same_v<T, float>) {
if (playerVar->GetType() != PlayerVarType::Float)
return;
FloatPlayerVariable* floatPlayerVar = reinterpret_cast<FloatPlayerVariable*>(playerVar);
floatPlayerVar->SetValues(value);
} else if constexpr (std::is_same_v<T, bool>) {
if (playerVar->GetType() != PlayerVarType::Bool)
return;
BoolPlayerVariable* boolPlayerVar = reinterpret_cast<BoolPlayerVariable*>(playerVar);
boolPlayerVar->SetValues(value);
}
}
template <typename T>
static void ChangePlayerVarFromList(const std::string& name, const T value, PlayerVariable* playerVar = nullptr) {
static_assert(std::is_same_v<T, std::string> || std::is_same_v<T, float> || std::is_same_v<T, bool>, "Invalid type: value must be string, float or bool");
if (!gotPlayerVars)
return;
if (!playerVar) {
playerVar = playerVars.Find(name);
if (!playerVar)
return;
}
auto customPlayerVar = customPlayerVars.Find(name);
auto defPlayerVar = defaultPlayerVars.Find(name);
if constexpr (std::is_same_v<T, std::string>) {
switch (playerVar->GetType()) {
case PlayerVarType::String:
// TO IMPLEMENT
break;
case PlayerVarType::Float:
{
if (!customPlayerVar)
customPlayerVar = customPlayerVars.emplace_back(std::make_unique<FloatPlayerVariable>(name)).get();
std::string valueStr = Utils::Values::to_string(value);
float actualValue = std::stof(valueStr);
FloatPlayerVariable* floatPlayerVar = reinterpret_cast<FloatPlayerVariable*>(playerVar);
FloatPlayerVariable* customFloatPlayerVar = reinterpret_cast<FloatPlayerVariable*>(customPlayerVar);
if (!defPlayerVar) {
defPlayerVar = defaultPlayerVars.emplace_back(std::make_unique<FloatPlayerVariable>(name)).get();
FloatPlayerVariable* defFloatPlayerVar = reinterpret_cast<FloatPlayerVariable*>(defPlayerVar);
defFloatPlayerVar->SetValues(floatPlayerVar->value);
}
floatPlayerVar->SetValues(actualValue);
customFloatPlayerVar->SetValues(actualValue);
break;
}
case PlayerVarType::Bool:
{
if (!customPlayerVar)
customPlayerVar = customPlayerVars.emplace_back(std::make_unique<BoolPlayerVariable>(name)).get();
std::string valueStr = Utils::Values::to_string(value);
bool actualValue = valueStr == "true";
BoolPlayerVariable* boolPlayerVar = reinterpret_cast<BoolPlayerVariable*>(playerVar);
BoolPlayerVariable* customBoolPlayerVar = reinterpret_cast<BoolPlayerVariable*>(customPlayerVar);
if (!defPlayerVar) {
defPlayerVar = defaultPlayerVars.emplace_back(std::make_unique<BoolPlayerVariable>(name)).get();
BoolPlayerVariable* defBoolPlayerVar = reinterpret_cast<BoolPlayerVariable*>(defPlayerVar);
defBoolPlayerVar->SetValues(boolPlayerVar->value);
}
boolPlayerVar->SetValues(actualValue);
customBoolPlayerVar->SetValues(actualValue);
break;
}
default:
break;
}
} else if constexpr (std::is_same_v<T, float>) {
if (playerVar->GetType() != PlayerVarType::Float)
return;
if (!customPlayerVar)
customPlayerVar = customPlayerVars.emplace_back(std::make_unique<FloatPlayerVariable>(name)).get();
FloatPlayerVariable* floatPlayerVar = reinterpret_cast<FloatPlayerVariable*>(playerVar);
FloatPlayerVariable* customFloatPlayerVar = reinterpret_cast<FloatPlayerVariable*>(customPlayerVar);
if (!defPlayerVar) {
defPlayerVar = defaultPlayerVars.emplace_back(std::make_unique<FloatPlayerVariable>(name)).get();
FloatPlayerVariable* defFloatPlayerVar = reinterpret_cast<FloatPlayerVariable*>(defPlayerVar);
defFloatPlayerVar->SetValues(floatPlayerVar->value);
}
floatPlayerVar->SetValues(value);
customFloatPlayerVar->SetValues(value);
} else if constexpr (std::is_same_v<T, bool>) {
if (playerVar->GetType() != PlayerVarType::Bool)
return;
if (!customPlayerVar)
customPlayerVar = customPlayerVars.emplace_back(std::make_unique<BoolPlayerVariable>(name)).get();
BoolPlayerVariable* boolPlayerVar = reinterpret_cast<BoolPlayerVariable*>(playerVar);
BoolPlayerVariable* customBoolPlayerVar = reinterpret_cast<BoolPlayerVariable*>(customPlayerVar);
if (!defPlayerVar) {
defPlayerVar = defaultPlayerVars.emplace_back(std::make_unique<BoolPlayerVariable>(name)).get();
BoolPlayerVariable* defBoolPlayerVar = reinterpret_cast<BoolPlayerVariable*>(defPlayerVar);
defBoolPlayerVar->SetValues(boolPlayerVar->value);
}
boolPlayerVar->SetValues(value);
customBoolPlayerVar->SetValues(value);
}
}
template <typename T>
static void ManagePlayerVarByBool(const std::string& name, const T valueIfTrue, const T valueIfFalse, bool boolVal, bool usePreviousVal = true) {
if (!gotPlayerVars)
return;
if (prevPlayerVarValueMap.find(name) == prevPlayerVarValueMap.end())
prevPlayerVarValueMap[name] = GetPlayerVarValue<T>(name);
if (prevBoolValueMap.find(name) == prevBoolValueMap.end())
prevBoolValueMap[name] = false;
if (boolVal) {
if (!prevBoolValueMap[name])
prevPlayerVarValueMap[name] = GetPlayerVarValue<T>(name);
ChangePlayerVar(name, valueIfTrue);
prevBoolValueMap[name] = true;
} else if (prevBoolValueMap[name]) {
prevBoolValueMap[name] = false;
ChangePlayerVar(name, usePreviousVal ? std::any_cast<T>(prevPlayerVarValueMap[name]) : valueIfFalse);
prevPlayerVarValueMap.erase(name);
}
}
static bool IsPlayerVarManagedByBool(const std::string& name) {
if (!gotPlayerVars)
return false;
return prevBoolValueMap.find(name) != prevBoolValueMap.end() && prevBoolValueMap[name];
}
static PlayerVariables* GetOffset_PlayerVariables() {
PlayerState* playerState = PlayerState::Get();
return playerState ? playerState->playerVariables : nullptr;
}
PlayerVariables* PlayerVariables::Get() {
2025-01-04 04:53:31 +02:00
return ClassHelpers::SafeGetter<PlayerVariables>(GetOffset_PlayerVariables, false, false);
}
}