Jump to content

Projectile position, and detection


dantashost

Recommended Posts

Basically, I've created invisible "markers" using circular colshapes to detect intruders/hostiles and prevent them from entering a certain person's "base". So far, so good, onColShapeHit detected those, but it does not work for projectiles, one for instance could throw a grenade at the base bombarding everything within its radius and consequently damaging possible personal stuff stored inside, like vehicles.

The question is how can I detect and redirect (set his Z coordinate like 15units above) a FIRED projectile so it doesn't damage the base's content?

Thanks in advance!

PS.: Projectile is classified as an element, is this a bug?

@Edit

(This will sound similar to my first thread lol) While I'm still interested in how projectiles are handled, I found the https://wiki.multitheftauto.com/wiki/OnClientExplosion client event, the downside is that it's not handled server-side, but I could check if the distance between my colshape and the explosion is within the colshape radius (oh glorious geometric math) and cancelEvent, successfully preventing damage (not sure about animations though).

Link to comment

Projectiles are elements. There is a function getElementsWithinColShape but it does not support projectiles. You can projectile's position to check if it's within the col shape and if it is then destroy the projectile (destroyElement). I'd use onClientRender and a little delay like 300ms. If you're having a shpere then it's easy to check if it's within colshape since there is a function getDistanceBetweenPoints3D (use colshape's and projectile's position) you can check if the distance is shorter than radius of the sphere colshape and destroy the projectile if it is. If it's a cylindrical (circle) shape then you can use getDistanceBetweenPoints2D (and use only X and Y coords). If the colshape is a rectangle then there is more calculating, you'd need to create a function like isPointInRectangle and and then compare the values (if projectile's X pos > left edge of rectangle and projectile's Y pos > top edge of rectangle and projectile's X < right edge of rectangle and projectile's Y pos < bottom edge of rectangle, then the projectile is within the rectangle). Similar for cuboids but with addition of Z.

There is no other way I can think of.

Link to comment

That is nice and better than checking the explosion, thanks, but... what do you mean by delay? Is that a timer delay? If so I could add a timer when a weapon fires (I believe there's an event for that) and kill it afterwards (though I'm not sure if it's better to create 'n kill timers everytime, or have a fixed delay to check for projectiles on every frame rendering).

@Edit

Wait, I think we didn't communicate good enough. I don't know the projectile, how do I know what projectile I'm checking against the colshapes? I need a source-independent event, this is, any projectile fired against that specific colshape must be destroyed but I can't predict what it is/where it comes from.

Link to comment

You can use timers but I've heard many complaints about timers lagging. I'd do a constant check in case other resources use createProjectile and by delay I mean check tickcount between each check and then do the action, like:

local iDelay = 300; 
local iLastCheckTick = 0; 
addEventHandler( "onClientRender", root,  
    function() 
        local iCurTick = getTickCount(); 
        if iCurTick - iLastCheckTick > iDelay then -- check if current tick count minus last time check > delay (which is 300 in this case) 
            -- DO ALL THE CHECKING HERE 
            iLastCheckTick = iCurTick; -- assign the current tick as the last check tick count 
        end 
    end 
) 
  

Link to comment

Ok, now I'm getting a bid mad, I can't for god's sake retrieve a colSphere radius (getElementRadius doesn't allow that), can someone help me?

if node then 
    local posX = xmlNodeGetAttribute(node, "posX") 
    local posY = xmlNodeGetAttribute(node, "posY") 
    local posZ = xmlNodeGetAttribute(node, "posZ") 
    local radius = xmlNodeGetAttribute(node, "radius") 
    local colMarker = createColSphere(posX, posY, posZ, radius) 
    if colMarker then -- If collision shape was created succesfully, attach other data to it 
        outputChatBox("marker created with "..tostring(getElementData(colMarker, "radius")).."radius!") 
        setElementID(colMarker, xmlNodeGetAttribute(node, "id")) 
        setElementData(colMarker, "baseId", xmlNodeGetAttribute(node, "baseId")) 
    end 
end 

Link to comment

1. xmlNodeGetAttribute returns string so you have to convert the string to a number (tonumber) because createColSphere expects numbers.

2. You haven't explained what exactly doesn't work, you said you can't get radius but how do you know that? What makes you think that way?

Link to comment

The problem isn't creating the colshape, actually everything is set correctly (expected parameter DOES match xmlNode value implicitly), I set the ID for a certain colshape to retrieve the colshape element later on (this works correctly), but I can't get a colshape's radius... getElementRadius(myColShape) neither getElementData(myColShape, "radius") work, to retrieve it I had to setElementData(myColShape, "radius") but this sounds very redundant as the element has an internal radius value already, it's like duplicating it or I'm confusing how I get a created element's data from .map loaded element's data...

Now I got the bases protected from projectiles doing the following:

To be able to check any projectile against a base, I had to create a queue and put every created projectile on this queue using onClientProjectileCreation (I didn't put the projectile element itself because I had problems taking it out of the array, so I stored IDs gotten from getElementID instead); onClientRender I checked if my queue (array) was empty, if it was not I looped through every projectile getElementByID(projectileID)'d for each one, and for each projectile I looped through every base (likely to increase in the future) checking if the projectile hit a certain base (sphere colshape vs. projectile), if so I set the projectile position up (z+20) then destroyElement'd it (this caused the bomb to explode! people liked to see it exploding in the air o.O), successfully protecting the base.

This could be achieved better if onColShapeHit detected the projectile, I hope it does in the future. :wink:

Big Code chunk ahead:

-- Protected bases (colshape IDs) 
protectedBases = { 
"colsphere (pbase_marker_1)", 
"colsphere (pbase_marker_2)", 
"colsphere (pbase_marker_3)", 
"colsphere (pbase_marker_4)", 
"colsphere (pbase_marker_5)" 
} 
  
-- Check delay from projectiles against bases 
iDelay = 300 
iLastCheckTick = 0 
projectileCheckCount = 0  -- Counter for ID generation 
projectileCheckList  = {} -- Projectile ID list 
  
-- Plays siren for the player upon touching forbidden area 
function sirenFiredAtBase(ex, ey, ez) 
    local uSound = playSound3D("siren.mp3", ex, ey, ez)  
    setSoundMaxDistance(uSound, 100) 
end 
addEvent("onSirenFiredAtBase", true ) 
addEventHandler("onSirenFiredAtBase", getRootElement(), sirenFiredAtBase) 
  
-- Adds a new projectile ID to our list for later check 
addEventHandler("onClientProjectileCreation", getRootElement(),  
function(creator) 
    -- Adds the projectile to the list 
    projectileCheckCount = projectileCheckCount+1 
    local projectileID = "check-projectile ("..projectileCheckCount..")" 
    setElementID(source, projectileID) 
    table.insert(projectileCheckList, projectileID)--source) 
end) 
  
-- Every 300ms we loop through our projectile list, for each projectile we loop through the base IDs 
-- and check if a projectile entered a base colshape area (thanks to 50p) 
addEventHandler("onClientRender", root, 
function() 
    local iCurTick = getTickCount(); 
    if (iCurTick - iLastCheckTick > iDelay) then -- check if current tick count minus last time check > delay (which is 300 in this case) 
        if (#projectileCheckList > 0) then 
            -- If we projectile list is not empty, we loop through it 
            for k,v in pairs(projectileCheckList) do 
                local theProjectile = getElementByID(projectileCheckList[k]) -- Get the projectile's id 
                for j=1,#protectedBases do 
                    local baseColshape  = getElementByID(protectedBases[j]) -- Get the base's id, check if it exists 
                    if (baseColshape and theProjectile) then 
                        local bx, by, bz = getElementPosition(baseColshape) 
                        local px, py, pz = getElementPosition(theProjectile) 
                        local dist = getDistanceBetweenPoints3D(bx, by, bz, px, py, pz) -- Distance between colshape's center and projectile 
                        local sphereRadius = tonumber(getElementData(baseColshape, "radius")) 
                        -- If the distance between given points is less than colshape's radius, the projectile will be thrown up, 
                        -- destroyed, removed from the projectile list, and we'll break out of the inner 'for' since projectile does 
                        -- not exist anymore. We also fire siren at the player! 
                        if ( dist <= (sphereRadius+10) ) then -- +10 for extra safety, just in case 
                            projectileCheckList[k] = nil 
                            setElementPosition(theProjectile, px, py, pz+30) 
                            destroyElement(theProjectile) 
                            local lpx, lpy, lpz = getElementPosition(getLocalPlayer()) 
                            sirenFiredAtBase(lpx, lpy, lpz) -- YOU SHOULD NOT DO THAT! 
                            break 
                        end -- Bomb entered a base's area? 
                    end -- Valid sphere colshape and projectile? 
                end -- Looped through all the bases for a single projectile 
            end -- Looped through all the projectiles fired during 300ms interval 
        end -- Is there projectiles to be checked? 
        iLastCheckTick = iCurTick; -- assign the current tick as the last check tick count 
    end 
end) 

Link to comment

If you're reading the .map file with XML functions but not loading it as a map (loadMapData or in meta.xml) then you have to set the element's data by attributes manually. So, even posX, posY and posZ is not stored in the colshape element (you can check for yourself).

Anyway, you've done what I suggested and I'm glad you wrote the code yourself! Good luck with the script.

Link to comment
If you're reading the .map file with XML functions but not loading it as a map (loadMapData or in meta.xml) then you have to set the element's data by attributes manually. So, even posX, posY and posZ is not stored in the colshape element (you can check for yourself).

Anyway, you've done what I suggested and I'm glad you wrote the code yourself! Good luck with the script.

Thanks, but wait... is it possible to have colshapes in *.map files and have them be created as such upon loading (not unrecognized elements, that is)? I stored them in a xml file as you said and loaded setting the element's data.

Link to comment

That depends on the gamemode you're running. Every gamemode has an EDF, in which is defined what happens with the different elementtypes in the mapfile. For example, race gamemode has 'racepickup'-elements, but these are only used by the race-gamemode, another gamemode like play won't load these elements.

If the colshapes are not loaded by the gamemode you're using, you could always alter the EDF of that gamemode yourself, so it does load the colshape-elements. Here's some info: https://wiki.multitheftauto.com/wiki/EDF

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...