diff --git a/Client/mods/deathmatch/logic/CClientEntity.cpp b/Client/mods/deathmatch/logic/CClientEntity.cpp index cc4ea3d7057..038daddc6f0 100644 --- a/Client/mods/deathmatch/logic/CClientEntity.cpp +++ b/Client/mods/deathmatch/logic/CClientEntity.cpp @@ -1334,6 +1334,13 @@ bool CClientEntity::IsOnScreen() { return pEntity->IsOnScreen(); } + + if (GetType() == CCLIENTMARKER) + { + CClientMarker* marker = static_cast(this); + return marker->IsClientSideOnScreen(); + } + return false; } diff --git a/Client/mods/deathmatch/logic/CClientEntity.h b/Client/mods/deathmatch/logic/CClientEntity.h index 031c1418b8f..c961c79a779 100644 --- a/Client/mods/deathmatch/logic/CClientEntity.h +++ b/Client/mods/deathmatch/logic/CClientEntity.h @@ -84,6 +84,7 @@ enum eClientEntityType class CEntity; class CClientColShape; +class CClientMarker; class CClientPed; class CCustomData; class CElementGroup; diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index 899a68cf0d8..acd3d5fabd4 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -10,6 +10,7 @@ *****************************************************************************/ #include "StdInc.h" +#include #include #include #include @@ -75,6 +76,10 @@ CVector g_vecBulletFireEndPosition; #define DOUBLECLICK_TIMEOUT 330 #define DOUBLECLICK_MOVE_THRESHOLD 10.0f +// Ray casting constants for click detection +constexpr float CLICK_RAY_DEPTH = 300.0f; // Screen-to-world ray projection depth +constexpr float MAX_CLICK_DISTANCE = 6000.0f; // Maximum distance for closest marker comparison + static constexpr long long TIME_DISCORD_UPDATE_RATE = 15000; CClientGame::CClientGame(bool bLocalPlay) : m_ServerInfo(new CServerInfo()) @@ -2337,6 +2342,48 @@ void CClientGame::ProcessServerControlBind(CControlFunctionBind* pBind) m_pNetAPI->RPC(KEY_BIND, bitStream.pBitStream); } +CClientMarker* CClientGame::GetClickedMarker(const CVector& vecOrigin, const CVector& vecTarget, float& fDistance) const +{ + if (!m_pMarkerManager) + return nullptr; + + CVector rayDirection = (vecTarget - vecOrigin); + rayDirection.Normalize(); + CClientMarker* closestMarker = nullptr; + float closestDistance = MAX_CLICK_DISTANCE; + + for (auto* marker : m_pMarkerManager->m_Markers) + { + if (!marker || !marker->IsStreamedIn() || !marker->IsVisible()) + continue; + + if (!marker->IsClientSideOnScreen()) + continue; + + const CSphere boundingSphere = marker->GetWorldBoundingSphere(); + + const CVector toSphere = boundingSphere.vecPosition - vecOrigin; + const float projection = toSphere.DotProduct(&rayDirection); + + if (projection <= 0.0f) + continue; + + const CVector closestPoint = vecOrigin + rayDirection * projection; + const float distanceToRay = (boundingSphere.vecPosition - closestPoint).Length(); + + if (distanceToRay <= boundingSphere.fRadius && projection < closestDistance) + { + closestDistance = projection; + closestMarker = marker; + } + } + + if (closestMarker) + fDistance = closestDistance; + + return closestMarker; +} + bool CClientGame::ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { bool bCursorForcedVisible = g_pCore->IsCursorForcedVisible(); @@ -2386,7 +2433,7 @@ bool CClientGame::ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wPa CVector2D vecCursorPosition((float)iX, (float)iY); - CVector vecOrigin, vecTarget, vecScreen((float)iX, (float)iY, 300.0f); + CVector vecOrigin, vecTarget, vecScreen((float)iX, (float)iY, CLICK_RAY_DEPTH); g_pCore->GetGraphics()->CalcWorldCoors(&vecScreen, &vecTarget); // Grab the camera position @@ -2404,32 +2451,50 @@ bool CClientGame::ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wPa CVector vecCollision; ElementID CollisionEntityID = INVALID_ELEMENT_ID; - CClientEntity* pCollisionEntity = NULL; + CClientEntity* collisionEntity = nullptr; + float closestDistance = std::numeric_limits::max(); + + // Check standard entity collision distance if (bCollision && pColPoint) { vecCollision = pColPoint->GetPosition(); + closestDistance = (vecCollision - vecOrigin).Length(); + if (pGameEntity) { CPools* pPools = g_pGame->GetPools(); CClientEntity* pEntity = pPools->GetClientEntity((DWORD*)pGameEntity->GetInterface()); if (pEntity) { - pCollisionEntity = pEntity; + collisionEntity = pEntity; if (!pEntity->IsLocalEntity()) CollisionEntityID = pEntity->GetID(); } } } - else + + // Check marker collision and compare distances + float markerDistance = std::numeric_limits::max(); + CClientMarker* clickedMarker = GetClickedMarker(vecOrigin, vecTarget, markerDistance); + + // Use marker if it's closer than any entity collision + if (clickedMarker && markerDistance < closestDistance) + { + collisionEntity = clickedMarker; + if (!clickedMarker->IsLocalEntity()) + CollisionEntityID = clickedMarker->GetID(); + clickedMarker->GetPosition(vecCollision); + closestDistance = markerDistance; + } + else if (!bCollision || !pColPoint) { + // No entity collision found, use target position vecCollision = vecTarget; } // Destroy the colpoint so we don't get a leak if (pColPoint) - { pColPoint->Destroy(); - } const char* szButton = NULL; const char* szState = NULL; @@ -2478,8 +2543,8 @@ bool CClientGame::ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wPa Arguments.PushNumber(vecCollision.fX); Arguments.PushNumber(vecCollision.fY); Arguments.PushNumber(vecCollision.fZ); - if (pCollisionEntity) - Arguments.PushElement(pCollisionEntity); + if (collisionEntity) + Arguments.PushElement(collisionEntity); else Arguments.PushBoolean(false); m_pRootEntity->CallEvent("onClientClick", Arguments, false); @@ -2522,8 +2587,8 @@ bool CClientGame::ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wPa DoubleClickArguments.PushNumber(vecCollision.fX); DoubleClickArguments.PushNumber(vecCollision.fY); DoubleClickArguments.PushNumber(vecCollision.fZ); - if (pCollisionEntity) - DoubleClickArguments.PushElement(pCollisionEntity); + if (collisionEntity) + DoubleClickArguments.PushElement(collisionEntity); else DoubleClickArguments.PushBoolean(false); m_pRootEntity->CallEvent("onClientDoubleClick", DoubleClickArguments, false); @@ -2552,7 +2617,7 @@ bool CClientGame::ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wPa CVector2D vecResolution = g_pCore->GetGUI()->GetResolution(); CVector2D vecCursorPosition(((float)iX) / vecResolution.fX, ((float)iY) / vecResolution.fY); - CVector vecTarget, vecScreen((float)iX, (float)iY, 300.0f); + CVector vecTarget, vecScreen((float)iX, (float)iY, CLICK_RAY_DEPTH); g_pCore->GetGraphics()->CalcWorldCoors(&vecScreen, &vecTarget); // Call the onClientCursorMove event diff --git a/Client/mods/deathmatch/logic/CClientGame.h b/Client/mods/deathmatch/logic/CClientGame.h index 74d64f7660f..1eaf0dbaf5b 100644 --- a/Client/mods/deathmatch/logic/CClientGame.h +++ b/Client/mods/deathmatch/logic/CClientGame.h @@ -382,6 +382,7 @@ class CClientGame void ProcessServerControlBind(CControlFunctionBind* pBind); bool ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + CClientMarker* GetClickedMarker(const CVector& vecOrigin, const CVector& vecTarget, float& fDistance) const; bool AreCursorEventsEnabled() { return m_bCursorEventsEnabled; } void SetCursorEventsEnabled(bool bCursorEventsEnabled) { m_bCursorEventsEnabled = bCursorEventsEnabled; } diff --git a/Client/mods/deathmatch/logic/CClientMarker.cpp b/Client/mods/deathmatch/logic/CClientMarker.cpp index 7afc3c7311b..fe6f7a9ca34 100644 --- a/Client/mods/deathmatch/logic/CClientMarker.cpp +++ b/Client/mods/deathmatch/logic/CClientMarker.cpp @@ -16,6 +16,11 @@ extern CClientGame* g_pClientGame; #define M_PI 3.14159265358979323846 #endif +// Threshold for determining if a marker is considered on-screen. +// The value 0.1f represents the minimum depth (Z value in screen coordinates) at which a marker is visible. +// Markers with a screen Z value below this threshold are considered off-screen. +constexpr float CLIENT_MARKER_ONSCREEN_THRESHOLD = 0.1f; + unsigned int CClientMarker::m_uiStreamedInMarkers = 0; CClientMarker::CClientMarker(CClientManager* pManager, ElementID ID, int iMarkerType) : ClassInit(this), CClientStreamElement(pManager->GetMarkerStreamer(), ID) @@ -322,7 +327,7 @@ void CClientMarker::SetSize(float fSize) break; } } - + m_pMarker->SetSize(fSize); } @@ -540,3 +545,36 @@ void CClientMarker::SetIgnoreAlphaLimits(bool ignore) { m_pMarker->SetIgnoreAlphaLimits(ignore); } + +bool CClientMarker::IsClientSideOnScreen() +{ + if (!IsStreamedIn() || !IsVisible()) + return false; + + CVector position; + GetPosition(position); + + CVector screen; + g_pCore->GetGraphics()->CalcScreenCoors(&position, &screen); + + if (screen.fZ <= CLIENT_MARKER_ONSCREEN_THRESHOLD) + return false; + + float resWidth = static_cast(g_pCore->GetGraphics()->GetViewportWidth()); + float resHeight = static_cast(g_pCore->GetGraphics()->GetViewportHeight()); + + CSphere boundingSphere = GetWorldBoundingSphere(); + CVector edgePos = boundingSphere.vecPosition; + edgePos.fX += boundingSphere.fRadius; + + CVector edgeScreen; + g_pCore->GetGraphics()->CalcScreenCoors(&edgePos, &edgeScreen); + + if (edgeScreen.fZ <= CLIENT_MARKER_ONSCREEN_THRESHOLD) + return true; + + float screenRadius = fabs(edgeScreen.fX - screen.fX); + + return (screen.fX + screenRadius) >= 0.0f && (screen.fX - screenRadius) <= resWidth && + (screen.fY + screenRadius) >= 0.0f && (screen.fY - screenRadius) <= resHeight; +} diff --git a/Client/mods/deathmatch/logic/CClientMarker.h b/Client/mods/deathmatch/logic/CClientMarker.h index 4aee90b93d9..4baac43eba3 100644 --- a/Client/mods/deathmatch/logic/CClientMarker.h +++ b/Client/mods/deathmatch/logic/CClientMarker.h @@ -77,6 +77,8 @@ class CClientMarker final : public CClientStreamElement, private CClientColCallb static bool IsLimitReached(); + bool IsClientSideOnScreen(); + CClientColShape* GetColShape() { return m_pCollision; } void Callback_OnCollision(CClientColShape& Shape, CClientEntity& Entity);