Skip to content

Commit

Permalink
CPedGroup + CPedGroupMembership (#551)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pirulax authored Jul 16, 2023
1 parent 24fbd05 commit 3976c25
Show file tree
Hide file tree
Showing 16 changed files with 565 additions and 154 deletions.
2 changes: 2 additions & 0 deletions source/InjectHooksMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,8 @@ void InjectHooksMain() {
CLoadedCarGroup::InjectHooks();
RenderBuffer::InjectHooks();
CStaticShadow::InjectHooks();
CPedGroup::InjectHooks();
CPedGroupMembership::InjectHooks();
CRealTimeShadowManager::InjectHooks();
CRealTimeShadow::InjectHooks();
CPopCycle::InjectHooks();
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ struct NotIsNull {
}
};

// Find first non-null value in range. If found it's returned, `null` otherwise.
//! Find first non-null value in range. If found it's returned, `null` otherwise.
template<rng::input_range R, typename T_Ret = rng::range_value_t<R>>
requires(std::is_pointer_v<T_Ret>)
T_Ret FirstNonNull(R&& range) {
Expand Down
1 change: 1 addition & 0 deletions source/game_sa/Entity/Ped/Ped.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2096,6 +2096,7 @@ void CPed::SetPedState(ePedState pedState) {
ReleaseCoverPoint();
if (bClearRadarBlipOnDeath) {
CRadar::ClearBlipForEntity(BLIP_CHAR, GetPedPool()->GetRef(this));
// TODO: Shouldn't we `bClearRadarBlipOnDeath = false` here?
}
}
}
Expand Down
134 changes: 106 additions & 28 deletions source/game_sa/PedGroup.cpp
Original file line number Diff line number Diff line change
@@ -1,61 +1,120 @@
#include "StdInc.h"

#include "PedGroup.h"
#include <TaskSimpleCarSetPedOut.h>
#include <TaskComplexFollowLeaderInFormation.h>

// 0x5FC150
CPedGroup::CPedGroup() {
m_groupMembership.m_pPedGroup = this;
m_groupIntelligence.m_pPedGroup = this;
m_bIsMissionGroup = false;
m_pPed = nullptr;
m_bMembersEnterLeadersVehicle = true;
}

// 0x5FC190
CPedGroup::~CPedGroup() {
for (auto i = 0u; i < m_groupMembership.m_apMembers.size(); i++) {
m_groupMembership.RemoveMember(i);
}
}

//! @returns Distance of the furthers member from the leader
float CPedGroup::FindDistanceToFurthestMember() {
return plugin::CallMethodAndReturn<float, 0x5FB010, CPedGroup*>(this);
/*
const auto leader = GetMembership().GetLeader();
for (const auto& mem : GetMembership().GetMembers(true)) {
}*/
}

// 0x5FB0A0
float CPedGroup::FindDistanceToNearestMember(CPed** ppOutNearestMember) {
return plugin::CallMethodAndReturn<float, 0x5FB0A0, CPedGroup*, CPed**>(this, ppOutNearestMember);
const auto [nearest, distSq] = GetMembership().FindClosestFollowerToLeader();
if (nearest) {
if (ppOutNearestMember) {
*ppOutNearestMember = nearest;
}
return std::sqrt(distSq);
}
return 1.0e10f;
}

void CPedGroup::Flush() {
plugin::CallMethod<0x5FB790, CPedGroup*>(this);
// 0x5FACD0
CPed* CPedGroup::GetClosestGroupPed(CPed* ped, float* pOutDistSq) {
const auto [closest, distSq] = GetMembership().GetMemberClosestTo(ped);
if (closest) {
if (pOutDistSq) {
*pOutDistSq = distSq;
}
}
return closest;
}

CPed* CPedGroup::GetClosestGroupPed(CPed* ped, float* pOutDistance) {
return plugin::CallMethodAndReturn<CPed*, 0x5FACD0, CPedGroup*, CPed*, float*>(this, ped, pOutDistance);
// 0x5FB790
void CPedGroup::Flush() {
m_groupMembership.Flush();
m_groupIntelligence.Flush();
m_bIsMissionGroup = false;
}

// 0x5F7DB0
bool CPedGroup::IsAnyoneUsingCar(const CVehicle* vehicle) {
return plugin::CallMethodAndReturn<bool, 0x5F7DB0, CPedGroup*, const CVehicle*>(this, vehicle);
assert(vehicle);

for (auto& mem : m_groupMembership.GetMembers()) {
if (mem.GetVehicleIfInOne() == vehicle) {
return true;
}
// I did a slight change here.
// Game originally checked both EnterAsDriver and EnterAsPassenger tasks
// but that makes no sense, as the ped can't have both tasks at the same time (I hope)
// So the function below returns the target vehicle of the first task found
if (mem.GetIntelligence()->GetEnteringVehicle() == vehicle) {
return true;
}
}
return false;
}

void CPedGroup::PlayerGaveCommand_Attack(CPed* playerPed, CPed* ped) {
plugin::CallMethod<0x5F7CC0, CPedGroup*, CPed*, CPed*>(this, playerPed, ped);
// 0x5F7CC0
void CPedGroup::PlayerGaveCommand_Attack(CPed* playerPed, CPed* target) {
if (!m_groupIntelligence.AddEvent(CEventGroupEvent{ playerPed, new CEventPlayerCommandToGroup{PLAYER_GROUP_COMMAND_ATTACK, target} })) {
return;
}
if (target && target->m_nPedType != PED_TYPE_GANG2) {
target->Say(target->IsGangster() ? 147 : 148);
}
}

void CPedGroup::PlayerGaveCommand_Gather(CPed* ped) {
plugin::CallMethod<0x5FAB60, CPedGroup*, CPed*>(this, ped);
}

void CPedGroup::Process() {
plugin::CallMethod<0x5FC7E0, CPedGroup*>(this);
m_groupMembership.Process();
m_groupIntelligence.Process();
}

void CPedGroup::RemoveAllFollowers() {
plugin::CallMethod<0x5FB7D0, CPedGroup*>(this);
GetMembership().RemoveAllFollowers(false);
}

void CPedGroup::Teleport(const CVector* pos) {
plugin::CallMethod<0x5F7AD0, CPedGroup*, const CVector*>(this, pos);
void CPedGroup::Teleport(const CVector& pos) {
if (const auto leader = GetMembership().GetLeader()) {
leader->Teleport(pos, false);
}

if (const auto oevent = m_groupIntelligence.m_pOldEventGroupEvent) {
if (oevent->GetEventType() == EVENT_LEADER_ENTRY_EXIT) {
return;
}
}

// Set *followers* out of the vehicle
for (auto& flwr : GetMembership().GetMembers(false)) {
if (!flwr.IsAlive() || !flwr.bInVehicle || flwr.IsCreatedByMission()) {
continue;
}
CTaskSimpleCarSetPedOut{ flwr.m_pVehicle, (eTargetDoor)CCarEnterExit::ComputeTargetDoorToExit(flwr.m_pVehicle, &flwr), false }.ProcessPed(&flwr);
}

// Teleport *followers*
const auto& offsets = CTaskComplexFollowLeaderInFormation::ms_offsets.offsets;
for (auto&& [offsetIdx, flwr] : notsa::enumerate(GetMembership().GetMembers(false))) {
if (!flwr.IsAlive()) {
continue;
}
flwr.Teleport(pos + CVector{ offsets[offsetIdx] }, false);
flwr.PositionAnyPedOutOfCollision();
flwr.GetTaskManager().AbortFirstPrimaryTaskIn({ TASK_PRIMARY_PHYSICAL_RESPONSE, TASK_PRIMARY_EVENT_RESPONSE_TEMP, TASK_PRIMARY_EVENT_RESPONSE_NONTEMP }, &flwr);
}
}

int32 CPedGroup::GetId() const {
Expand All @@ -65,3 +124,22 @@ int32 CPedGroup::GetId() const {
bool CPedGroup::IsActive() const {
return CPedGroups::ms_activeGroups[GetId()];
}

void CPedGroup::InjectHooks() {
RH_ScopedClass(CPedGroup);
RH_ScopedCategory(); // TODO: Change this to the appropriate category!

RH_ScopedInstall(Constructor, 0x5FC150);
RH_ScopedInstall(Destructor, 0x5FC190);

RH_ScopedInstall(Teleport, 0x5F7AD0);
RH_ScopedInstall(PlayerGaveCommand_Gather, 0x5FAB60, {.reversed = false});
RH_ScopedInstall(PlayerGaveCommand_Attack, 0x5F7CC0);
RH_ScopedInstall(IsAnyoneUsingCar, 0x5F7DB0);
RH_ScopedInstall(GetClosestGroupPed, 0x5FACD0);
RH_ScopedInstall(FindDistanceToFurthestMember, 0x5FB010, {.reversed = false});
RH_ScopedInstall(FindDistanceToNearestMember, 0x5FB0A0);
RH_ScopedInstall(Flush, 0x5FB790);
RH_ScopedInstall(Process, 0x5FC7E0);
RH_ScopedInstall(RemoveAllFollowers, 0x5FB7D0);
}
75 changes: 53 additions & 22 deletions source/game_sa/PedGroup.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,67 @@ class CVehicle;

class CPedGroup {
public:
CPed* m_pPed;
bool m_bMembersEnterLeadersVehicle;
CPedGroupMembership m_groupMembership;
CPedGroupIntelligence m_groupIntelligence;
bool m_bIsMissionGroup;
char field_2D1[3];
static void InjectHooks();

public:
CPedGroup();
~CPedGroup();
CPedGroup() = default;
~CPedGroup() = default;

float FindDistanceToFurthestMember();
//! Find follower closest to the leader
float FindDistanceToNearestMember(CPed** ppOutNearestMember);
void Flush();

//! Clear state
void Flush();

//! Find member closest to `ped`
CPed* GetClosestGroupPed(CPed* ped, float* pOutDistance);
bool IsAnyoneUsingCar(const CVehicle* vehicle);
void PlayerGaveCommand_Attack(CPed* playerPed, CPed* ped);
void PlayerGaveCommand_Gather(CPed* ped);
void Process();
void RemoveAllFollowers();
void Teleport(const CVector* pos);

// NOTSA
inline CPedGroupIntelligence& GetIntelligence() { return m_groupIntelligence; }
//! Find distance of the furthest member to `ped`
float FindDistanceToFurthestMember();

//! Is anyone from this group using the given car
bool IsAnyoneUsingCar(const CVehicle* vehicle);

//! todo
void PlayerGaveCommand_Attack(CPed* playerPed, CPed* ped);

//! todo
void PlayerGaveCommand_Gather(CPed* ped);

//! Update routine
void Process();

//! Remove all followers of the group [That is, all members, excluding the leader]
void RemoveAllFollowers();

//! Teleport the whole group [incl. leader] to a position
void Teleport(const CVector& pos);

//! Get id of this group
int32 GetId() const;
bool IsActive() const;

inline auto& GetMembership() const { return m_groupMembership; }
inline auto& GetMembership() { return m_groupMembership; }
};
auto& GetIntelligence() { return m_groupIntelligence; }
auto& GetMembership() const { return m_groupMembership; }
auto& GetMembership() { return m_groupMembership; }

private: // Wrappers for hooks
// 0x5FC150
CPedGroup* Constructor() {
this->CPedGroup::CPedGroup();
return this;
}

// 0x5FC190
CPedGroup* Destructor() {
this->CPedGroup::~CPedGroup();
return this;
}

public:
CPed* m_pPed{};
bool m_bMembersEnterLeadersVehicle{true};
CPedGroupMembership m_groupMembership{*this};
CPedGroupIntelligence m_groupIntelligence{*this};
bool m_bIsMissionGroup{};
};
VALIDATE_SIZE(CPedGroup, 0x2D4);
20 changes: 17 additions & 3 deletions source/game_sa/PedGroupIntelligence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ void CPedGroupIntelligence::InjectHooks() {
RH_ScopedCategoryGlobal();

//RH_ScopedInstall(Constructor, 0x5F7250, { .reversed = false });
//RH_ScopedInstall(Destructor, 0x5F7350, { .reversed = false });


RH_ScopedOverloadedInstall(AddEvent, "", 0x5F7470, bool(CPedGroupIntelligence::*)(CEvent*), { .reversed = false });
RH_ScopedInstall(SetScriptCommandTask, 0x5F8560, { .reversed = false });
RH_ScopedInstall(Flush, 0x5F7350, { .reversed = false });
RH_ScopedInstall(GetTaskMain, 0x5F85A0, { .reversed = false });
RH_ScopedInstall(ComputeDefaultTasks, 0x5F88D0, { .reversed = false });
RH_ScopedInstall(GetTaskScriptCommand, 0x5F8690, { .reversed = false });
Expand All @@ -28,8 +28,18 @@ CPedGroupIntelligence::CPedGroupIntelligence() {
plugin::CallMethod<0x5F7250, CPedGroupIntelligence*>(this);
}

// 0x5F7350
CPedGroupIntelligence::CPedGroupIntelligence(CPedGroup& owner) :
m_pPedGroup{ &owner }
{
}

// Unknown address (If any)
CPedGroupIntelligence::~CPedGroupIntelligence() {
Flush(); // Not sure if it does this at all, but it worked so far, so let's leave it like this for now
}

// 0x5F7350
void CPedGroupIntelligence::Flush() { // Pirulax: For some reason this is called `~CPedGroupIntelligence` in *there*...
plugin::CallMethod<0x5F7350, CPedGroupIntelligence*>(this);
}

Expand Down Expand Up @@ -87,6 +97,10 @@ eSecondaryTask CPedGroupIntelligence::GetTaskSecondarySlot(CPed* ped) {
return plugin::CallMethodAndReturn<eSecondaryTask, 0x5F8650>(this, ped);
}

void CPedGroupIntelligence::Process() {
plugin::CallMethod<0x5FC4A0>(this);
}

// 0x5F88D0
void CPedGroupIntelligence::ComputeDefaultTasks(CPed* ped) {
plugin::CallMethod<0x5F88D0, CPedGroupIntelligence*, CPed*>(this, ped);
Expand Down
3 changes: 3 additions & 0 deletions source/game_sa/PedGroupIntelligence.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ class CPedGroupIntelligence {
static void InjectHooks();

CPedGroupIntelligence();
CPedGroupIntelligence(CPedGroup& owner); // notsa
~CPedGroupIntelligence();

void Flush();

bool AddEvent(CEvent* event);
void ComputeDefaultTasks(CPed* ped);
void* ComputeEventResponseTasks();
Expand Down
Loading

0 comments on commit 3976c25

Please sign in to comment.