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
|