Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ class RESOURCES_CORE_EXPORT ResourceEventManagerComponent : public fwRefCountabl
//
void AddResourceHandledEvent(const std::string& resourceName, const std::string& eventName);

//
// An event to observe which events resources subscribe to, so that expensive event sources
// can cache whether producing an event is worth it.
// Arguments: eventName, resourceName
//
fwEvent<const std::string&, const std::string&> OnResourceHandledEvent;

//
// An event to handle event execution externally.
// Arguments: eventName, eventPayload, eventSource, eventCanceled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,11 @@ void ResourceEventManagerComponent::AddResourceHandledEvent(const std::string& r
}

m_eventResources.emplace(eventName, resourceName);

OnResourceHandledEvent(eventName, resourceName);
}


bool ResourceEventManagerComponent::TriggerEvent(const std::string& eventName, const std::string& eventPayload, const std::string& eventSource /* = std::string() */, ResourceEventComponent* filter /* = nullptr*/)
{
// add a value to signify event cancelation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1569,6 +1569,8 @@ class ServerGameState : public ServerGameStatePublic, public fx::IAttached<fx::S

bool ValidateEntity(EntityLockdownMode entityLockdownMode, const fx::sync::SyncEntityPtr& entity);

void HandlePedHealthUpdate(const fx::sync::SyncEntityPtr& entity, fx::sync::CPedHealthNodeData* healthNode, int oldHealth, int oldArmour);

public:
std::unordered_set<uint32_t> blockedEvents;
std::shared_mutex blockedEventsMutex;
Expand All @@ -1588,6 +1590,16 @@ class ServerGameState : public ServerGameStatePublic, public fx::IAttached<fx::S
private:
fx::ServerInstanceBase* m_instance;

enum PedEventFlags : uint32_t
{
PedEventHealthChanged = 1 << 0,
PedEventDeath = 1 << 1,
};

// updated when a resource registers a handler, so the sync parse path only needs an
// atomic read instead of a lookup in the event registry
std::atomic<uint32_t> m_pedEventFlags{ 0 };

#ifdef USE_ASYNC_SCL_POSTING
std::unique_ptr<ThreadPool> m_tg;
#endif
Expand Down
114 changes: 114 additions & 0 deletions code/components/citizen-server-impl/src/state/ServerGameState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3518,9 +3518,23 @@ bool ServerGameState::ProcessClonePacket(const fx::ClientSharedPtr& client, rl::
{
if (parsingType == 2)
{
auto healthNode = (entity->type == sync::NetObjEntityType::Ped || entity->type == sync::NetObjEntityType::Player)
? syncTree->GetPedHealth()
: nullptr;

// maxHealth is only 0 until the node was parsed for the first time
bool hadHealth = healthNode && healthNode->maxHealth != 0;
int oldHealth = hadHealth ? healthNode->health : 0;
int oldArmour = hadHealth ? healthNode->armour : 0;

syncTree->ParseSync(state);

entity->hasSynced = true;

if (hadHealth)
{
HandlePedHealthUpdate(entity, healthNode, oldHealth, oldArmour);
}
}
else if (parsingType == 1)
{
Expand Down Expand Up @@ -3708,6 +3722,83 @@ bool ServerGameState::ProcessClonePacket(const fx::ClientSharedPtr& client, rl::
return true;
}

void ServerGameState::HandlePedHealthUpdate(const fx::sync::SyncEntityPtr& entity, fx::sync::CPedHealthNodeData* healthNode, int oldHealth, int oldArmour)
{
int health = healthNode->health;
int armour = healthNode->armour;

if (health == oldHealth && armour == oldArmour)
{
return;
}

bool died = oldHealth > 0 && health <= 0;

uint32_t flags = m_pedEventFlags.load(std::memory_order_relaxed);
bool wantsHealthChanged = (flags & PedEventHealthChanged) != 0;
bool wantsDeath = died && (flags & PedEventDeath) != 0;

// don't pay for events nobody is listening to
if (!wantsHealthChanged && !wantsDeath)
{
return;
}

// the node may be parsed again before the callback runs, so pass values, not the node
uint32_t weaponHash = healthNode->causeOfDeath;
int sourceOfDamage = healthNode->sourceOfDamage;

gscomms_execute_callback_on_main_thread([this, entity, wantsHealthChanged, wantsDeath, oldHealth, health, oldArmour, armour, sourceOfDamage, weaponHash]()
{
auto evComponent = m_instance->GetComponent<fx::ResourceManager>()->GetComponent<fx::ResourceEventManagerComponent>();
auto ped = MakeScriptHandle(entity);

uint32_t attacker = 0;

if (sourceOfDamage != 0)
{
if (auto attackerEntity = GetEntity(0, sourceOfDamage))
{
attacker = MakeScriptHandle(attackerEntity);
}
}

if (wantsHealthChanged)
{
/*NETEV onPedHealthChanged SERVER
/#*
* Triggered when the health or armour of a ped changed.
*
* @param ped - The handle of the ped whose health or armour changed.
* @param oldHealth - The health the ped had before the change.
* @param health - The health the ped has after the change.
* @param oldArmour - The armour the ped had before the change.
* @param armour - The armour the ped has after the change.
* @param attacker - The handle of the entity that caused the damage, or 0 if unknown.
* @param weaponHash - The hash of the weapon that caused the damage, or 0 if unknown.
#/
declare function onPedHealthChanged(ped: number, oldHealth: number, health: number, oldArmour: number, armour: number, attacker: number, weaponHash: number): void;
*/
evComponent->TriggerEvent2("onPedHealthChanged", {}, ped, oldHealth, health, oldArmour, armour, attacker, weaponHash);
}

if (wantsDeath)
{
/*NETEV onPedDeath SERVER
/#*
* Triggered when a ped died.
*
* @param ped - The handle of the ped that died.
* @param attacker - The handle of the entity that killed the ped, or 0 if unknown.
* @param weaponHash - The hash of the weapon that killed the ped, or 0 if unknown.
#/
declare function onPedDeath(ped: number, attacker: number, weaponHash: number): void;
*/
evComponent->TriggerEvent2("onPedDeath", {}, ped, attacker, weaponHash);
}
});
}

bool ServerGameState::ValidateEntity(EntityLockdownMode entityLockdownMode, const fx::sync::SyncEntityPtr& entity)
{
// can't validate an entity without sync tree
Expand Down Expand Up @@ -4571,6 +4662,29 @@ void ServerGameState::AttachToObject(fx::ServerInstanceBase* instance)
{
m_instance = instance;

instance->GetComponent<fx::ResourceManager>()->GetComponent<fx::ResourceEventManagerComponent>()->OnResourceHandledEvent.Connect([this](const std::string& eventName, const std::string&)
{
uint32_t flags = 0;

if (eventName == "onPedHealthChanged")
{
flags = PedEventHealthChanged;
}
else if (eventName == "onPedDeath")
{
flags = PedEventDeath;
}
else if (eventName == "*")
{
flags = PedEventHealthChanged | PedEventDeath;
}

if (flags)
{
m_pedEventFlags.fetch_or(flags, std::memory_order_relaxed);
}
});

m_lockdownModeVar = instance->AddVariable<fx::EntityLockdownMode>("sv_entityLockdown", ConVar_None, m_entityLockdownMode, &m_entityLockdownMode);
m_stateBagStrictModeVar = instance->AddVariable<bool>("sv_stateBagStrictMode", ConVar_None, m_stateBagStrictMode, &m_stateBagStrictMode);

Expand Down
Loading