#include "Entity.hpp" #include "Natives.hpp" #include "core/util/Joaat.hpp" #include "core/backend/ScriptMgr.hpp" #include "game/pointers/Pointers.hpp" #include "types/rage/tlsContext.hpp" #include "types/entity/CDynamicEntity.hpp" #include "types/network/netObject.hpp" #include "types/network/CNetworkPlayerMgr.hpp" #include "game/gta/Scripts.hpp" #include "types/ped/CPedFactory.hpp" #include "types/network/netObjectMgrBase.hpp" #include "game/backend/Self.hpp" #include "game/gta/Packet.hpp" namespace { int GetNextTokenValue(int prev_token) { for (int i = 0; i < 0x1F; i++) { if ((i << 27) - (prev_token << 27) > 0) return i; } return 0; } } namespace YimMenu { void Entity::PopulatePointer() { m_Pointer = Pointers.HandleToPtr(m_Handle); } void Entity::PopulateHandle() { if (m_Pointer) m_Handle = Pointers.PtrToHandle(m_Pointer); } void Entity::AssertValid(std::string_view function_name) { if (!IsValid()) { LOG(WARNING) << "IsValid() assertion failed for " << function_name; } } void Entity::AssertControl(std::string_view function_name) { if (!IsValid()) return; if (!HasControl()) { LOG(WARNING) << "HasControl() assertion failed for " << function_name; } } void Entity::AssertScriptContext(std::string_view function_name) { if (!rage::tlsContext::Get()->m_ScriptThreadActive) { LOG(WARNING) << "Script thread not active for " << function_name; } } bool Entity::IsValid() { if (m_Handle) { return ENTITY::DOES_ENTITY_EXIST(m_Handle); } else if (m_Pointer) { return true; // TODO: potential use after free } return false; } bool Entity::IsPed() { ENTITY_ASSERT_VALID(); if (auto ptr = GetPointer()) return ptr->IsPed(); return false; } bool Entity::IsVehicle() { ENTITY_ASSERT_VALID(); if (auto ptr = GetPointer()) return ptr->IsVehicle(); return false; } bool Entity::IsObject() { ENTITY_ASSERT_VALID(); if (auto ptr = GetPointer()) return ptr->IsObject(); return false; } bool Entity::IsPlayer() { ENTITY_ASSERT_VALID(); return PED::IS_PED_A_PLAYER(GetHandle()); } bool Entity::IsMissionEntity() { ENTITY_ASSERT_VALID(); return ENTITY::IS_ENTITY_A_MISSION_ENTITY(GetHandle()); // TODO: detect more mission states } int Entity::GetModel() { ENTITY_ASSERT_VALID(); return ENTITY::GET_ENTITY_MODEL(GetHandle()); } rage::fvector3 Entity::GetPosition() { ENTITY_ASSERT_VALID(); return ENTITY::GET_ENTITY_COORDS(GetHandle(), false); } void Entity::SetPosition(rage::fvector3 position) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); ENTITY_ASSERT_SCRIPT_CONTEXT(); ENTITY::SET_ENTITY_COORDS_NO_OFFSET(GetHandle(), position.x, position.y, position.z, true, true, true); } rage::fvector3 Entity::GetRotation(int order) { ENTITY_ASSERT_VALID(); return ENTITY::GET_ENTITY_ROTATION(GetHandle(), order); } void Entity::SetRotation(rage::fvector3 rotation, int order) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); ENTITY::SET_ENTITY_ROTATION(GetHandle(), rotation.x, rotation.y, rotation.z, order, false); } rage::fvector3 Entity::GetVelocity() { ENTITY_ASSERT_VALID(); return ENTITY::GET_ENTITY_VELOCITY(GetHandle()); } void Entity::SetVelocity(rage::fvector3 vel) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); ENTITY::SET_ENTITY_VELOCITY(GetHandle(), vel.x, vel.y, vel.z); } float Entity::GetHeading() { ENTITY_ASSERT_VALID(); return ENTITY::GET_ENTITY_HEADING(GetHandle()); } void Entity::SetHeading(float heading) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); ENTITY::SET_ENTITY_HEADING(GetHandle(), heading); } float Entity::GetSpeed() { ENTITY_ASSERT_VALID(); return ENTITY::GET_ENTITY_SPEED(GetHandle()); } void Entity::SetCollision(bool enabled) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); ENTITY::SET_ENTITY_COLLISION(GetHandle(), enabled, true); } void Entity::SetFrozen(bool enabled) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); ENTITY::FREEZE_ENTITY_POSITION(GetHandle(), enabled); } void Entity::Delete() { ENTITY_ASSERT_VALID(); if (!IsValid()) return; if (IsNetworked()) { auto net = GetPointer()->m_NetObject; DeleteNetwork(net); } else { // TODO: call delete functions directly if (!ENTITY::IS_ENTITY_A_MISSION_ENTITY(GetHandle())) ENTITY::SET_ENTITY_AS_MISSION_ENTITY(GetHandle(), true, true); auto hnd = GetHandle(); ENTITY::DELETE_ENTITY(&hnd); } } void Entity::DeleteNetwork(std::uint16_t network_id, std::uint32_t ownership_token, bool local, Player* for_player) { char buf[0x200]{}; rage::datBitBuffer remove_buf(buf, sizeof(buf)); int msgs_written = 0; if (ownership_token == -1) { remove_buf.Write(network_id, 13); remove_buf.Write(GetNextTokenValue(ownership_token), 5); msgs_written++; } else { // try all tokens if we don't know it for (int i = 0; i < 0x1F; i++) { remove_buf.Write(network_id, 13); remove_buf.Write(i, 5); msgs_written++; } } Packet pack; pack.WriteMessageHeader(rage::netMessage::Type::PackedReliables); pack.GetBuffer().Write(4, 4); // remove pack.GetBuffer().Write(msgs_written, 5); pack.GetBuffer().Write(remove_buf.m_BitsRead, 13); pack.GetBuffer().WriteArray(buf, remove_buf.m_BitsRead); if (for_player) { if (for_player->IsValid()) pack.Send(for_player->GetMessageId()); } else { for (int i = 0; i < 32; i++) if (auto player = (*Pointers.NetworkPlayerMgr)->m_Players[i]; player && player->IsPhysical() && !player->IsLocal()) pack.Send(player->m_MessageId); } if (local) if (auto object = Pointers.GetNetObjectById(network_id)) (*Pointers.NetworkObjectMgr)->UnregisterNetworkObject(object, 8, true, true); } void Entity::DeleteNetwork(rage::netObject* object, bool local, Player* for_player) { DeleteNetwork(object->m_ObjectId, object->m_OwnershipToken, local, for_player); } bool Entity::IsNetworked() { return GetNetworkObject() != nullptr; } bool Entity::IsRemote() { if (!IsNetworked()) return false; return GetNetworkObject()->m_IsRemote; } rage::netObject* Entity::GetNetworkObject() { if (!IsValid()) return nullptr; return GetPointer()->m_NetObject; } int Entity::GetOwner() { if (!IsNetworked()) return -1; return GetNetworkObject()->m_OwnerId; } std::uint16_t Entity::GetNetworkObjectId() { ENTITY_ASSERT_VALID(); if (!IsNetworked()) return 0; return GetNetworkObject()->m_ObjectId; } void Entity::PreventMigration() { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); if (!*Pointers.IsSessionStarted) return; if (!IsNetworked() || !NETWORK::NETWORK_HAS_ENTITY_BEEN_REGISTERED_WITH_THIS_THREAD(GetHandle())) { #ifdef ENTITY_DEBUG LOGF(WARNING, "PreventMigration(): entity is not networked!"); #endif return; } NETWORK::NETWORK_DISABLE_PROXIMITY_MIGRATION(NETWORK::PED_TO_NET(GetHandle())); } void Entity::ForceControl() { ENTITY_ASSERT_VALID(); if (!IsNetworked() || HasControl()) return; (*Pointers.NetworkObjectMgr)->ChangeOwner(GetNetworkObject(), Self::GetPlayer().GetHandle(), 0); } bool Entity::RequestControl(int timeout) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_SCRIPT_CONTEXT(); if (!IsNetworked() || HasControl()) return true; for (int i = 0; i < (timeout + 1); i++) { if (!IsValid() || !*Pointers.IsSessionStarted) return false; if (HasControl()) return true; Pointers.RequestControl(GetNetworkObject()); if (timeout) ScriptMgr::Yield(); } return HasControl(); } void Entity::ForceSync(Player* for_player) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); ENTITY_ASSERT_SCRIPT_CONTEXT(); if (!IsNetworked()) return; char data[0x400]; auto net = GetNetworkObject(); for (int i = 0; i < 32; i++) { if ((*Pointers.NetworkPlayerMgr)->m_Players[i] && (*Pointers.NetworkPlayerMgr)->m_Players[i] != (*Pointers.NetworkPlayerMgr)->m_LocalPlayer && (!for_player || !for_player->IsValid() || for_player->GetId() == i)) { rage::datBitBuffer buffer(data, sizeof(data)); (*Pointers.NetworkObjectMgr)->PackCloneCreate(net, (*Pointers.NetworkPlayerMgr)->m_Players[i], &buffer); } } } bool Entity::HasControl() { return NETWORK::NETWORK_HAS_CONTROL_OF_ENTITY(GetHandle()); } bool Entity::IsInvincible() { // TODO this is bad! ENTITY_ASSERT_VALID(); return !ENTITY::GET_ENTITY_CAN_BE_DAMAGED(GetHandle()); } void Entity::SetInvincible(bool status) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); ENTITY::SET_ENTITY_INVINCIBLE(GetHandle(), status, true); } bool Entity::IsDead() { ENTITY_ASSERT_VALID(); return ENTITY::IS_ENTITY_DEAD(GetHandle(), true); } void Entity::Kill() { ENTITY_ASSERT_VALID(); if (HasControl()) { ENTITY::SET_ENTITY_HEALTH(GetHandle(), 0, PLAYER::PLAYER_PED_ID(), 0); } else { auto ptr = GetPointer(); auto local = reinterpret_cast((*Pointers.PedFactory)->m_LocalPed); auto pos = GetPosition(); std::uint32_t weapon = "WEAPON_EXPLOSION"_J; if (!ptr || !local) return; Pointers.TriggerWeaponDamageEvent(local, ptr, &pos, 0, true, weapon, 9999.9f, 2, 0, (1<<4)|0x80000, 0, 0, 0, false, false, true, true, nullptr); } } int Entity::GetHealth() { ENTITY_ASSERT_VALID(); return ENTITY::GET_ENTITY_HEALTH(GetHandle()); } void Entity::SetHealth(int health) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); ENTITY::SET_ENTITY_HEALTH(GetHandle(), health, 0, 0); } int Entity::GetMaxHealth() { ENTITY_ASSERT_VALID(); return ENTITY::GET_ENTITY_MAX_HEALTH(GetHandle()); } bool Entity::IsVisible() { ENTITY_ASSERT_VALID(); return ENTITY::IS_ENTITY_VISIBLE(GetHandle()); } void Entity::SetVisible(bool status) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); ENTITY::SET_ENTITY_VISIBLE(GetHandle(), status, true); } int Entity::GetAlpha() { ENTITY_ASSERT_VALID(); return ENTITY::GET_ENTITY_ALPHA(GetHandle()); } void Entity::SetAlpha(int alpha) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); // doesn't matter anyway because alpha changes are not networked ENTITY::SET_ENTITY_ALPHA(GetHandle(), alpha, false); } void Entity::ResetAlpha() { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_CONTROL(); // doesn't matter anyway because alpha changes are not networked ENTITY::RESET_ENTITY_ALPHA(GetHandle()); } bool Entity::HasInterior() { ENTITY_ASSERT_VALID(); return INTERIOR::GET_INTERIOR_FROM_ENTITY(GetHandle()) != 0; } void Entity::Explode(ExplosionType explosion, float damage, bool is_visible, bool is_audible, float camera_shake) { ENTITY_ASSERT_VALID(); ENTITY_ASSERT_SCRIPT_CONTEXT(); if (!IsValid()) return; auto pos = GetPosition(); Scripts::RunWithSpoofedThreadName("am_mp_orbital_cannon"_J, [&] { // add_owned_explosion is more or less useless now FIRE::ADD_EXPLOSION(pos.x, pos.y, pos.z, static_cast(explosion), damage, is_audible, !is_visible, camera_shake, damage == 0.0f); }); } bool Entity::operator==(const Entity& other) { if (m_Handle != 0 && other.m_Handle != 0) return m_Handle == other.m_Handle; if (m_Pointer != nullptr && other.m_Pointer != nullptr) return m_Pointer == other.m_Pointer; if (other.m_Pointer != nullptr) if (auto ptr = GetPointer()) return ptr == other.m_Pointer; if (other.m_Handle != 0) if (auto handle = GetHandle(); handle != 0) return handle == other.m_Handle; return false; } }