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
54 changes: 54 additions & 0 deletions Client/mods/deathmatch/logic/CClientGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5467,6 +5467,18 @@ void CClientGame::SendProjectileSync(CClientProjectile* pProjectile)
case WEAPONTYPE_GRENADE:
case WEAPONTYPE_TEARGAS:
case WEAPONTYPE_MOLOTOV:
{
SFloatSync<7, 17> projectileForce;
projectileForce.data.fValue = pProjectile->GetForce();
pBitStream->Write(&projectileForce);

SVelocitySync velocity;
pProjectile->GetVelocity(velocity.data.vecVelocity);
pBitStream->Write(&velocity);

break;
}

case WEAPONTYPE_REMOTE_SATCHEL_CHARGE:
{
SFloatSync<7, 17> projectileForce;
Expand All @@ -5477,6 +5489,10 @@ void CClientGame::SendProjectileSync(CClientProjectile* pProjectile)
pProjectile->GetVelocity(velocity.data.vecVelocity);
pBitStream->Write(&velocity);

// No attach offset at throw time - that's only ever added later by a server resync
// (CGame::Packet_ProjectileRestPosition) once it's known whether/where it stuck to something
pBitStream->WriteBit(false);

break;
}
case WEAPONTYPE_ROCKET:
Expand Down Expand Up @@ -5511,6 +5527,44 @@ void CClientGame::SendProjectileSync(CClientProjectile* pProjectile)
}
}

void CClientGame::SendProjectileRestPosition(eWeaponType weaponType, const CVector& vecOrigin, const CVector& vecRestPosition, ElementID attachedToID,
const CVector& vecAttachOffsetPosition, const CVector& vecAttachOffsetRotation)
{
NetBitStreamInterface* pBitStream = g_pNet->AllocateNetBitStream();
if (pBitStream)
{
pBitStream->Write(static_cast<unsigned char>(weaponType));

pBitStream->Write(vecOrigin.fX);
pBitStream->Write(vecOrigin.fY);
pBitStream->Write(vecOrigin.fZ);

pBitStream->Write(vecRestPosition.fX);
pBitStream->Write(vecRestPosition.fY);
pBitStream->Write(vecRestPosition.fZ);

if (attachedToID != INVALID_ELEMENT_ID)
{
pBitStream->WriteBit(true);
pBitStream->Write(attachedToID);

pBitStream->Write(vecAttachOffsetPosition.fX);
pBitStream->Write(vecAttachOffsetPosition.fY);
pBitStream->Write(vecAttachOffsetPosition.fZ);

pBitStream->Write(vecAttachOffsetRotation.fX);
pBitStream->Write(vecAttachOffsetRotation.fY);
pBitStream->Write(vecAttachOffsetRotation.fZ);
}
else
pBitStream->WriteBit(false);

g_pNet->SendPacket(PACKET_ID_PROJECTILE_REST_POSITION, pBitStream, PACKET_PRIORITY_LOW, PACKET_RELIABILITY_RELIABLE_ORDERED);

g_pNet->DeallocateNetBitStream(pBitStream);
}
}

void CClientGame::ResetAmmoInClip()
{
memset(&m_wasWeaponAmmoInClip[0], 0, sizeof(m_wasWeaponAmmoInClip));
Expand Down
2 changes: 2 additions & 0 deletions Client/mods/deathmatch/logic/CClientGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,8 @@ class CClientGame
std::optional<VehicleBlowState> vehicleBlowState = std::nullopt);
void SendFireSync(CFire* pFire);
void SendProjectileSync(CClientProjectile* pProjectile);
void SendProjectileRestPosition(eWeaponType weaponType, const CVector& vecOrigin, const CVector& vecRestPosition, ElementID attachedToID,
const CVector& vecAttachOffsetPosition, const CVector& vecAttachOffsetRotation);

void SetServerVersionSortable(const SString& strVersion) { m_strServerVersionSortable = strVersion; }
const SString& GetServerVersionSortable() { return m_strServerVersionSortable; }
Expand Down
48 changes: 48 additions & 0 deletions Client/mods/deathmatch/logic/CClientProjectile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,29 @@ void CClientProjectile::DoPulse()
if (m_bCorrected == false && m_pProjectile != NULL && GetWeaponType() == eWeaponType::WEAPONTYPE_REMOTE_SATCHEL_CHARGE)
{
m_bCorrected = m_pProjectile->CorrectPhysics();

// Tell the server where we actually settled, once, so a player who streams in later gets placed here
// directly instead of having the whole throw replayed at them (https://github.com/multitheftauto/mtasa-blue/issues/369, #368)
if (m_bCorrected && IsLocal() && GetCreator() == g_pClientGame->GetLocalPlayer())
{
CVector vecRestPosition;
GetPosition(vecRestPosition);

// Did it stick to a vehicle/ped? Report that too, along with exactly where on it (GTA's own attach
// offset, e.g. the hood instead of the vehicle's centre), so the resync keeps it glued to the same
// spot instead of leaving it behind - or snapping it to the vehicle's origin - the moment it moves.
CClientEntity* pAttachedTo = GetSatchelAttachedTo();
ElementID attachedToID = INVALID_ELEMENT_ID;
CVector vecAttachOffsetPosition, vecAttachOffsetRotation;
if (pAttachedTo)
{
attachedToID = pAttachedTo->GetID();
GetSatchelAttachOffsets(vecAttachOffsetPosition, vecAttachOffsetRotation);
}

g_pClientGame->SendProjectileRestPosition(GetWeaponType(), *GetOrigin(), vecRestPosition, attachedToID, vecAttachOffsetPosition,
vecAttachOffsetRotation);
}
}
}

Expand Down Expand Up @@ -331,3 +354,28 @@ CClientEntity* CClientProjectile::GetSatchelAttachedTo()
CPools* pPools = g_pGame->GetPools();
return pPools->GetClientEntity((DWORD*)pAttachedToSA->GetInterface());
}

void CClientProjectile::GetSatchelAttachOffsets(CVector& vecOffsetPosition, CVector& vecOffsetRotation)
{
if (m_pProjectile)
m_pProjectile->GetAttachedOffsets(vecOffsetPosition, vecOffsetRotation);
}

void CClientProjectile::AttachSatchelToEntity(CClientEntity* pEntity, const CVector& vecOffsetPosition, const CVector& vecOffsetRotation)
{
if (!m_pProjectile || !pEntity)
return;

CPhysical* pGamePhysical = dynamic_cast<CPhysical*>(pEntity->GetGameEntity());
if (pGamePhysical)
m_pProjectile->AttachEntityToEntity(*pGamePhysical, vecOffsetPosition, vecOffsetRotation);
}

void CClientProjectile::SetStaticUntilCollisionLoaded()
{
// Used when this satchel was just placed by a resync (CGame::Packet_ProjectileRestPosition) rather than thrown
// locally - the area's collision might not be streamed in yet, so without this it falls through the world
// until something already-loaded catches it (https://github.com/multitheftauto/mtasa-blue/issues/369, #368)
if (m_pProjectile)
m_pProjectile->SetStaticWaitingForCollision(true);
}
3 changes: 3 additions & 0 deletions Client/mods/deathmatch/logic/CClientProjectile.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ class CClientProjectile final : public CClientEntity
float GetForce() { return m_fForce; }
bool IsLocal() { return m_bLocal; }
CClientEntity* GetSatchelAttachedTo();
void GetSatchelAttachOffsets(CVector& vecOffsetPosition, CVector& vecOffsetRotation);
void AttachSatchelToEntity(CClientEntity* pEntity, const CVector& vecOffsetPosition, const CVector& vecOffsetRotation);
void SetStaticUntilCollisionLoaded();

protected:
CClientProjectileManager* m_pProjectileManager;
Expand Down
89 changes: 89 additions & 0 deletions Client/mods/deathmatch/logic/CClientProjectileManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,95 @@ void CClientProjectileManager::DoPulse()
pProjectile->DoPulse();
}
}

ProcessPendingCreations();
}

void CClientProjectileManager::QueuePendingCreation(ElementID creatorID, eWeaponType eWeapon, const CVector& vecOrigin, float fForce, ElementID targetID,
ElementID originSourceID, const CVector& vecRotation, const CVector& vecVelocity, unsigned short usModel,
bool bHasAttachOffset, const CVector& vecAttachOffsetPosition, const CVector& vecAttachOffsetRotation)
{
SPendingProjectileCreation pending;
pending.creatorID = creatorID;
pending.weaponType = eWeapon;
pending.vecOrigin = vecOrigin;
pending.fForce = fForce;
pending.targetID = targetID;
pending.originSourceID = originSourceID;
pending.vecRotation = vecRotation;
pending.vecVelocity = vecVelocity;
pending.usModel = usModel;
pending.bHasAttachOffset = bHasAttachOffset;
pending.vecAttachOffsetPosition = vecAttachOffsetPosition;
pending.vecAttachOffsetRotation = vecAttachOffsetRotation;
pending.llCreationTime = GetTickCount64_();
m_PendingCreations.push_back(pending);
}

void CClientProjectileManager::SettleResyncedSatchel(CClientProjectile* pProjectile, eWeaponType weaponType, float fForce, const CVector& vecVelocity,
CClientEntity* pOriginSource, const CVector& vecAttachOffsetPosition,
const CVector& vecAttachOffsetRotation)
{
// A live throw always has some non-zero force/velocity - zero on both is only ever seen on a satchel resync
// packet (CGame::Packet_ProjectileRestPosition), which deliberately zeroes them once the satchel has settled.
if (weaponType != WEAPONTYPE_REMOTE_SATCHEL_CHARGE || fForce != 0.0f || vecVelocity != CVector())
return;

pProjectile->SetStaticUntilCollisionLoaded();

if (pOriginSource)
pProjectile->AttachSatchelToEntity(pOriginSource, vecAttachOffsetPosition, vecAttachOffsetRotation);
}

void CClientProjectileManager::ProcessPendingCreations()
{
if (m_PendingCreations.empty())
return;

// Generous timeout: the creator's ped/vehicle just needs to come within the game's own streaming distance,
// which can take a while if whoever it belongs to is approaching on foot from the edge of sync range.
constexpr long long PENDING_CREATION_TIMEOUT = 60000;
const long long llNow = GetTickCount64_();

for (auto iter = m_PendingCreations.begin(); iter != m_PendingCreations.end();)
{
SPendingProjectileCreation& pending = *iter;

bool bResolved = false;
CClientEntity* pCreator = CElementIDs::GetElement(pending.creatorID);
if (pCreator)
{
if (pCreator->GetType() == CCLIENTPED || pCreator->GetType() == CCLIENTPLAYER)
{
CClientVehicle* pVehicle = static_cast<CClientPed*>(pCreator)->GetOccupiedVehicle();
if (pVehicle)
pCreator = pVehicle;
}

CClientEntity* pTargetEntity = NULL;
if (pending.targetID != INVALID_ELEMENT_ID)
pTargetEntity = CElementIDs::GetElement(pending.targetID);

CClientProjectile* pProjectile = Create(pCreator, pending.weaponType, pending.vecOrigin, pending.fForce, NULL, pTargetEntity);
if (pProjectile)
{
pProjectile->Initiate(pending.vecOrigin, pending.vecRotation, pending.vecVelocity, pending.usModel);

CClientEntity* pOriginSource = NULL;
if (pending.originSourceID != INVALID_ELEMENT_ID)
pOriginSource = CElementIDs::GetElement(pending.originSourceID);
SettleResyncedSatchel(pProjectile, pending.weaponType, pending.fForce, pending.vecVelocity, pOriginSource, pending.vecAttachOffsetPosition,
pending.vecAttachOffsetRotation);

bResolved = true;
}
}

if (bResolved || (llNow - pending.llCreationTime) > PENDING_CREATION_TIMEOUT)
iter = m_PendingCreations.erase(iter);
else
++iter;
}
}

void CClientProjectileManager::RemoveAll()
Expand Down
35 changes: 35 additions & 0 deletions Client/mods/deathmatch/logic/CClientProjectileManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "CClientProjectile.h"
#include <list>
#include <vector>

typedef void(ProjectileInitiateHandler)(CClientProjectile*);
class CClientManager;
Expand Down Expand Up @@ -41,13 +42,29 @@ class CClientProjectileManager
float fForce, CVector* target, CEntity* pGameTarget);
CClientProjectile* Create(CClientEntity* pCreator, eWeaponType eWeapon, CVector& vecOrigin, float fForce, CVector* target, CClientEntity* pTargetEntity);

// The creator's in-game ped/vehicle may not have streamed in yet (e.g. they just connected, or we only just came
// into range of a projectile they planted earlier while out of view). Queue the creation and keep retrying for a
// while instead of silently dropping it.
void QueuePendingCreation(ElementID creatorID, eWeaponType eWeapon, const CVector& vecOrigin, float fForce, ElementID targetID, ElementID originSourceID,
const CVector& vecRotation, const CVector& vecVelocity, unsigned short usModel, bool bHasAttachOffset,
const CVector& vecAttachOffsetPosition, const CVector& vecAttachOffsetRotation);

// Pins a satchel that was just placed by a rest-position resync (zero velocity/force) in place instead of
// letting physics run on it: the area's collision might not be streamed in yet, and if it was originally stuck
// to a vehicle/ped it should keep following it, at the exact same spot it was stuck at, instead of snapping to
// the vehicle/ped's origin (https://github.com/multitheftauto/mtasa-blue/issues/369, #368).
void SettleResyncedSatchel(CClientProjectile* pProjectile, eWeaponType weaponType, float fForce, const CVector& vecVelocity, CClientEntity* pOriginSource,
const CVector& vecAttachOffsetPosition, const CVector& vecAttachOffsetRotation);

protected:
void AddToList(CClientProjectile* pProjectile) { m_List.push_back(pProjectile); }
void RemoveFromList(CClientProjectile* pProjectile);

void TakeOutTheTrash();

private:
void ProcessPendingCreations();

CClientManager* m_pManager;
std::list<CClientProjectile*> m_List;

Expand All @@ -56,4 +73,22 @@ class CClientProjectileManager

bool m_bCreating;
CClientProjectilePtr m_pLastCreated;

struct SPendingProjectileCreation
{
ElementID creatorID;
eWeaponType weaponType;
CVector vecOrigin;
float fForce;
ElementID targetID;
ElementID originSourceID;
CVector vecRotation;
CVector vecVelocity;
unsigned short usModel;
bool bHasAttachOffset;
CVector vecAttachOffsetPosition;
CVector vecAttachOffsetRotation;
long long llCreationTime;
};
std::vector<SPendingProjectileCreation> m_PendingCreations;
};
Loading
Loading