Skip to content
Draft
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5e8e3f3
Fix marker screen detection and click events
MohabCodeX Aug 14, 2025
96d6533
Update
MohabCodeX Aug 14, 2025
03c6f61
Merge branch 'multitheftauto:master' into fix/client-side-entity
MohabCodeX Aug 16, 2025
a52e31c
update
MohabCodeX Aug 16, 2025
5929998
Refactor
MohabCodeX Aug 19, 2025
4ba63eb
Merge branch 'multitheftauto:master' into fix/client-side-entity
MohabCodeX Aug 20, 2025
148684c
Add marker click detection
MohabCodeX Aug 20, 2025
a468c71
Update
MohabCodeX Aug 20, 2025
beb6f94
Merge branch 'master' into fix/client-side-entity
MohabCodeX Aug 20, 2025
bcf244b
Update Client/mods/deathmatch/logic/CClientEntity.cpp
MohabCodeX Aug 20, 2025
98a9267
Refactor
MohabCodeX Aug 21, 2025
eb7bde5
Refactor
MohabCodeX Aug 21, 2025
74cbc2b
Merge branch 'master' into fix/client-side-entity
MohabCodeX Aug 21, 2025
5abe1e6
Merge branch 'master' into fix/client-side-entity
MohabCodeX Aug 21, 2025
2e75412
format
MohabCodeX Aug 21, 2025
41cea25
Merge branch 'master' into fix/client-side-entity
MohabCodeX Aug 22, 2025
148ecfb
Merge branch 'master' into fix/client-side-entity
MohabCodeX Aug 22, 2025
1a01ce0
Merge branch 'master' into fix/client-side-entity
MohabCodeX Aug 25, 2025
5266bf9
Merge branch 'master' into fix/client-side-entity
MohabCodeX Aug 26, 2025
e3c8b36
Merge branch 'master' into fix/client-side-entity
MohabCodeX Aug 31, 2025
5da2734
Merge branch 'master' into fix/client-side-entity
MohabCodeX Aug 31, 2025
6ed365f
Merge branch 'master' into fix/client-side-entity
MohabCodeX Sep 3, 2025
0f609ac
Refactor marker click handling
MohabCodeX Sep 3, 2025
7e0eaf7
Remove gta-reversed submodule
MohabCodeX Sep 3, 2025
05c5cef
Merge branch 'master' into fix/client-side-entity
MohabCodeX Sep 5, 2025
d27e8b9
Merge branch 'master' into fix/client-side-entity
MohabCodeX Sep 5, 2025
ea42a23
Merge branch 'master' into fix/client-side-entity
MohabCodeX Sep 5, 2025
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
7 changes: 7 additions & 0 deletions Client/mods/deathmatch/logic/CClientEntity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,13 @@ bool CClientEntity::IsOnScreen()
{
return pEntity->IsOnScreen();
}

if (GetType() == CCLIENTMARKER)
{
CClientMarker* marker = static_cast<CClientMarker*>(this);
return marker->IsClientSideOnScreen();
}

return false;
}

Expand Down
1 change: 1 addition & 0 deletions Client/mods/deathmatch/logic/CClientEntity.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ enum eClientEntityType

class CEntity;
class CClientColShape;
class CClientMarker;
class CClientPed;
class CCustomData;
class CElementGroup;
Expand Down
95 changes: 85 additions & 10 deletions Client/mods/deathmatch/logic/CClientGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,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())
Expand Down Expand Up @@ -2325,6 +2329,58 @@ void CClientGame::ProcessServerControlBind(CControlFunctionBind* pBind)
m_pNetAPI->RPC(KEY_BIND, bitStream.pBitStream);
}

CClientMarker* CClientGame::CheckMarkerClick(float screenX, float screenY, float& distance) noexcept
{
if (!m_pMarkerManager)
return nullptr;

CCamera* camera = g_pGame->GetCamera();
CMatrix cameraMatrix;
camera->GetMatrix(&cameraMatrix);
CVector origin = cameraMatrix.vPos;

CVector target;
CVector screen(screenX, screenY, CLICK_RAY_DEPTH);
g_pCore->GetGraphics()->CalcWorldCoors(&screen, &target);

CVector rayDirection = target - origin;
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;

CSphere boundingSphere = marker->GetWorldBoundingSphere();

CVector toSphere = boundingSphere.vecPosition - origin;
float projection = toSphere.DotProduct(&rayDirection);

if (projection <= 0.0f)
continue;

CVector closestPoint = origin + rayDirection * projection;
float distanceToRay = (boundingSphere.vecPosition - closestPoint).Length();

if (distanceToRay <= boundingSphere.fRadius && projection < closestDistance)
{
closestDistance = projection;
closestMarker = marker;
}
}

if (closestMarker)
distance = closestDistance;

return closestMarker;
}

bool CClientGame::ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
bool bCursorForcedVisible = g_pCore->IsCursorForcedVisible();
Expand Down Expand Up @@ -2374,7 +2430,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
Expand All @@ -2392,7 +2448,8 @@ bool CClientGame::ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wPa

CVector vecCollision;
ElementID CollisionEntityID = INVALID_ELEMENT_ID;
CClientEntity* pCollisionEntity = NULL;
CClientEntity* collisionEntity = nullptr;

if (bCollision && pColPoint)
{
vecCollision = pColPoint->GetPosition();
Expand All @@ -2402,7 +2459,7 @@ bool CClientGame::ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wPa
CClientEntity* pEntity = pPools->GetClientEntity((DWORD*)pGameEntity->GetInterface());
if (pEntity)
{
pCollisionEntity = pEntity;
collisionEntity = pEntity;
if (!pEntity->IsLocalEntity())
CollisionEntityID = pEntity->GetID();
}
Expand All @@ -2415,9 +2472,7 @@ bool CClientGame::ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wPa

// Destroy the colpoint so we don't get a leak
if (pColPoint)
{
pColPoint->Destroy();
}

const char* szButton = NULL;
const char* szState = NULL;
Expand Down Expand Up @@ -2457,6 +2512,25 @@ bool CClientGame::ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wPa
if (std::isnan(vecCollision.fZ))
vecCollision.fZ = 0;

float markerDistance = 0.0f;
CClientMarker* clickedMarker = CheckMarkerClick(static_cast<float>(iX), static_cast<float>(iY), markerDistance);
if (clickedMarker)
{
CVector markerPosition;
clickedMarker->GetPosition(markerPosition);

CLuaArguments MarkerArguments;
MarkerArguments.PushString(szButton);
MarkerArguments.PushString(szState);
MarkerArguments.PushNumber(vecCursorPosition.fX);
MarkerArguments.PushNumber(vecCursorPosition.fY);
MarkerArguments.PushNumber(markerPosition.fX);
MarkerArguments.PushNumber(markerPosition.fY);
MarkerArguments.PushNumber(markerPosition.fZ);
MarkerArguments.PushNumber(markerDistance);
clickedMarker->CallEvent("onClientMarkerClick", MarkerArguments, false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not entirely sure if we need a separate event for this? onClientClick works on all entity types such as object, vehicle, ped, so why have a separate event just for markers?

The main issue with markers is that methods like IsOnScreen or ProcessLineOfSight (used for collision detection) are based on CEntity. But markers, meaning the C3dMarker class, don’t inherit from CEntity, which means they aren’t treated as full-fledged entities at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FileEX
Thanks for the feedback, i think working good now, could you check it please ?

i've tested it with simple script, seems ok.

local marker
local trash

function createElements()
    local player = getLocalPlayer()
    local x, y, z = getElementPosition(player)
    local matrix = getElementMatrix(player)
    

    local forwardX = matrix[2][1]
    local forwardY = matrix[2][2]
    local forwardZ = matrix[2][3]
    

    local markerDistance = 5
    local trashDistance = 7 
    local markerX = x + markerDistance * forwardX
    local markerY = y + markerDistance * forwardY
    local markerZ = z + markerDistance * forwardZ
    local trashX = x + trashDistance * forwardX
    local trashY = y + trashDistance * forwardY
    local trashZ = z + trashDistance * forwardZ 
    

    marker = createMarker(markerX, markerY, markerZ, "cylinder", 1.0, 255, 0, 0, 150)
    
    trash = createObject(1359, trashX, trashY, trashZ)
    
    setTimer(checkOnScreen, 2000, 0)
end

function checkOnScreen()
    if marker then
        local onScreenMarker = isElementOnScreen(marker)
        outputChatBox("Marker on screen: " .. tostring(onScreenMarker))
    end
    if trash then
        local onScreenTrash = isElementOnScreen(trash)
        outputChatBox("Trash on screen: " .. tostring(onScreenTrash))
    end
end

function onClick(button, state, absoluteX, absoluteY, worldX, worldY, worldZ, clickedElement)
    if state == "down" then  
        if clickedElement == marker then
            outputChatBox("Clicked on marker!")
        elseif clickedElement == trash then
            outputChatBox("Clicked on trash!")
        end
    end
end

addEventHandler("onClientResourceStart", resourceRoot, createElements)
addEventHandler("onClientClick", root, onClick)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn’t work properly.

}

// Call the event for the client
CLuaArguments Arguments;
Arguments.PushString(szButton);
Expand All @@ -2466,8 +2540,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);
Expand Down Expand Up @@ -2510,8 +2584,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);
Expand Down Expand Up @@ -2540,7 +2614,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
Expand Down Expand Up @@ -2741,6 +2815,7 @@ void CClientGame::AddBuiltInEvents()
// Marker events
m_Events.AddEvent("onClientMarkerHit", "entity, matchingDimension", nullptr, false);
m_Events.AddEvent("onClientMarkerLeave", "entity, matchingDimension", nullptr, false);
m_Events.AddEvent("onClientMarkerClick", "button, state, screenX, screenY, worldX, worldY, worldZ, distance", nullptr, false);

m_Events.AddEvent("onClientPlayerMarkerHit", "marker, matchingDimension", nullptr, false);
m_Events.AddEvent("onClientPlayerMarkerLeave", "marker, matchingDimension", nullptr, false);
Expand Down
1 change: 1 addition & 0 deletions Client/mods/deathmatch/logic/CClientGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ class CClientGame
void ProcessServerControlBind(CControlFunctionBind* pBind);

bool ProcessMessageForCursorEvents(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
CClientMarker* CheckMarkerClick(float screenX, float screenY, float& distance) noexcept;
bool AreCursorEventsEnabled() { return m_bCursorEventsEnabled; }
void SetCursorEventsEnabled(bool bCursorEventsEnabled) { m_bCursorEventsEnabled = bCursorEventsEnabled; }

Expand Down
40 changes: 39 additions & 1 deletion Client/mods/deathmatch/logic/CClientMarker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -322,7 +327,7 @@ void CClientMarker::SetSize(float fSize)
break;
}
}

m_pMarker->SetSize(fSize);
}

Expand Down Expand Up @@ -540,3 +545,36 @@ void CClientMarker::SetIgnoreAlphaLimits(bool ignore)
{
m_pMarker->SetIgnoreAlphaLimits(ignore);
}

bool CClientMarker::IsClientSideOnScreen() noexcept
{
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<float>(g_pCore->GetGraphics()->GetViewportWidth());
float resHeight = static_cast<float>(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;
}
2 changes: 2 additions & 0 deletions Client/mods/deathmatch/logic/CClientMarker.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class CClientMarker final : public CClientStreamElement, private CClientColCallb

static bool IsLimitReached();

bool IsClientSideOnScreen() noexcept;

CClientColShape* GetColShape() { return m_pCollision; }

void Callback_OnCollision(CClientColShape& Shape, CClientEntity& Entity);
Expand Down