diff --git a/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua b/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua index 2e7e0767ce..ccb8606c17 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua @@ -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" \ No newline at end of file +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." \ No newline at end of file diff --git a/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/lua/entities/gmod_wire_expression2/core/custom/prop.lua index e60a7b2061..6202ffbb2b 100644 --- a/lua/entities/gmod_wire_expression2/core/custom/prop.lua +++ b/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -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 +)