Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(extra-natives-five): add Collision Detection system [CLIENT] #3117

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

xalva98
Copy link

@xalva98 xalva98 commented Jan 31, 2025

Goal of this PR

The main goal is to provide a native-side collision management system for scripts, so script authors no longer have to run their own loops to check wheter or not a player has entered/left a certain area.

This PR introduces an internal collision/colshape management mechanism that periodically checks the player’s position at a C++ level and triggers events (onPlayerEnterColshape / onPlayerLeaveColshape) automatically.

How is this PR achieving the goal

Implements a grid-based colshape manager in C++ that stores and updates collision shapes (circles, spheres, cubes, rectangles, cylinders.
Provides new natives (CREATE_COLSHAPE_CIRCLE, CREATE_COLSHAPE_CUBE, CREATE_COLSHAPE_CYLINDER, CREATE_COLSHAPE_RECTANGLE, CREATE_COLSHAPE_SPHERE, CREATE_COLSHAPE_POLYZONE, DELETE_COLSHAPE, DOES_COLSHAPE_EXIST, SET_COLSHAPE_SYSTEM_ENABLED, SET_COLSHAPE_SYSTEM_UPDATE_INTERVAL, GET_IS_COLSHAPE_SYSTEM_ENABLED) that scripts can call to register shapes with the manager.
The manager runs on a background thread/timer, checks the local player’s position against these shapes, and triggers events onPlayerEnterColshape and onPlayerLeaveColshape.
Scripts can listen for these events instead of writing a bunch of code that checks coordinates and checks for collision (usually a sphere #(vec3 - vec3) in lua.

There are also new Convars:
colShapeMinBound, colShapeMaxBound that determine world borders (in which the system is active)
colShapeCellSize which determines the cell size on the grid
colShapeAutoInfiniteThreshold which determines how large a colshape has to be for the system to consider it 'infinite' (check even when you're not in the grid)
colShapeMaxInfiniteShapeDistance a threshold value that ignores infinite shapes that are further away than this.

Basic values for these are in the code down below:

#ifdef GTA_FIVE
	static ConVar<float> colShapeMinBound("colShapeMinBound", ConVar_Archive, -10000.0f);
	static ConVar<float> colShapeMaxBound("colShapeMaxBound", ConVar_Archive, 10000.0f);
#elif defined(IS_RDR3) // rdr3 map is lager than gta5
	static ConVar<float> colShapeMinBound("colShapeMinBound", ConVar_Archive, -15000.0f);
	static ConVar<float> colShapeMaxBound("colShapeMaxBound", ConVar_Archive, 15000.0f);
#else
	static ConVar<float> colShapeMinBound("colShapeMinBound", ConVar_Archive, -10000.0f);
	static ConVar<float> colShapeMaxBound("colShapeMaxBound", ConVar_Archive, 10000.0f);
#endif
static ConVar<float> colShapeCellSize("colShapeCellSize", ConVar_Archive, 500.0f);
static ConVar<float> colShapeAutoInfiniteThreshold("colShapeAutoInfiniteThreshold", ConVar_Archive, 1000.0f);
static ConVar<float> colShapeMaxInfiniteShapeDistance("colShapeMaxInfiniteShapeDistance", ConVar_Archive, 1000.0f);

This PR applies to the following area(s)

FiveM, (should work for RedM aswell, not tested), Lua, C#, JS
Client Natives

Successfully tested on

Game builds: 3095 (FiveM), not tested on RedM
Platforms: Windows

--------------------------------------------------------------------------------
-- ShapeManager definition
--------------------------------------------------------------------------------
ShapeManager = {
    shapes = {},   -- [shapeId] = { type=..., x=..., etc. }
    defaultColor   = { r = 255, g = 0,   b = 0,   a = 100 }, -- Red
    highlightColor = { r = 0,   g = 255, b = 0,   a = 150 }, -- Green
}

--------------------------------------------------------------------------------
-- Circle
--------------------------------------------------------------------------------
function ShapeManager:CreateCircle(x, y, z, radius)
    local shapeId = CreateColshapeCircle(x, y, z, radius)
    if shapeId == -1 then
        print("^1[Colshape] Failed to create circle!^0")
        return
    end
    
    self.shapes[shapeId] = {
        type   = "circle",
        x      = x,
        y      = y,
        z      = z,
        radius = radius,
        color  = self.defaultColor,
    }
    return shapeId
end

--------------------------------------------------------------------------------
-- Cube
--------------------------------------------------------------------------------
function ShapeManager:CreateCube(x1, y1, z1, x2, y2, z2)
    local shapeId = CreateColshapeCube(x1, y1, z1, x2, y2, z2)
    if shapeId == -1 then
        print("^1[Colshape] Failed to create cube!^0")
        return
    end
    
    self.shapes[shapeId] = {
        type = "cube",
        x1   = x1, y1 = y1, z1 = z1,
        x2   = x2, y2 = y2, z2 = z2,
        color = self.defaultColor,
    }
    return shapeId
end

--------------------------------------------------------------------------------
-- Cylinder
--------------------------------------------------------------------------------
function ShapeManager:CreateCylinder(x, y, z, radius, height)
    local shapeId = CreateColshapeCylinder(x, y, z, radius, height)
    if shapeId == -1 then
        print("^1[Colshape] Failed to create cylinder!^0")
        return
    end
    
    self.shapes[shapeId] = {
        type   = "cylinder",
        x      = x,
        y      = y,
        z      = z,
        radius = radius,
        height = height,
        color  = self.defaultColor,
    }
    return shapeId
end

--------------------------------------------------------------------------------
-- Rectangle
--------------------------------------------------------------------------------
function ShapeManager:CreateRectangle(x1, y1, x2, y2, bottomZ, height)
    local shapeId = CreateColshapeRectangle(x1, y1, x2, y2, bottomZ, height)
    if shapeId == -1 then
        print("^1[Colshape] Failed to create rectangle!^0")
        return
    end
    
    self.shapes[shapeId] = {
        type   = "rectangle",
        x1     = x1,
        y1     = y1,
        x2     = x2,
        y2     = y2,
        z      = bottomZ,
        height = height,
        color  = self.defaultColor,
    }
    return shapeId
end

--------------------------------------------------------------------------------
-- Sphere
--------------------------------------------------------------------------------
function ShapeManager:CreateSphere(x, y, z, radius)
    local shapeId = CreateColshapeSphere(x, y, z, radius)
    if shapeId == -1 then
        print("^1[Colshape] Failed to create sphere!^0")
        return
    end
    
    self.shapes[shapeId] = {
        type   = "sphere",
        x      = x,
        y      = y,
        z      = z,
        radius = radius,
        color  = self.defaultColor,
    }
    return shapeId
end

--------------------------------------------------------------------------------
-- Polyzone
--------------------------------------------------------------------------------
function ShapeManager:CreatePolyzone(points, minZ, maxZ)
    local packedData = msgpack.pack(points)
    local dataLen    = packedData:len()
    if dataLen == 0 then
        print("^1[Colshape] Polyzone data is empty^0")
        return
    end

    local shapeId
    if minZ and maxZ then
        shapeId = CreateColshapePolyzone(packedData, dataLen, minZ, maxZ)
    else
        shapeId = CreateColshapePolyzone(packedData, dataLen)
        minZ = -10000.0
        maxZ =  10000.0
    end

    if shapeId == -1 then
        print("^1[Colshape] Failed to create polyzone^0")
        return
    end

    self.shapes[shapeId] = {
        type     = "polyzone",
        vertices = points,
        minZ     = minZ,
        maxZ     = maxZ,
        color    = self.defaultColor,
    }
    return shapeId
end

--------------------------------------------------------------------------------
-- Drawing logic
--------------------------------------------------------------------------------

local function DrawSphereShape(shape)
    DrawSphere(
        shape.x, shape.y, shape.z,
        shape.radius,
        shape.color.r, shape.color.g, shape.color.b,
        shape.color.a / 255.0
    )
end

local function DrawCircleMarker(shape)
    DrawMarker(
        1,
        shape.x, shape.y, shape.z - 1.0,
        0,0,0, 0,0,0,
        shape.radius*2.0, shape.radius*2.0, 1.0,
        shape.color.r, shape.color.g, shape.color.b, shape.color.a,
        false,false,2,false,nil,nil,false
    )
end

local function DrawCube(x1, y1, z1, x2, y2, z2, color)
    local minX, maxX = math.min(x1,x2), math.max(x1,x2)
    local minY, maxY = math.min(y1,y2), math.max(y1,y2)
    local minZ, maxZ = math.min(z1,z2), math.max(z1,z2)
    local corners = {
        {minX,minY,minZ}, {maxX,minY,minZ}, {maxX,maxY,minZ}, {minX,maxY,minZ},
        {minX,minY,maxZ}, {maxX,minY,maxZ}, {maxX,maxY,maxZ}, {minX,maxY,maxZ},
    }
    local edges = {
        {1,2},{2,3},{3,4},{4,1},
        {5,6},{6,7},{7,8},{8,5},
        {1,5},{2,6},{3,7},{4,8}
    }
    for _, e in ipairs(edges) do
        local v1 = corners[e[1]]
        local v2 = corners[e[2]]
        DrawLine(v1[1],v1[2],v1[3], v2[1],v2[2],v2[3],
                 color.r,color.g,color.b,color.a)
    end
end

local function DrawCylinder(x, y, z, radius, height, color, segments)
    local step = (2*math.pi)/(segments or 32)
    local bottomZ = z
    local topZ    = z + height
    for i=0,(segments or 32)-1 do
        local a1,a2 = i*step, (i+1)*step
        local x1 = x + radius*math.cos(a1)
        local y1 = y + radius*math.sin(a1)
        local x2 = x + radius*math.cos(a2)
        local y2 = y + radius*math.sin(a2)

        DrawLine(x1,y1,bottomZ, x2,y2,bottomZ, color.r,color.g,color.b,color.a)
        DrawLine(x1,y1,topZ,    x2,y2,topZ,    color.r,color.g,color.b,color.a)
        DrawLine(x1,y1,bottomZ, x1,y1,topZ,    color.r,color.g,color.b,color.a)
    end
end

local function DrawRectangleShape(x1,y1,z, x2,y2, _, height, color)
    local minX,maxX = math.min(x1,x2), math.max(x1,x2)
    local minY,maxY = math.min(y1,y2), math.max(y1,y2)
    local minZ,maxZ = z,z+height
    local corners = {
        {minX,minY,minZ}, {maxX,minY,minZ}, {maxX,maxY,minZ}, {minX,maxY,minZ},
        {minX,minY,maxZ}, {maxX,minY,maxZ}, {maxX,maxY,maxZ}, {minX,maxY,maxZ},
    }
    local edges = {
        {1,2},{2,3},{3,4},{4,1},
        {5,6},{6,7},{7,8},{8,5},
        {1,5},{2,6},{3,7},{4,8}
    }
    for _, e in ipairs(edges) do
        local v1 = corners[e[1]]
        local v2 = corners[e[2]]
        DrawLine(v1[1],v1[2],v1[3], v2[1],v2[2],v2[3],
                 color.r,color.g,color.b,color.a)
    end
end

local function safeGetZ(vec)
    local ok,val=pcall(function() return vec.z end)
    if ok and type(val)=="number" then return val end
    return 0.0
end

local function drawPolyZone(shape, color)
    local zonePoints = shape.vertices
    local bottomZ = shape.minZ or -10000.0
    local topZ    = shape.maxZ or  10000.0
    local zOffset = topZ - bottomZ
    local r, g, b, a = color.r, color.g, color.b, color.a

    for i = 1, #zonePoints do
        local currentPoint = zonePoints[i]
        local nextPoint    = zonePoints[(i % #zonePoints) + 1]

        local cX, cY = currentPoint.x, currentPoint.y
        local nX, nY = nextPoint.x,    nextPoint.y

        local cZ = bottomZ
        local nZ = bottomZ
        DrawPoly(cX, cY, cZ,  nX, nY, nZ,  cX, cY, cZ + zOffset,  r, g, b, a)
        DrawPoly(nX, nY, nZ,  nX, nY, nZ + zOffset,  cX, cY, cZ + zOffset,  r, g, b, a)

        DrawPoly(cX, cY, cZ + zOffset,  nX, nY, nZ + zOffset,  cX, cY, cZ,  r, g, b, a)
        DrawPoly(nX, nY, nZ + zOffset,  nX, nY, nZ,            cX, cY, cZ,  r, g, b, a)

        DrawLine(cX, cY, cZ,            nX, nY, nZ,            255, 0, 0, 255)
        DrawLine(cX, cY, cZ + zOffset,   nX, nY, nZ + zOffset,   255, 0, 0, 255)
        DrawLine(cX, cY, cZ,            cX, cY, cZ + zOffset,   255, 0, 0, 255)
    end
end

function ShapeManager:DrawColShape(shape)
    if not shape or not shape.color then return end

    if shape.type=="circle" then
        DrawCircleMarker(shape)
    elseif shape.type=="cube" then
        DrawCube(shape.x1,shape.y1,shape.z1, shape.x2,shape.y2,shape.z2, shape.color)
    elseif shape.type=="cylinder" then
        DrawCylinder(shape.x, shape.y, shape.z, shape.radius, shape.height, shape.color, 64)
    elseif shape.type=="rectangle" then
        DrawRectangleShape(shape.x1,shape.y1,shape.z, shape.x2,shape.y2,shape.z, shape.height, shape.color)
    elseif shape.type=="sphere" then
        DrawSphereShape(shape)
    elseif shape.type=="polyzone" then
        drawPolyZone(shape, shape.color)
    end
end


--------------------------------------------------------------------------------
-- Example command: create shapes near the player's ground level
--------------------------------------------------------------------------------
RegisterCommand("createcolshapes", function()
    local ped = PlayerPedId()
    local coords = GetEntityCoords(ped)
    local x,y,z  = coords.x, coords.y, coords.z

    -- 1) Circle
    ShapeManager:CreateCircle(x+10.0, y, z, 5.0)

    -- 2) Cube
    ShapeManager:CreateCube(x-15.0, y-15.0, z-1.0, x-5.0, y-5.0, z+5.0)

    -- 3) Cylinder
    ShapeManager:CreateCylinder(x, y+20.0, z-1.0, 6.0, 10.0)

    -- 4) Rectangle
    ShapeManager:CreateRectangle(x+20.0, y-20.0, x+30.0, y-10.0, z-1.0, 5.0)

    -- 5) Sphere
    ShapeManager:CreateSphere(x-20.0, y+20.0, z, 7.0)

    local vecpoints = {
        vector4(x+20 + math.random(1,10), y+30 + math.random(1,10), 1.0, 0.0),
        vector4(x+50 + math.random(1,10), y+30 + math.random(1,10), 1.0, 0.0),
        vector4(x+50 + math.random(1,10), y+50 + math.random(1,10), 1.0, 0.0),
        vector4(x+20 + math.random(1,10), y+50 + math.random(1,10), 1.0, 0.0),
    }
    ShapeManager:CreatePolyzone(vecpoints, z - 1.0, z+15.0)

    local vecpoints_3 = {
        vector3(x-20 + math.random(1,10), y-30 + math.random(1,10), z),
        vector3(x-50 + math.random(1,10), y-30 + math.random(1,10), z),
        vector3(x-50 + math.random(1,10), y-50 + math.random(1,10), z),
        vector3(x-20 + math.random(1,10), y-50 + math.random(1,10), z),
    }

    ShapeManager:CreatePolyzone(vecpoints_3, z - 1.0, z+15.0)

    local vecPointsNoMinMax = {
        vector3(x-2 + math.random(1,10), y-4 + math.random(1,10), z),
        vector3(x-5+ math.random(1,10), y-4 + math.random(1,10), z),
        vector3(x-5 + math.random(1,10), y-8 + math.random(1,10), z),
        vector3(x-2 + math.random(1,10), y-8 + math.random(1,10), z),
    }

    ShapeManager:CreatePolyzone(vecPointsNoMinMax)


end, false)

--------------------------------------------------------------------------------
-- Command to delete shapes
--------------------------------------------------------------------------------
RegisterCommand("deletecolshape", function(_, args)
    local shapeId = tonumber(args[1])
    if not shapeId then
        print("Usage: /deletecolshape <id>")
        return
    end
    local success = DeleteColshape(shapeId)
    print("DeleteColshape(", shapeId, ") =>", success)
    if success then
        ShapeManager.shapes[shapeId] = nil
    end
end, false)

--------------------------------------------------------------------------------
-- Toggle system + interval
--------------------------------------------------------------------------------
local isEnabled = false

RegisterCommand("activate", function()
    isEnabled = not isEnabled
    SetColshapeSystemEnabled(isEnabled)
    print("Colshape system is now", isEnabled and "enabled" or "disabled")
end)

RegisterCommand("updateinterval", function(_, args)
    local interval = tonumber(args[1])
    if interval then
        SetColshapeSystemUpdateInterval(interval)
        print("Update interval set to", interval)
    else
        print("Usage: /updateinterval <ms>")
    end
end)

RegisterCommand("deleteallcolshapes", function()
    for i = 0, 1000 do
        DeleteColshape(i)
        ShapeManager.shapes[i] = nil
    end
end)

AddEventHandler("onResourceStop", function(resourceName)
    if resourceName == GetCurrentResourceName() then
        for shapeId, _ in pairs(ShapeManager.shapes) do
            DeleteColshape(shapeId)
        end
    end
end)



-- ------------------------------------------------------------------------------
-- Main loop: debug-draw shapes every frame
-- ------------------------------------------------------------------------------
-- if u use the command below you should comment this out!
Citizen.CreateThread(function()
    while true do
        Citizen.Wait(0)
        for shapeId, shape in pairs(ShapeManager.shapes) do
            ShapeManager:DrawColShape(shape)
        end
    end
end)


--------------------------------------------------------------------------------
-- Enter/Leave events: highlight shapes
--------------------------------------------------------------------------------

local activeShapes = {}

local function DrawWhileInside(shapeId)
    CreateThread(function()
        while activeShapes[shapeId] do
            Citizen.Wait(0)
            ShapeManager:DrawColShape(ShapeManager.shapes[shapeId])
        end
    end)
end

AddEventHandler("onPlayerEnterColshape", function(shapeId)
    print("Player entered colshape:", shapeId)
    local shape = ShapeManager.shapes[shapeId]
    if shape then
        shape.color = ShapeManager.highlightColor
        activeShapes[shapeId] = true
        DrawWhileInside(shapeId)
    end
end)

AddEventHandler("onPlayerLeaveColshape", function(shapeId)
    print("Player left colshape:", shapeId)
    local shape = ShapeManager.shapes[shapeId]
    if shape then
        shape.color = ShapeManager.defaultColor
        activeShapes[shapeId] = nil
    end
end)


local function getRandomGroundCoords()
    local x = math.random(-5000, 5000)
    local y = math.random(-5000, 5000)
    local z = math.random(-20, 50)
    return x, y, z
end

----------------------------------------------------------------------------
-- Command: create many sets of shapes around the map
----------------------------------------------------------------------------
RegisterCommand("spawnrandomcolshapes", function(source, args)
    -- Number of sets of shapes to spawn, you can "benchmark" with this i guess
    local count = tonumber(args[1]) or 50
    print(("Spawning %d sets of shapes around the map..."):format(count))

    for i = 1, count do
        CreateThread(function()
            local x, y, z = getRandomGroundCoords()

            ShapeManager:CreateCircle(x+10.0, y, z, 5.0)
            ShapeManager:CreateCube(x-15.0, y-15.0, z-1.0, x-5.0, y-5.0, z+5.0)
            ShapeManager:CreateCylinder(x + 1.0, y+20.0, z-1.0, 10.0, 50.0)
            ShapeManager:CreateRectangle(x+20.0, y-20.0, x+30.0, y-10.0, z-1.0, 5.0)
            ShapeManager:CreateSphere(x-20.0, y+20.0, z, 40.0)
        
            local vecpoints = {
                vector4(x+20 + math.random(1,10), y+30 + math.random(1,10), 1.0, 0.0),
                vector4(x+50 + math.random(1,10), y+30 + math.random(1,10), 1.0, 0.0),
                vector4(x+50 + math.random(1,10), y+50 + math.random(1,10), 1.0, 0.0),
                vector4(x+20 + math.random(1,10), y+50 + math.random(1,10), 1.0, 0.0),
            }
            ShapeManager:CreatePolyzone(vecpoints, z - 1.0, z+15.0)
        
            local vecpoints_3 = {
                vector3(x-20 + math.random(1,10), y-30 + math.random(1,10), z),
                vector3(x-50 + math.random(1,10), y-30 + math.random(1,10), z),
                vector3(x-50 + math.random(1,10), y-50 + math.random(1,10), z),
                vector3(x-20 + math.random(1,10), y-50 + math.random(1,10), z),
            }
        
            ShapeManager:CreatePolyzone(vecpoints_3, z - 1.0, z+15.0)
        
            local vecPointsNoMinMax = {
                vector3(x-2 + math.random(1,10), y-4 + math.random(1,10), z),
                vector3(x-5+ math.random(1,10), y-4 + math.random(1,10), z),
                vector3(x-5 + math.random(1,10), y-8 + math.random(1,10), z),
                vector3(x-2 + math.random(1,10), y-8 + math.random(1,10), z),
            }
        
            ShapeManager:CreatePolyzone(vecPointsNoMinMax)

            Wait(1)
        end)
    end

    print(("Done! Spawned %d sets of shapes around the map."):format(count))
end, false)

Checklist

  • Code compiles and has been tested successfully.
  • Code explains itself well and/or is documented.
  • My commit message explains what the changes do and what they are for.
  • No extra compilation warnings are added by these changes.

@github-actions github-actions bot added the invalid Requires changes before it's considered valid and can be (re)triaged label Jan 31, 2025
@JericoFX
Copy link

This is awesome!!

@CanysLypys
Copy link

We need this guys this is very important!

static auto getPlayerPed = fx::ScriptEngine::GetNativeHandler(HASH_PLAYER_PED_ID);
static auto getEntityCoords = fx::ScriptEngine::GetNativeHandler(HASH_GET_ENTITY_COORDS);

int playerPedId = FxNativeInvoke::Invoke<int>(getPlayerPed);
Copy link
Contributor

Choose a reason for hiding this comment

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

is this safe to be called from a background thread?

Copy link
Author

@xalva98 xalva98 Jan 31, 2025

Choose a reason for hiding this comment

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

I'm honestly not sure, I can just say that it "worked" in testing but there might be other issues since I can't test it with adhesive, there's probably stuff in there that checks if natives get called from non-game threads.

My thought process was basically "make it seperate -> it's gonna be faster and the main thread doesn't get cluttered" but I'm very inexperienced when it comes to this honestly so any advice is appreciated

{
return;
}
scrVector coords = FxNativeInvoke::Invoke<scrVector>(getEntityCoords, playerPedId, true);
Copy link
Contributor

Choose a reason for hiding this comment

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

is this safe to be called from a background thread?

@nnsdev
Copy link

nnsdev commented Jan 31, 2025

Hey, I don't really mean to scope creep here, but this would be incredible if it could also store a set of data per colshape that is msgpacked or something as the second parameter to the functions, it would allow for a lot more flexibility

@xalva98
Copy link
Author

xalva98 commented Jan 31, 2025

Hey, I don't really mean to scope creep here, but this would be incredible if it could also store a set of data per colshape that is msgpacked or something as the second parameter to the functions, it would allow for a lot more flexibility

Hi,

could you provide a js/lua/pseudo code snippet on what exactly you're asking for so i can understand it a little bit better? What is the purpose, what issue do you want to solve with that 👍

Greetings

@nnsdev
Copy link

nnsdev commented Jan 31, 2025

Hi,

could you provide a js/lua/pseudo code snippet on what exactly you're asking for so i can understand it a little bit better? What is the purpose, what issue do you want to solve with that 👍

Greetings

Hey! Yeah sure.

Consider this example: You want to create a set of colshapes for, say, all the different PDs around the map, now all of these are technically the "same" PD functionality wise, but they should be separated from each other, such as parking or similiar systems.

Now you create colshapes with IDs where you can easily check if its a PD (like police-department-{xyz} and you just check if the string starts with police-department. If you didn't have to care about them being separate from each other, this is fine, but once you get into separation you'll end up either exploding the string or looking up information in some table.

On paper with your sample Lua code, you can totally just pull this off by putting this information into the shapes table and build a wrapper out of it. But I think it would be far more beneficiary for everyone if this information was directly stored on the colshape, because then you can look up any form of information you want to store in resources independently, in case you are using these shapes and their events in multiple resources at once.

So I was thinking of a flow like this:

local randomLuaTable = {}

local shapeReference = CreateColshapeRectangle(id, x1, y1, x2, y2, bottomZ, height)
SetShapeData(shapeReference, randomLuaTable)

AddEventHandler("onPlayerEnterColshape", function(shapeId, data)
    print(data) -- data from randomLuaTable, or nil if not present
end)

Alternatively, I really don't know which way is better because I don't really know, you could also just have there be another parameter in the CreateColshape* natives that takes a table.

Hope this makes sense, and there is many ways to do this with what you have right now but I think it would be really interesting to have this natively.

@Yum1x
Copy link

Yum1x commented Jan 31, 2025

Hi,
could you provide a js/lua/pseudo code snippet on what exactly you're asking for so i can understand it a little bit better? What is the purpose, what issue do you want to solve with that 👍
Greetings

Hey! Yeah sure.

Consider this example: You want to create a set of colshapes for, say, all the different PDs around the map, now all of these are technically the "same" PD functionality wise, but they should be separated from each other, such as parking or similiar systems.

Now you create colshapes with IDs where you can easily check if its a PD (like police-department-{xyz} and you just check if the string starts with police-department. If you didn't have to care about them being separate from each other, this is fine, but once you get into separation you'll end up either exploding the string or looking up information in some table.

On paper with your sample Lua code, you can totally just pull this off by putting this information into the shapes table and build a wrapper out of it. But I think it would be far more beneficiary for everyone if this information was directly stored on the colshape, because then you can look up any form of information you want to store in resources independently, in case you are using these shapes and their events in multiple resources at once.

So I was thinking of a flow like this:

local randomLuaTable = {}

local shapeReference = CreateColshapeRectangle(id, x1, y1, x2, y2, bottomZ, height)
SetShapeData(shapeReference, randomLuaTable)

AddEventHandler("onPlayerEnterColshape", function(shapeId, data)
    print(data) -- data from randomLuaTable, or nil if not present
end)

Alternatively, I really don't know which way is better because I don't really know, you could also just have there be another parameter in the CreateColshape* natives that takes a table.

Hope this makes sense, and there is many ways to do this with what you have right now but I think it would be really interesting to have this natively.

IMO, that should be handled in script-level and each dev should create their own data management.
I think that this feature shouldn't be considered as drop replacement from PolyZone, also should be good a way to update colshape options.

@Yum1x
Copy link

Yum1x commented Jan 31, 2025

Is there a specific reason to return a boolean from CREATE_* functions? As I remember many(or all) create-like functions by R* returns a int representing the handle and 0 or -1 when a fail occours.

@xalva98
Copy link
Author

xalva98 commented Jan 31, 2025

Is there a specific reason to return a boolean from CREATE_* functions? As I remember many(or all) create-like functions by R* returns a int representing the handle and 0 or -1 when a fail occours.

There is no reason for this at all and we can change that if it's the recommended way of doing things. This was more or less just my intution. It is my first time ever working with this codebase 🤷

@JericoFX
Copy link

JericoFX commented Feb 4, 2025

any feedback from R* on this one?, this is awesome dont let this sleep like the state bag feature.

removed unused imports and unused code comments

// use game thread instead of seperate thread as suggested by Heron
static auto lastUpdateTime = std::chrono::high_resolution_clock::now();
OnMainGameFrame.Connect([]()
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about a method to enable the colshape manager? Could be a native or a ConVar. This would also allow adjusting the hardcoded bounds depending on the requirements of the server that is using it.

return;
}
// Args: colShapeId, x, y, z, radius, (bool infinite)
std::string colShapeId = context.CheckArgument<const char*>(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

ids should be ints to match the other entity handles. Also to match the existing natives the create methods should generate these handles itself.

Copy link
Author

Choose a reason for hiding this comment

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

I'll change that asap, I'll also add natives where you can control the update time and activate the entire system / deactivate it when called from any scripting runtime as you suggested. Thanks for the feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
invalid Requires changes before it's considered valid and can be (re)triaged
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants