785f55a017
The meta object is extended by some commonly used functionality that Garry's Mod does not expose directly. Some of these are Server only and will produce wrong results on the Client, also due to Garry's Mod not exposing things properly.
260 lines
7.9 KiB
Lua
260 lines
7.9 KiB
Lua
--[[
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2017 Xaymar
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
--]]
|
|
|
|
-- Finds the player meta table or terminates
|
|
local meta = FindMetaTable("Player")
|
|
if !meta then print("FAILED TO FIND PLAYER META") return end
|
|
|
|
-- Blinds the player by setting view out into the void
|
|
function meta:Blind(bool)
|
|
if !self:IsValid() then return end
|
|
|
|
if SERVER then
|
|
umsg.Start("SetBlind", self)
|
|
if bool then
|
|
umsg.Bool(true)
|
|
else
|
|
umsg.Bool(false)
|
|
end
|
|
umsg.End()
|
|
elseif CLIENT then
|
|
blind = bool
|
|
end
|
|
end
|
|
|
|
-- Blinds the player by setting view out into the void
|
|
function meta:RemoveProp()
|
|
if CLIENT || !self:IsValid() then return end
|
|
|
|
if self.ph_prop && self.ph_prop:IsValid() then
|
|
self.ph_prop:Remove()
|
|
self.ph_prop = nil
|
|
end
|
|
end
|
|
|
|
-- Sets a new Hull for a player.
|
|
function meta:NewHull(hullOBBMin, hullOBBMax)
|
|
if !self:IsValid() then return end
|
|
if hullOBBMax == nil then return end
|
|
if hullOBBMin == nil then return end
|
|
|
|
local hullOBB = hullOBBMax - hullOBBMin
|
|
local hullOBBXY = math.max(hullOBB.x, hullOBB.y)
|
|
|
|
local xyMul = 0.5
|
|
local hullMin = Vector(-hullOBBXY * xyMul, -hullOBBXY * xyMul, 0)
|
|
local hullMax = Vector( hullOBBXY * xyMul, hullOBBXY * xyMul, hullOBB.z)
|
|
|
|
self:SetHull(hullMin, hullMax)
|
|
self:SetHullDuck(hullMin, hullMax)
|
|
self:SetViewOffset(Vector(0, 0, hullOBB.z))
|
|
self:SetViewOffsetDucked(Vector(0, 0, hullOBB.z / 2.0))
|
|
end
|
|
|
|
-- Can the passed entity be used by the player?
|
|
function testflag(set, flag)
|
|
if (flag == 0) then return true end -- Mod(%) by 0 is nan.
|
|
return (set % (2 * flag)) >= flag
|
|
end
|
|
|
|
function meta:IsUseableEntity(ent, requiredCaps)
|
|
if ((ent != nil) && (ent:IsValid())) then
|
|
local caps = ent:ObjectCaps()
|
|
local capsmask = 16 + 32 + 64 + 128
|
|
if (testflag(caps, 16)
|
|
|| testflag(caps, 32)
|
|
|| testflag(caps, 64)
|
|
|| testflag(caps, 128)
|
|
) && testflag(caps, requiredCaps) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function IntervalDistance(x, x0, x1)
|
|
-- swap so x0 < x1
|
|
if ( x0 > x1 ) then
|
|
local tmp = x0
|
|
x0 = x1
|
|
x1 = tmp
|
|
end
|
|
|
|
if ( x < x0 ) then
|
|
return x0-x
|
|
elseif ( x > x1 ) then
|
|
return x - x1
|
|
end
|
|
return 0
|
|
end
|
|
|
|
-- Find useable entity (Lua version of CBasePlayer:FindUseEntity)
|
|
function meta:FindUseEntity()
|
|
-- https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/0d8dceea4310fde5706b3ce1c70609d72a38efdf/mp/src/game/shared/baseplayer_shared.cpp
|
|
local PLAYER_USE_RADIUS = 80
|
|
|
|
-- Vectors
|
|
local forward = self:EyeAngles():Forward()
|
|
local up = self:EyeAngles():Up()
|
|
local center = self:EyePos()
|
|
|
|
local trace = {
|
|
start = center,
|
|
endpos = center,
|
|
mins = Vector(-16, -16, -16),
|
|
maxs = Vector( 16, 16, 16),
|
|
mask = MASK_SOLID + CONTENTS_DEBRIS + CONTENTS_PLAYERCLIP,
|
|
filter = function(ent)
|
|
if (ent == self) then return false end
|
|
if (!ent:IsValid()) then return false end
|
|
if (ent:IsPlayer()) then return false end
|
|
if (ent == self:GetHands()) then return false end
|
|
return true
|
|
end,
|
|
output = {}
|
|
}
|
|
|
|
local foundEnt = nil
|
|
local nearestDist = 16777216
|
|
local nearestEnt = nil
|
|
|
|
local tangents_num = 8
|
|
local tangents = {}
|
|
tangents[1] = 0
|
|
tangents[2] = 1
|
|
tangents[3] = 0.57735026919
|
|
tangents[4] = 0.3639702342
|
|
tangents[5] = 0.267949192431
|
|
tangents[6] = 0.1763269807
|
|
tangents[7] = -0.1763269807
|
|
tangents[8] = -0.267949192431
|
|
for idx=1,tangents_num,1 do
|
|
if (idx == 1) then
|
|
trace.endpos = center + forward * 1024
|
|
util.TraceLine(trace)
|
|
else
|
|
local down = forward - (Vector(tangents[idx], tangents[idx], tangents[idx]) * up)
|
|
down:Normalize()
|
|
trace.endpos = center + down * 72
|
|
util.TraceHull(trace)
|
|
end
|
|
foundEnt = trace.output.Entity
|
|
|
|
local useable = self:IsUseableEntity(foundEnt, 0)
|
|
while ((foundEnt:IsValid()) && !useable && (foundEnt:GetMoveParent():IsValid())) do
|
|
foundEnt = foundEnt:GetMoveParent()
|
|
useable = self:IsUseableEntity(foundEnt, 0)
|
|
end
|
|
|
|
if (useable) then
|
|
local delta = trace.output.HitPos - trace.output.StartPos
|
|
local centerZ = foundEnt:WorldSpaceCenter().z
|
|
delta.z = IntervalDistance(trace.output.HitPos.z, centerZ + foundEnt:OBBMins().z, centerZ + foundEnt:OBBMaxs().z)
|
|
local dist = delta:Length()
|
|
if (dist < PLAYER_USE_RADIUS) then
|
|
if (foundEnt:IsNPC() && (foundEnt:Team() == self:Team())) then
|
|
foundEnt = self:DoubleCheckUseNPC(foundEnt, center, forward)
|
|
end
|
|
--if (dist < nearestDist) then -- Not identical to CBasePlayer
|
|
--nearestDist = dist
|
|
nearestEnt = foundEnt
|
|
--end
|
|
if (idx == 1) then return foundEnt end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- check ground entity first
|
|
-- if you've got a useable ground entity, then shrink the cone of this search to 45 degrees
|
|
-- otherwise, search out in a 90 degree cone (hemisphere)
|
|
if (self:GetGroundEntity():IsValid() && self:IsUseableEntity(self:GetGroundEntity(), 256)) then
|
|
nearestEnt = self:GetGroundEntity()
|
|
end
|
|
if (nearestEnt) then
|
|
local point = self:NearestPoint(center)
|
|
nearestDist = util.DistanceToLine(point, center, forward)
|
|
end
|
|
|
|
local search = ents.FindInSphere(center, PLAYER_USE_RADIUS)
|
|
for k,v in ipairs(search) do
|
|
if (v) && (v:IsValid()) && (self:IsUseableEntity(v, 512)) then
|
|
local point = v:NearestPoint(center)
|
|
local dir = (point - center):GetNormalized()
|
|
local dot = dir:Dot(forward)
|
|
if (dot >= 0.8) then
|
|
local dist = util.DistanceToLine(point, center, forward)
|
|
if (dist < nearestDist) then
|
|
trace.endpos = point
|
|
util.TraceLine(trace)
|
|
if ((trace.output.Fraction == 1.0) || (trace.output.Entity == v)) then
|
|
nearestEnt = v
|
|
nearestDist = dist
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if (!nearestEnt) then
|
|
trace.endpos = center + forward * PLAYER_USE_RADIUS
|
|
trace.mask = MASK_OPAQUE_AND_NPCS
|
|
util.TraceLine(trace)
|
|
if (trace.output.Entity
|
|
&& trace.output.Entity:IsValid()
|
|
&& self:IsUseableEntity(trace.output.Entity, 0)
|
|
&& trace.output.Entity:IsNPC()
|
|
&& (trace.output.Entity:Team() == self:Team())) then
|
|
nearestEnt = trace.output.Entity
|
|
end
|
|
end
|
|
if (foundEnt:IsNPC() && (foundEnt:Team() == self:Team())) then
|
|
foundEnt = self:DoubleCheckUseNPC(foundEnt, center, forward)
|
|
end
|
|
|
|
return nearestEnt
|
|
end
|
|
|
|
-- Double Check NPC
|
|
-- Perhaps a poorly-named function. This function traces against the supplied
|
|
-- NPC's hitboxes (instead of hull). If the trace hits a different NPC, the
|
|
-- new NPC is selected. Otherwise, the supplied NPC is determined to be the
|
|
-- one the citizen wants. This function allows the selection of a citizen over
|
|
-- another citizen's shoulder, which is impossible without tracing against
|
|
-- hitboxes instead of the hull (sjb)
|
|
function meta:DoubleCheckUseNPC(npc, src, dir)
|
|
local trace = {
|
|
start = src,
|
|
endpos = src + dir * 1024,
|
|
mask = MASK_SHOT,
|
|
result = {}
|
|
}
|
|
util.TraceLine(trace)
|
|
if ((trace.result.Entity != nil) && (trace.result.Entity:IsValid()) && (trace.result.Entity:IsNPC()) && (trace.result.Entity != npc)) then
|
|
-- Player is selecting a different NPC through some negative space
|
|
-- in the first NPC's hitboxes (between legs, over shoulder, etc).
|
|
return trace.result.Entity
|
|
end
|
|
return npc
|
|
end
|