Skip to content

Commit

Permalink
E2 Collision Core(now part of propcore) (#3099)
Browse files Browse the repository at this point in the history
* Collision core & event for E2

* Clear trailing whitespace in collision.lua

* Errors in strict mode for tracking

* Aliases for our/their getters as entity/hitentity

* Migrate to propcore

* Forgot to remove collision.lua from extloader

* Getter camelCasing & fixes.

* E2helper documentation for collision & functions

* Proper op costs

* Collisions are now queued and flushed on think

* Collisions only check the tick after a collision

* Event requirements & callback overload

* Forgot to remove a print

* Fit trackCollision descriptions to e2helper box
  • Loading branch information
DerelictDrone authored Jul 22, 2024
1 parent 1e35480 commit c8f38c3
Show file tree
Hide file tree
Showing 2 changed files with 345 additions and 1 deletion.
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 )
-- 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
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
)

0 comments on commit c8f38c3

Please sign in to comment.