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

E2 Collision Core(now part of propcore) #3099

Merged
merged 14 commits into from
Jul 22, 2024
30 changes: 29 additions & 1 deletion lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,32 @@ E2Helper.Descriptions["setEyeTarget(e:v)"] = "For NPCs, sets the eye target to t
E2Helper.Descriptions["setEyeTargetLocal(e:v)"] = "Sets the eye target to the local eye position"
E2Helper.Descriptions["setEyeTargetWorld(e:v)"] = "Sets the eye target to the world position"
E2Helper.Descriptions["setFlexScale(e:n)"] = "Sets the flex scale of the entity"
E2Helper.Descriptions["setFlexWeight"] = "Sets the weight of the flex"
E2Helper.Descriptions["setFlexWeight"] = "Sets the weight of the flex"
E2Helper.Descriptions["trackCollision(e)"] = "Starts tracking collisions for the entity, will fire event entityCollision when they occur. Does not track when players or vehicles hit world, only other entities.\nNeeds event entityCollision(entity, entity, collision) in order to run.\nReturns 1 on success or 0 on error in non-strict"
E2Helper.Descriptions["trackCollision(ef)"] = "Starts tracking collisions for the entity, will call the provided function, then fire event entityCollision when they occur.\nMay track without event entityCollision. Passed callback function needs argument signature of (eexcd), aka (entity, entity, collision)\nFor more info see trackCollision(e)"
E2Helper.Descriptions["isTrackingCollision(e)"] = "Returns 1 if the entity's collisions are already being tracked, 0 if not. Errors on an invalid ent"
E2Helper.Descriptions["stopTrackingCollision(e)"] = "Stops tracking collisions for the entity.\nError in strict if entity is invalid or entity isn't being tracked"
E2Helper.Descriptions["hitPos(xcd:)"] = "Returns a vector of where the collision ocurred"
E2Helper.Descriptions["pos(xcd:)"] = E2Helper.Descriptions["hitPos(xcd:)"] .. "\nAlias of hitPos(xcd:)"
E2Helper.Descriptions["position(xcd:)"] = E2Helper.Descriptions["pos(xcd:)"]
E2Helper.Descriptions["ourOldVelocity(xcd:)"] = "Returns a vector of the velocity of the tracked entity before the collision occurred."
E2Helper.Descriptions["entityOldVelocity(xcd:)"] = E2Helper.Descriptions["ourOldVelocity(xcd:)"] .. "\nAlias of ourOldVelocity(xcd:)"
E2Helper.Descriptions["theirOldVelocity(xcd:)"] = "Returns a vector of the velocity of the hit entity before the collision occurred"
E2Helper.Descriptions["hitEntityOldVelocity(xcd:)"] = E2Helper.Descriptions["theirOldVelocity(xcd:)"] .. "\nAlias of theirOldVelocity(xcd:)"
E2Helper.Descriptions["hitNormal(xcd:)"] = "Returns the hitnormal(vector) of the surface on the tracked entity that hit the other entity"
E2Helper.Descriptions["hitSpeed(xcd:)"] = "Returns a vector of the speed the impact occurred with"
E2Helper.Descriptions["ourNewVelocity(xcd:)"] = "Returns a vector of the velocity of the tracked entity after the collision occurred."
E2Helper.Descriptions["entityNewVelocity(xcd:)"] = E2Helper.Descriptions["ourNewVelocity(xcd:)"] .. "\nAlias of ourNewVelocity(xcd:)"
E2Helper.Descriptions["theirNewVelocity(xcd:)"] = "Returns a vector of the velocity of the hit entity after the collision occurred."
E2Helper.Descriptions["hitEntityNewVelocity(xcd:)"] = E2Helper.Descriptions["theirNewVelocity(xcd:)"] .. "\nAlias of theirNewVelocity(xcd:)"
E2Helper.Descriptions["ourOldAngularVelocity(xcd:)"] = "Returns a vector of the angular velocity of the tracked entity before the collision occurred."
E2Helper.Descriptions["entityOldAngularVelocity(xcd:)"] = E2Helper.Descriptions["ourOldAngularVelocity(xcd:)"] .. "\nAlias of ourOldAngularVelocity(xcd:)"
E2Helper.Descriptions["theirOldAngularVelocity(xcd:)"] = "Returns a vector of the angular velocity of the hit entity before the collision occurred."
E2Helper.Descriptions["hitEntityOldAngularVelocity(xcd:)"] = E2Helper.Descriptions["theirOldAngularVelocity(xcd:)"] .. "\nAlias of ourOldAngularVelocity(xcd:)"
E2Helper.Descriptions["speed(xcd:)"] = "Returns a number representing the speed at which the collision occurred."
E2Helper.Descriptions["ourSurfaceProps(xcd:)"] = "Returns a number representing the surface properties of the tracked entity"
E2Helper.Descriptions["entitySurfaceProps(xcd:)"] = E2Helper.Descriptions["ourSurfaceProps(xcd:)"] .. "\nAlias of ourSurfaceProps(xcd:)"
E2Helper.Descriptions["theirSurfaceProps(xcd:)"] = "Returns a number representing the surface properties of the hit entity"
E2Helper.Descriptions["hitEntitySurfaceProps(xcd:)"] = E2Helper.Descriptions["theirSurfaceProps(xcd:)"] .. "\nAlias of theirSurfaceProps(xcd:)"
E2Helper.Descriptions["deltaTime(xcd:)"] = "Returns a number representing how long ago the last collision between the tracked entity and the hit entity was, in seconds.\nCapped at 1 second."
E2Helper.Descriptions["hitEntity(xcd:)"] = "Returns the entity that was hit for this collision."
316 changes: 316 additions & 0 deletions lua/entities/gmod_wire_expression2/core/custom/prop.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1277,3 +1277,319 @@ registerCallback("destruct",
end
end
)

-- * Collision tracking

registerType("collision", "xcd", nil,
nil,
nil,
nil,
function(v)
return not istable(v) or not v.HitPos
end
)

-- These are just the types we care about
-- Helps filter out physobjs cause that's not an e2 type
local typefilter = {
entity = "e",
vector = "v",
number = "n",
}

local newE2Table = E2Lib.newE2Table

__e2setcost(20)

e2function table collision:toTable()
local E2CD = newE2Table()
for k,v in pairs(this) do
local type = typefilter[string.lower(type(v))]
if type then
if type == "v" then
-- These need to be given copies, otherwise E2s modifications will propagate.
E2CD.s[k] = Vector(v)
else
E2CD.s[k] = v
end
E2CD.stypes[k] = type
end
end
return E2CD
end

-- Getter functions below, sorted by return type

__e2setcost(5)

local function GetHitPos(self,collision)
if not collision then return self:throw("Invalid collision data!") end
return Vector(collision.HitPos)
end

-- * Vectors

e2function vector collision:hitPos()
return GetHitPos(self,this)
end

e2function vector collision:pos()
return GetHitPos(self,this)
end

e2function vector collision:position()
return GetHitPos(self,this)
end

e2function vector collision:ourOldVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.OurOldVelocity)
end

e2function vector collision:entityOldVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.OurOldVelocity)
end

e2function vector collision:theirOldVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.TheirOldVelocity)
end

e2function vector collision:hitEntityOldVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.TheirOldVelocity)
end

e2function vector collision:hitNormal()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.HitNormal)
end

e2function vector collision:hitSpeed()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.HitSpeed)
end

e2function vector collision:ourNewVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.OurNewVelocity)
end

e2function vector collision:entityNewVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.OurNewVelocity)
end

e2function vector collision:theirNewVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.TheirNewVelocity)
end

e2function vector collision:hitEntityNewVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.TheirNewVelocity)
end

e2function vector collision:ourOldAngularVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.OurOldAngularVelocity)
end

e2function vector collision:entityOldAngularVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.OurOldAngularVelocity)
end

e2function vector collision:theirOldAngularVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.TheirOldAngularVelocity)
end

e2function vector collision:hitEntityOldAngularVelocity()
if not this then return self:throw("Invalid collision data!",Vector(0,0,0)) end
return Vector(this.TheirOldAngularVelocity)
end

-- * Numbers
__e2setcost(2)

e2function number collision:speed()
if not this then return self:throw("Invalid collision data!",0) end
return this.Speed
end

e2function number collision:ourSurfaceProps()
if not this then return self:throw("Invalid collision data!",0) end
return this.OurSurfaceProps
end

e2function number collision:entitySurfaceProps()
if not this then return self:throw("Invalid collision data!",0) end
return this.OurSurfaceProps
end

e2function number collision:theirSurfaceProps()
if not this then return self:throw("Invalid collision data!",0) end
return this.TheirSurfaceProps
end

e2function number collision:hitEntitySurfaceProps()
if not this then return self:throw("Invalid collision data!",0) end
return this.TheirSurfaceProps
end

e2function number collision:deltaTime()
if not this then return self:throw("Invalid collision data!",0) end
return this.DeltaTime
end

-- * Entities

e2function entity collision:hitEntity()
if not this then return self:throw("Invalid collision data!",Entity(0)) end
return this.HitEntity
end


__e2setcost( 20 )

local processNextTick = false
local registered_chips = {}

local function E2CollisionEventHandler()
for chip,ctx in pairs(registered_chips) do
if IsValid(chip) then
if not chip.error then
for _,i in ipairs(ctx.data.E2QueuedCollisions) do
if i.cb then
-- Arguments for this were checked when we set it up, no need to typecheck
i.cb:UnsafeCall({i.us,i.xcd.HitEntity,i.xcd})
if chip.error then break end
end
-- It's okay to ExecuteEvent regardless, it'll just return when it fails to find the registered event
chip:ExecuteEvent("entityCollision",{i.us,i.xcd.HitEntity,i.xcd})
if chip.error then break end
end
end
-- Wipe queued collisions regardless of error
ctx.data.E2QueuedCollisions = {}
end
end
processNextTick = false
end

local function startCollisionTracking(self,ent,entIndex,lambda)
local ctx = self
local callbackID = ent:AddCallback("PhysicsCollide",
function( us, cd )
table.insert(ctx.data.E2QueuedCollisions,{us=us,xcd=cd,cb=lambda})
if not processNextTick then
processNextTick = true
timer.Simple(0,E2CollisionEventHandler) -- A timer set to 0 runs next GM:Tick() hook
end
end)
self.data.E2TrackedCollisions[entIndex] = callbackID -- This ID is needed to remove the physcollide callback
ent:CallOnRemove("E2Chip_CCB" .. callbackID, function()
self.data.E2TrackedCollisions[entIndex] = nil
end)
end

e2function number trackCollision( entity ent )
-- If it's not registered, collisions will just stack up infinitely and not be flushed.
if not registered_chips[self.entity] then
self:forceThrow("event entityCollision(eexcd) is needed to use trackCollision(e)!")
end
if IsValid(ent) then
local entIndex = ent:EntIndex()
if self.data.E2TrackedCollisions[entIndex] then
return self:throw("Attempting to track collisions for an already tracked entity",0)
end
startCollisionTracking(self,ent,entIndex)
return 1
end
return self:throw("Attempting to track collisions for an invalid entity",0)
end

e2function number trackCollision( entity ent, function cb )
Copy link
Member Author

Choose a reason for hiding this comment

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

This is going to break glualint but it's valid & works ingame, fairly certain this may be the only vanilla e2 function(not automatically generated) that takes a lambda at present.

-- However, since this one IS providing a callback, we can just register it and run the CB
if not registered_chips[self.entity] then
registered_chips[self.entity] = self
end
if IsValid(ent) then
local entIndex = ent:EntIndex()
if self.data.E2TrackedCollisions[entIndex] then
return self:throw("Attempting to track collisions for an already tracked entity",0)
end
-- First, double check the arg sig lines up
Copy link
Contributor

@Vurv78 Vurv78 Aug 6, 2024

Choose a reason for hiding this comment

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

Just FYI, This is what Function:Unwrap is for, in case you didn't know. Although you used other Function: Methods, so you probably did. Maybe a separate method for just checking the arguments would suffice.

function Function:Unwrap(arg_sig, ctx)

Copy link
Member Author

@DerelictDrone DerelictDrone Aug 6, 2024

Choose a reason for hiding this comment

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

I had overlooked unwrap, it would be nice if it could be passed an optional format/prepended string though so I can use the wording of the error message here but if you don't think that's needed I'll get a PR up soon to replace this check and msg with the unwrap

if cb.arg_sig ~= "eexcd" then
local arg_sig = "(void)"
if #cb.arg_sig > 0 then
arg_sig = "("..cb.arg_sig..")"
end
self:forceThrow("Collision callback expecting arguments (eexcd), got "..arg_sig)
end
startCollisionTracking(self,ent,entIndex,cb)
return 1
end
return self:throw("Attempting to track collisions for an invalid entity",0)
end

__e2setcost( 5 )

e2function number isTrackingCollision( entity ent )
if not IsValid(ent) then
return self:throw("Attempting to check tracking of collisions for an invalid entity",0)
end
if self.data.E2TrackedCollisions[ent:EntIndex()] then
return 1
else
return 0
end
end

e2function void stopTrackingCollision( entity ent )
if IsValid(ent) then
local entIndex = ent:EntIndex()
if self.data.E2TrackedCollisions[entIndex] then
local callbackID = self.data.E2TrackedCollisions[entIndex]
ent:RemoveCallOnRemove("E2Chip_CCB" .. callbackID)
ent:RemoveCallback("PhysicsCollide", callbackID)
self.data.E2TrackedCollisions[entIndex] = nil
else
return self:throw("Attempting to stop tracking collisions for an untracked entity",nil)
end
else
return self:throw("Attempting to stop tracking collisions for an invalid entity",nil)
end
end

registerCallback("construct", function( self )
self.data.E2TrackedCollisions = {}
self.data.E2QueuedCollisions = {}
end)

registerCallback("destruct", function( self )
for k,v in pairs(self.data.E2TrackedCollisions) do
local ent = Entity(tonumber(k))
if IsValid(ent) then
ent:RemoveCallOnRemove("E2Chip_CCB" .. v)
ent:RemoveCallback("PhysicsCollide", v)
end
end
-- Moved from event destructor to general destructor
-- Cause now it can be dynamically registered for callback functions
registered_chips[self.entity] = nil
end)



E2Lib.registerEvent("entityCollision", {
{"Entity", "e"},
{"HitEntity", "e"},
{"CollisionData", "xcd"},
},
function(ctx) -- Event constructor
registered_chips[ctx.entity] = ctx
end
)
Loading