833 lines
No EOL
24 KiB
Lua
833 lines
No EOL
24 KiB
Lua
--[[
|
|
|
|
|
|
Written by Syranide, me@syranide.com
|
|
fixed and updated by minifisch, mail@minifisch.net
|
|
big thanks to Syranide! :)
|
|
|
|
|
|
]]
|
|
--
|
|
if SERVER then
|
|
AddCSLuaFile()
|
|
end
|
|
|
|
if CLIENT then
|
|
local target = {
|
|
active = false
|
|
}
|
|
|
|
local snaptarget = {
|
|
active = false
|
|
}
|
|
|
|
local snapkey = false
|
|
local snaptime = false
|
|
local snaplock = false
|
|
local snapclick = false
|
|
local snapclickfade = 0
|
|
local snapcursor = false
|
|
local snapspawnmenu = false
|
|
|
|
local cache = {
|
|
vPlayerPos = 0,
|
|
vLookPos = 0,
|
|
vLookClipPos = 0,
|
|
vLookVector = 0
|
|
}
|
|
|
|
local condefs = {
|
|
snap_enabled = 1,
|
|
snap_gcboost = 1,
|
|
snap_gcstrength = 125,
|
|
snap_hidegrid = 0,
|
|
snap_clickgrid = 0,
|
|
snap_toggledelay = 0,
|
|
snap_disableuse = 0,
|
|
snap_allentities = 0,
|
|
-- snap_alltools = 0,
|
|
snap_enabletoggle = 0,
|
|
snap_lockdelay = 0.5,
|
|
snap_distance = 250,
|
|
snap_gridlimit = 16,
|
|
snap_gridsize = 8,
|
|
snap_gridalpha = 0.4,
|
|
snap_gridoffset = 0.5,
|
|
snap_boundingbox = 1,
|
|
snap_revertaim = 1,
|
|
snap_centerline = 1
|
|
}
|
|
|
|
local convars = {}
|
|
|
|
for key, value in pairs(condefs) do
|
|
convars[#convars + 1] = key
|
|
end
|
|
|
|
local modelsaveset = {}
|
|
local modeloffsets = {}
|
|
|
|
local function DrawScreenLine(vsA, vsB)
|
|
surface.DrawLine(vsA.x, vsA.y, vsB.x, vsB.y)
|
|
end
|
|
|
|
local function ToScreen(vWorld)
|
|
local vsScreen = vWorld:ToScreen()
|
|
|
|
return Vector(vsScreen.x, vsScreen.y, 0)
|
|
end
|
|
|
|
local function PointToScreen(vPoint)
|
|
if cache.vLookVector:DotProduct(vPoint - cache.vLookClipPos) > 0 then return ToScreen(vPoint) end
|
|
end
|
|
|
|
local function LineToScreen(vStart, vEnd)
|
|
local dotStart = cache.vLookVector:DotProduct(vStart - cache.vLookClipPos)
|
|
local dotEnd = cache.vLookVector:DotProduct(vEnd - cache.vLookClipPos)
|
|
|
|
if dotStart > 0 and dotEnd > 0 then
|
|
return ToScreen(vStart), ToScreen(vEnd)
|
|
elseif dotStart > 0 or dotEnd > 0 then
|
|
local vLength = vEnd - vStart
|
|
local vIntersect = vStart + vLength * ((cache.vLookClipPos:DotProduct(cache.vLookVector) - vStart:DotProduct(cache.vLookVector)) / vLength:DotProduct(cache.vLookVector))
|
|
|
|
if dotStart <= 0 then
|
|
return ToScreen(vIntersect), ToScreen(vEnd)
|
|
else
|
|
return ToScreen(vStart), ToScreen(vIntersect)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function RayQuadIntersect(vOrigin, vDirection, vPlane, vX, vY)
|
|
local vp = vDirection:Cross(vY)
|
|
local d = vX:DotProduct(vp)
|
|
if (d <= 0.0) then return end
|
|
local vt = vOrigin - vPlane
|
|
local u = vt:DotProduct(vp)
|
|
if (u < 0.0 or u > d) then return end
|
|
local v = vDirection:DotProduct(vt:Cross(vX))
|
|
if (v < 0.0 or v > d) then return end
|
|
|
|
return Vector(u / d, v / d, 0)
|
|
end
|
|
|
|
local function OnInitialize()
|
|
for key, value in pairs(condefs) do
|
|
CreateClientConVar(key, value, true, false)
|
|
end
|
|
|
|
for _, filename in ipairs(file.Find('smartsnap_offsets_*.png', "GAME")) do
|
|
local file = file.Read(filename)
|
|
|
|
if file then
|
|
lines = string.Explode("\n", file)
|
|
header = table.remove(lines, 1)
|
|
|
|
if header == "SMARTSNAP_OFFSETS" then
|
|
for _, line in ipairs(lines) do
|
|
local pos = string.find(line, '=')
|
|
|
|
if pos then
|
|
local key = string.lower(string.Trim(string.sub(line, 1, pos - 1)))
|
|
local value = string.Trim(string.sub(line, pos + 1))
|
|
local c = string.Explode(",", value)
|
|
modeloffsets[key] = {tonumber(c[1]), tonumber(c[2]), tonumber(c[3]), tonumber(c[4]), tonumber(c[5]), tonumber(c[6])}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function OnShutDown()
|
|
output = file.Read('smartsnap_offsets_custom.png')
|
|
|
|
if output == nil then
|
|
output = "SMARTSNAP_OFFSETS\n"
|
|
end
|
|
|
|
for model, _ in pairs(modelsaveset) do
|
|
output = output .. model .. '=' .. table.concat(modeloffsets[model], ",") .. "\n"
|
|
end
|
|
|
|
file.Write('smartsnap_offsets_custom.png', output)
|
|
end
|
|
|
|
local function GetDevOffset()
|
|
local model = string.lower(target.entity:GetModel())
|
|
|
|
if modeloffsets[model] == nil then
|
|
modeloffsets[model] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}
|
|
end
|
|
|
|
return modeloffsets[model]
|
|
end
|
|
|
|
concommand.Add("snap_dev_alloffset", function(player, command, arguments)
|
|
if target.active == true then
|
|
if #arguments >= 1 then
|
|
local v = GetDevOffset()
|
|
|
|
for i = 1, 6 do
|
|
v[i] = v[i] + tonumber(arguments[1])
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
concommand.Add("snap_dev_gridoffset", function(player, command, arguments)
|
|
if target.active == true then
|
|
if #arguments >= 1 then
|
|
local v = GetDevOffset()
|
|
v[target.face] = v[target.face] + tonumber(arguments[1])
|
|
end
|
|
end
|
|
end)
|
|
|
|
concommand.Add("snap_dev_saveoffset", function(player, command, arguments)
|
|
if target.active == true then
|
|
local v = GetDevOffset()
|
|
modelsaveset[string.lower(target.entity:GetModel())] = true
|
|
end
|
|
end)
|
|
|
|
local function SnapToggleGrid()
|
|
if (GetConVarNumber("snap_enabled") == 0) then
|
|
RunConsoleCommand('snap_enabled', '1')
|
|
else
|
|
RunConsoleCommand('snap_enabled', '0')
|
|
end
|
|
end
|
|
|
|
local function SnapPress()
|
|
if GetConVarNumber("snap_clickgrid") ~= 0 and not snapclick then
|
|
snapclick = true
|
|
snapclickfade = CurTime()
|
|
elseif GetConVarNumber("snap_clickgrid") == 0 or snapclick then
|
|
if (snaplock or snapcursor) then
|
|
snaptime = false
|
|
else
|
|
local toggledelay = GetConVarNumber("snap_toggledelay")
|
|
|
|
if (toggledelay > 0 and snaptime and snaptime + toggledelay > CurTime()) then
|
|
SnapToggleGrid()
|
|
snaptime = false
|
|
snaplock = false
|
|
else
|
|
snaptime = CurTime()
|
|
end
|
|
end
|
|
|
|
snapkey = target.active
|
|
|
|
if (not snapcursor) then
|
|
snaplock = false
|
|
end
|
|
end
|
|
end
|
|
|
|
local function SnapRelease()
|
|
snapkey = false
|
|
end
|
|
|
|
local function SnapLock()
|
|
snaplock = not snaplock
|
|
end
|
|
|
|
local function OnSpawnMenu()
|
|
snapspawnmenu = true
|
|
end
|
|
|
|
local function OnKeyPress(player, key)
|
|
if (key == IN_USE and GetConVarNumber("snap_disableuse") == 0) then
|
|
SnapPress()
|
|
end
|
|
end
|
|
|
|
local function OnKeyRelease(player, key)
|
|
if (key == IN_USE and GetConVarNumber("snap_disableuse") == 0) then
|
|
SnapRelease()
|
|
end
|
|
end
|
|
|
|
local function OnThink()
|
|
if (vgui.CursorVisible()) then
|
|
if (not snapcursor and snaplock) then
|
|
snaptarget = table.Copy(target)
|
|
end
|
|
|
|
snaptime = false
|
|
snapcursor = true
|
|
else
|
|
if (snapcursor and snaplock) then
|
|
target = snaptarget
|
|
end
|
|
|
|
snapspawnmenu = false
|
|
snapcursor = false
|
|
end
|
|
|
|
if (GetConVarNumber("snap_enabletoggle") ~= 0) then
|
|
if (snapkey and snaptime and not snaplock) then
|
|
if (CurTime() > snaptime + GetConVarNumber("snap_lockdelay")) then
|
|
snaplock = true
|
|
snaptime = false
|
|
end
|
|
end
|
|
end
|
|
|
|
local locked = target.locked and target.active
|
|
target.locked = (snapkey or snaplock and not snapcursor) and target.active
|
|
|
|
if (not target.locked and locked and GetConVarNumber("snap_revertaim") ~= 0) then
|
|
if (snapcursor) then
|
|
local screen = target.entity:LocalToWorld(target.vector):ToScreen()
|
|
gui.SetMousePos(math.Round(screen.x), math.Round(screen.y))
|
|
else
|
|
local angles = (target.entity:LocalToWorld(target.vector) - LocalPlayer():GetShootPos()):Angle()
|
|
LocalPlayer():SetEyeAngles(angles)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function CalculateGridAxis(L)
|
|
local length = L:Length()
|
|
local grid = math.Clamp(math.floor(length / (2 * GetConVarNumber("snap_gridsize"))) * 2, 2, GetConVarNumber("snap_gridlimit"))
|
|
local offset = math.Clamp(GetConVarNumber("snap_gridoffset") / length, 0, 1 / grid)
|
|
local scale = 1 - offset * 2
|
|
|
|
return {
|
|
length = length,
|
|
offset = offset,
|
|
scale = scale,
|
|
grid = grid
|
|
}
|
|
end
|
|
|
|
local function CalculateSnap(X, Y, v)
|
|
local LX = CalculateGridAxis(X)
|
|
local LY = CalculateGridAxis(Y)
|
|
local BX = math.Clamp(math.Round(v.x * LX.grid), 0, LX.grid)
|
|
local BY = math.Clamp(math.Round(v.y * LY.grid), 0, LY.grid)
|
|
|
|
if BX == 1 and v.x < (1 / LX.grid + LX.offset) / 2 then
|
|
BX = 0
|
|
end
|
|
|
|
if BX == LX.grid - 1 and v.x > 1 - (1 / LX.grid + LX.offset) / 2 then
|
|
BX = LX.grid
|
|
end
|
|
|
|
if BY == 1 and v.y < (1 / LY.grid + LY.offset) / 2 then
|
|
BY = 0
|
|
end
|
|
|
|
if BY == LY.grid - 1 and v.y > 1 - (1 / LY.grid + LY.offset) / 2 then
|
|
BY = LY.grid
|
|
end
|
|
|
|
local RX = X * (BX / LX.grid)
|
|
local RY = Y * (BY / LY.grid)
|
|
|
|
if BX == 0 then
|
|
RX = X * math.Clamp(LX.offset, 0, 1 / LX.grid)
|
|
end
|
|
|
|
if BX == LX.grid then
|
|
RX = X * (1 - math.Clamp(LX.offset, 0, 1 / LX.grid))
|
|
end
|
|
|
|
if BY == 0 then
|
|
RY = Y * math.Clamp(LY.offset, 0, 1 / LY.grid)
|
|
end
|
|
|
|
if BY == LY.grid then
|
|
RY = Y * (1 - math.Clamp(LY.offset, 0, 1 / LY.grid))
|
|
end
|
|
|
|
return RX + RY
|
|
end
|
|
|
|
local function DrawGridLines(vOrigin, vSX, vSY, gridLines, offsetX, offsetY, sign)
|
|
local centerline = (GetConVarNumber("snap_centerline") ~= 0)
|
|
local vTemp = vOrigin + vSX * 0.5
|
|
local vX = vTemp + vSY * (offsetY)
|
|
local vY = vTemp + vSY * (1 - offsetY)
|
|
local vOffset, temp
|
|
local xtemp = ToScreen(vX) - ToScreen(vY)
|
|
xtemp:Normalize()
|
|
local vsNormal = xtemp
|
|
|
|
if math.abs(vsNormal.x) < 1 - math.abs(vsNormal.y) then
|
|
temp = -0.5 * sign
|
|
else
|
|
temp = 0.5 * sign
|
|
end
|
|
|
|
if math.abs(vsNormal.x) < math.abs(vsNormal.y) then
|
|
vsOffset = Vector(temp, 0, 0)
|
|
else
|
|
vsOffset = Vector(0, temp, 0)
|
|
end
|
|
|
|
if offsetX < 1 / gridLines then
|
|
local vTemp = vOrigin + vSX * offsetX
|
|
local vX = vTemp + vSY * offsetY
|
|
local vY = vTemp + vSY * (1 - offsetY)
|
|
local vsX, vsY = LineToScreen(vX, vY)
|
|
|
|
if (vsX) then
|
|
DrawScreenLine(vsX + vsOffset, vsY + vsOffset)
|
|
end
|
|
end
|
|
|
|
for i = 1, gridLines - 1 do
|
|
local vTemp = vOrigin + vSX * (i / gridLines)
|
|
local vX = vTemp + vSY * offsetY
|
|
local vY = vTemp + vSY * (1 - offsetY)
|
|
local vsX, vsY = LineToScreen(vX, vY)
|
|
|
|
if (vsX) then
|
|
if (gridLines / i == 2 and centerline) then
|
|
DrawScreenLine(vsX + vsOffset * -1, vsY + vsOffset * -1)
|
|
DrawScreenLine(vsX + vsOffset * 3, vsY + vsOffset * 3)
|
|
else
|
|
DrawScreenLine(vsX + vsOffset, vsY + vsOffset)
|
|
end
|
|
end
|
|
end
|
|
|
|
if offsetX < 1 / gridLines then
|
|
local vTemp = vOrigin + vSX * (1 - offsetX)
|
|
local vX = vTemp + vSY * offsetY
|
|
local vY = vTemp + vSY * (1 - offsetY)
|
|
local vsX, vsY = LineToScreen(vX, vY)
|
|
|
|
if (vsX) then
|
|
DrawScreenLine(vsX + vsOffset, vsY + vsOffset)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function DrawGrid(vOrigin, vSX, vSY)
|
|
local LX = CalculateGridAxis(vSX)
|
|
local LY = CalculateGridAxis(vSY)
|
|
surface.SetDrawColor(0, 0, 0, math.Round(GetConVarNumber("snap_gridalpha") * 255))
|
|
DrawGridLines(vOrigin, vSX, vSY, LX.grid, LX.offset, LY.offset, 1)
|
|
DrawGridLines(vOrigin, vSY, vSX, LY.grid, LY.offset, LX.offset, 1)
|
|
surface.SetDrawColor(255, 255, 255, math.Round(GetConVarNumber("snap_gridalpha") * 255))
|
|
DrawGridLines(vOrigin, vSX, vSY, LX.grid, LX.offset, LY.offset, -1)
|
|
DrawGridLines(vOrigin, vSY, vSX, LY.grid, LY.offset, LX.offset, -1)
|
|
end
|
|
|
|
local function DrawBoundaryLines(vOrigin, vOpposite)
|
|
local vPoint
|
|
|
|
if (vOrigin:Distance(vOpposite) > 5) then
|
|
local x = vOpposite - vOrigin
|
|
x:Normalize()
|
|
vPoint = vOrigin + x * 5
|
|
else
|
|
vPoint = vOrigin + (vOpposite - vOrigin) / 2
|
|
end
|
|
|
|
local vsA, vsB = LineToScreen(vPoint, vOrigin)
|
|
|
|
if (vsA) then
|
|
surface.SetDrawColor(0, 0, 255, 192)
|
|
DrawScreenLine(vsA, vsB)
|
|
end
|
|
end
|
|
|
|
local function DrawBoundary(vOrigin, vX, vY, vZ)
|
|
DrawBoundaryLines(vOrigin, vX)
|
|
DrawBoundaryLines(vOrigin, vY)
|
|
DrawBoundaryLines(vOrigin, vZ)
|
|
end
|
|
|
|
local function DrawSnapCross(vsCenter, r, g, b)
|
|
surface.SetDrawColor(0, 0, 0, 255)
|
|
DrawScreenLine(vsCenter + Vector(-2.5, -2.0), vsCenter + Vector(2.5, 3.0))
|
|
DrawScreenLine(vsCenter + Vector(1.5, -2.0), vsCenter + Vector(-3.5, 3.0))
|
|
surface.SetDrawColor(r, g, b, 255)
|
|
DrawScreenLine(vsCenter + Vector(-1.5, -2.0), vsCenter + Vector(3.5, 3.0))
|
|
DrawScreenLine(vsCenter + Vector(2.5, -2.0), vsCenter + Vector(-2.5, 3.0))
|
|
end
|
|
|
|
local function ComputeEdges(entity, obbmax, obbmin)
|
|
return {
|
|
lsw = entity:LocalToWorld(Vector(obbmin.x, obbmin.y, obbmin.z)),
|
|
lse = entity:LocalToWorld(Vector(obbmax.x, obbmin.y, obbmin.z)),
|
|
lnw = entity:LocalToWorld(Vector(obbmin.x, obbmax.y, obbmin.z)),
|
|
lne = entity:LocalToWorld(Vector(obbmax.x, obbmax.y, obbmin.z)),
|
|
usw = entity:LocalToWorld(Vector(obbmin.x, obbmin.y, obbmax.z)),
|
|
use = entity:LocalToWorld(Vector(obbmax.x, obbmin.y, obbmax.z)),
|
|
unw = entity:LocalToWorld(Vector(obbmin.x, obbmax.y, obbmax.z)),
|
|
une = entity:LocalToWorld(Vector(obbmax.x, obbmax.y, obbmax.z))
|
|
}
|
|
end
|
|
|
|
local function OnPaintHUD()
|
|
target.active = false
|
|
if GetConVarNumber("snap_clickgrid") ~= 0 and not snapclick then return end
|
|
snapclickprev = snapclick
|
|
snapclick = snapclickprev and snapclickfade > CurTime()
|
|
if (GetConVarNumber("snap_enabled") == 0) then return end
|
|
if (not LocalPlayer():Alive() or LocalPlayer():InVehicle()) then return end
|
|
|
|
if (target.locked) then
|
|
if (not target.entity:IsValid()) then return end
|
|
else
|
|
local trace = LocalPlayer():GetEyeTrace()
|
|
cache.vLookTrace = trace
|
|
if (not trace.HitNonWorld) then return end
|
|
local entity = trace.Entity
|
|
if (entity == nil) then return end
|
|
if (not entity:IsValid()) then return end
|
|
local class = entity:GetClass()
|
|
if (class ~= 'prop_physics' and class ~= 'phys_magnet' and class ~= 'gmod_spawner' and GetConVarNumber('snap_allentities') == 0 or class == 'player') then return end
|
|
if (not LocalPlayer():GetActiveWeapon():IsValid()) then return end
|
|
if (LocalPlayer():GetActiveWeapon():GetClass() == 'weapon_physgun') then return end
|
|
if (LocalPlayer():GetActiveWeapon():GetClass() ~= 'gmod_tool') then return end
|
|
target.entity = entity
|
|
end
|
|
|
|
--ErrorNoHalt(collectgarbage("count"))
|
|
if GetConVarNumber("snap_gcboost") ~= 0 then
|
|
collectgarbage("step", GetConVarNumber("snap_gcstrength"))
|
|
end
|
|
|
|
snapclick = snapclickprev
|
|
snapclickfade = CurTime() + 0.25
|
|
-- updating the cache perhaps shouldn't be done here, CalcView?
|
|
cache.vLookPos = LocalPlayer():GetShootPos()
|
|
cache.vLookVector = LocalPlayer():GetAimVector()
|
|
cache.vLookClipPos = cache.vLookPos + cache.vLookVector * 3
|
|
local model = string.lower(target.entity:GetModel())
|
|
local offsets = modeloffsets[model]
|
|
|
|
if not offsets then
|
|
local offset = 0.25
|
|
offsets = {offset, offset, offset, offset, offset, offset}
|
|
end
|
|
|
|
if cache.eEntity ~= target.entity or cache.vEntAngles ~= target.entity:GetAngles() or vEntPosition ~= target.entity:GetPos() then
|
|
cache.eEntity = target.entity
|
|
cache.vEntAngles = target.entity:GetAngles()
|
|
cache.vEntPosition = target.entity:GetPos()
|
|
local obbmax = target.entity:OBBMaxs()
|
|
local obbmin = target.entity:OBBMins()
|
|
local obvsnap = ComputeEdges(target.entity, obbmax, obbmin)
|
|
local obbmax = target.entity:OBBMaxs() - Vector(offsets[5], offsets[3], offsets[1])
|
|
local obbmin = target.entity:OBBMins() + Vector(offsets[6], offsets[4], offsets[2])
|
|
local obvgrid = ComputeEdges(target.entity, obbmax, obbmin)
|
|
local faces = {{obvgrid.unw, obvgrid.usw - obvgrid.unw, obvgrid.une - obvgrid.unw, obvgrid.lnw - obvgrid.unw, Vector(0, 0, -offsets[1])}, {obvgrid.lsw, obvgrid.lnw - obvgrid.lsw, obvgrid.lse - obvgrid.lsw, obvgrid.usw - obvgrid.lsw, Vector(0, 0, offsets[2])}, {obvgrid.unw, obvgrid.une - obvgrid.unw, obvgrid.lnw - obvgrid.unw, obvgrid.usw - obvgrid.unw, Vector(0, -offsets[3], 0)}, {obvgrid.usw, obvgrid.lsw - obvgrid.usw, obvgrid.use - obvgrid.usw, obvgrid.unw - obvgrid.usw, Vector(0, offsets[4], 0)}, {obvgrid.une, obvgrid.use - obvgrid.une, obvgrid.lne - obvgrid.une, obvgrid.unw - obvgrid.une, Vector(-offsets[5], 0, 0)}, {obvgrid.unw, obvgrid.lnw - obvgrid.unw, obvgrid.usw - obvgrid.unw, obvgrid.une - obvgrid.unw, Vector(offsets[6], 0, 0)}}
|
|
cache.aGrid = obvgrid
|
|
cache.aSnap = obvsnap
|
|
cache.aFaces = faces
|
|
end
|
|
|
|
local obvgrid = cache.aGrid
|
|
local obvsnap = cache.aSnap
|
|
local faces = cache.aFaces
|
|
|
|
if (not target.locked) then
|
|
-- should improve this by expanding the bounding box or something instead!
|
|
-- create a larger bounding box and then planes for each side, and check distance from the plane
|
|
-- separate function perhaps?
|
|
local distance = (LocalPlayer():GetPos() - target.entity:GetPos()):Length() - (obvgrid.unw - obvgrid.lse):Length()
|
|
if (distance > GetConVarNumber("snap_distance")) then return end
|
|
|
|
for face, vertices in ipairs(faces) do
|
|
intersection = RayQuadIntersect(cache.vLookPos, cache.vLookVector, vertices[1], vertices[2], vertices[3])
|
|
|
|
if (intersection) then
|
|
target.face = face
|
|
break
|
|
end
|
|
end
|
|
|
|
if intersection == nil then return end
|
|
end
|
|
|
|
if (GetConVarNumber("snap_boundingbox") ~= 0) then
|
|
DrawBoundary(obvgrid.unw, obvgrid.lnw, obvgrid.usw, obvgrid.une)
|
|
DrawBoundary(obvgrid.une, obvgrid.lne, obvgrid.use, obvgrid.unw)
|
|
DrawBoundary(obvgrid.lnw, obvgrid.unw, obvgrid.lsw, obvgrid.lne)
|
|
DrawBoundary(obvgrid.lne, obvgrid.une, obvgrid.lse, obvgrid.lnw)
|
|
DrawBoundary(obvgrid.usw, obvgrid.lsw, obvgrid.unw, obvgrid.use)
|
|
DrawBoundary(obvgrid.use, obvgrid.lse, obvgrid.une, obvgrid.usw)
|
|
DrawBoundary(obvgrid.lsw, obvgrid.usw, obvgrid.lnw, obvgrid.lse)
|
|
DrawBoundary(obvgrid.lse, obvgrid.use, obvgrid.lne, obvgrid.lsw)
|
|
end
|
|
|
|
local vectorOrigin = faces[target.face][1]
|
|
local vectorX = faces[target.face][2]
|
|
local vectorY = faces[target.face][3]
|
|
local vectorZ = faces[target.face][4]
|
|
local vectorOffset = faces[target.face][5]
|
|
local vectorGrid
|
|
|
|
if (not target.locked) then
|
|
vectorGrid = vectorOrigin + CalculateSnap(vectorX, vectorY, intersection)
|
|
|
|
local trace = util.TraceLine({
|
|
start = target.entity:LocalToWorld(target.entity:WorldToLocal(vectorGrid) - vectorOffset) - vectorZ:GetNormalized() * 0.01,
|
|
endpos = vectorGrid + vectorZ
|
|
})
|
|
|
|
local vectorSnap = trace.HitPos
|
|
target.offset = target.entity:WorldToLocal(vectorSnap)
|
|
target.vector = target.entity:WorldToLocal(vectorGrid)
|
|
target.error = true
|
|
|
|
if (trace.Entity == nil or not trace.Entity:IsValid()) then
|
|
snaperror = -1
|
|
elseif (trace.Entity ~= target.entity) then
|
|
snaperror = -2
|
|
elseif (trace.HitPos == trace.StartPos) then
|
|
snaperror = -2
|
|
else
|
|
snaperror = (LocalPlayer():GetEyeTrace().HitPos - trace.HitPos):Length()
|
|
target.error = false
|
|
|
|
if ((vectorSnap - vectorGrid):Length() > 0.5) then
|
|
local marker = PointToScreen(vectorSnap)
|
|
|
|
if (marker) then
|
|
DrawSnapCross(marker, 255, 255, 255)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
vectorGrid = target.entity:LocalToWorld(target.vector)
|
|
local vectorSnap = target.entity:LocalToWorld(target.offset)
|
|
local marker = PointToScreen(vectorSnap)
|
|
snaperror = (LocalPlayer():GetEyeTrace().HitPos - vectorSnap):Length()
|
|
|
|
if (marker) then
|
|
if (target.error == true) then
|
|
snaperror = -2
|
|
DrawSnapCross(marker, 0, 255, 255)
|
|
elseif (snaperror < 0.001) then
|
|
DrawSnapCross(marker, 0, 255, 0)
|
|
elseif (snaperror < 0.1) then
|
|
DrawSnapCross(marker, 255, 255, 0)
|
|
else
|
|
DrawSnapCross(marker, 255, 0, 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
if (GetConVarNumber("snap_hidegrid") == 0) then
|
|
DrawGrid(vectorOrigin, vectorX, vectorY)
|
|
end
|
|
|
|
target.active = true
|
|
local vsCursor = PointToScreen(vectorGrid)
|
|
|
|
if (vsCursor) then
|
|
if (snaperror == -1) then
|
|
target.active = false
|
|
DrawSnapCross(vsCursor, 0, 255, 255)
|
|
elseif (snaperror == -2) then
|
|
DrawSnapCross(vsCursor, 255, 0, 255)
|
|
elseif (snaperror < 0.001) then
|
|
DrawSnapCross(vsCursor, 0, 255, 0)
|
|
elseif (snaperror < 0.1) then
|
|
DrawSnapCross(vsCursor, 255, 255, 0)
|
|
else
|
|
DrawSnapCross(vsCursor, 255, 0, 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function OnSnapView(player, origin, angles, fov)
|
|
local targetvalid = target.active and target.locked and target.entity:IsValid()
|
|
local snaptargetvalid = snaptarget.active and snaptarget.locked and snaptarget.entity:IsValid()
|
|
|
|
if (snapcursor and not snapspawnmenu and targetvalid) then
|
|
local screen = ToScreen(target.entity:LocalToWorld(target.offset))
|
|
gui.SetMousePos(math.Round(screen.x), math.Round(screen.y))
|
|
end
|
|
|
|
if (not snapcursor and targetvalid) then
|
|
return {
|
|
angles = (target.entity:LocalToWorld(target.offset) - player:GetShootPos()):Angle()
|
|
}
|
|
elseif (snaplock and snaptargetvalid) then
|
|
return {
|
|
angles = (snaptarget.entity:LocalToWorld(snaptarget.offset) - player:GetShootPos()):Angle()
|
|
}
|
|
end
|
|
end
|
|
|
|
local function OnSnapAim(user)
|
|
local targetvalid = target.active and target.locked and target.entity:IsValid()
|
|
local snaptargetvalid = snaptarget.active and snaptarget.locked and snaptarget.entity:IsValid()
|
|
|
|
if (not snapcursor and targetvalid) then
|
|
user:SetViewAngles((target.entity:LocalToWorld(target.offset) - LocalPlayer():GetShootPos()):Angle())
|
|
elseif (snaplock and snaptargetvalid) then
|
|
user:SetViewAngles((snaptarget.entity:LocalToWorld(snaptarget.offset) - LocalPlayer():GetShootPos()):Angle())
|
|
end
|
|
end
|
|
|
|
concommand.Add("+snap", SnapPress)
|
|
concommand.Add("-snap", SnapRelease)
|
|
concommand.Add("snaplock", SnapLock)
|
|
concommand.Add("snaptogglegrid", SnapToggleGrid)
|
|
hook.Add("Initialize", "SmartsnapInitialize", OnInitialize)
|
|
hook.Add("SpawnMenuOpen", "SmartsnapSpawnMenu", OnSpawnMenu)
|
|
hook.Add("Think", "SmartsnapThink", OnThink)
|
|
hook.Add("ShutDown", "SmartsnapShutDown", OnShutDown)
|
|
hook.Add("KeyPress", "SmartsnapKeyPress", OnKeyPress)
|
|
hook.Add("KeyRelease", "SmartsnapKeyRelease", OnKeyRelease)
|
|
hook.Add("CreateMove", "SmartsnapSnap", OnSnapAim)
|
|
hook.Add("CalcView", "SmartsnapSnapView", OnSnapView)
|
|
hook.Add("SpawnMenuOpen", "SmartsnapSpawnMenu", OnSpawnMenu)
|
|
hook.Add("HUDPaintBackground", "SmartsnapPaintHUD", OnPaintHUD)
|
|
|
|
local function OnPopulateToolPanel(panel)
|
|
panel:AddControl("ComboBox", {
|
|
Options = {
|
|
["default"] = condefs
|
|
},
|
|
CVars = convars,
|
|
Label = "",
|
|
MenuButton = "1",
|
|
Folder = "smartsnap"
|
|
})
|
|
|
|
panel:AddControl("CheckBox", {
|
|
Label = "Enable",
|
|
Command = "snap_enabled"
|
|
})
|
|
|
|
panel:AddControl("CheckBox", {
|
|
Label = "Use click grid (USE temporarily enables grid)",
|
|
Command = "snap_clickgrid"
|
|
})
|
|
|
|
panel:AddControl("CheckBox", {
|
|
Label = "Hide grid (only shows snap point)",
|
|
Command = "snap_hidegrid"
|
|
})
|
|
|
|
panel:AddControl("CheckBox", {
|
|
Label = "Smart toggle enabled",
|
|
Command = "snap_enabletoggle"
|
|
})
|
|
|
|
panel:AddControl("CheckBox", {
|
|
Label = "Revert aim to grid snap on detach",
|
|
Command = "snap_revertaim"
|
|
})
|
|
|
|
panel:AddControl("CheckBox", {
|
|
Label = "Enable for all entities",
|
|
Command = "snap_allentities"
|
|
})
|
|
|
|
-- panel:AddControl("CheckBox", {
|
|
-- Label = "Enable for all tools",
|
|
-- Command = "snap_alltools"
|
|
-- })
|
|
|
|
panel:AddControl("CheckBox", {
|
|
Label = "Draw thick center lines",
|
|
Command = "snap_centerline"
|
|
})
|
|
|
|
panel:AddControl("Slider", {
|
|
Label = "Grid toggle delay (double click snap-key)",
|
|
Command = "snap_toggledelay",
|
|
Type = "Float",
|
|
Min = "0.0",
|
|
Max = "0.2"
|
|
})
|
|
|
|
panel:AddControl("Slider", {
|
|
Label = "Smart lock delay",
|
|
Command = "snap_lockdelay",
|
|
Type = "Float",
|
|
Min = "0.0",
|
|
Max = "5.0"
|
|
})
|
|
|
|
panel:AddControl("CheckBox", {
|
|
Label = "Bounding box enabled",
|
|
Command = "snap_boundingbox"
|
|
})
|
|
|
|
panel:AddControl("Slider", {
|
|
Label = "Grid draw distance",
|
|
Command = "snap_distance",
|
|
Type = "Integer",
|
|
Min = "50",
|
|
Max = "1000"
|
|
})
|
|
|
|
panel:AddControl("Slider", {
|
|
Label = "Grid edge offset",
|
|
Command = "snap_gridoffset",
|
|
Type = "Float",
|
|
Min = "0.0",
|
|
Max = "2.5"
|
|
})
|
|
|
|
panel:AddControl("Slider", {
|
|
Label = "Grid transparency",
|
|
Command = "snap_gridalpha",
|
|
Type = "Float",
|
|
Min = "0.1",
|
|
Max = "1.0"
|
|
})
|
|
|
|
panel:AddControl("Slider", {
|
|
Label = "Maximum number of snap points on an axis",
|
|
Command = "snap_gridlimit",
|
|
Type = "Integer",
|
|
Min = "2",
|
|
Max = "64"
|
|
})
|
|
|
|
panel:AddControl("Slider", {
|
|
Label = "Minimum distance between each snap point",
|
|
Command = "snap_gridsize",
|
|
Type = "Integer",
|
|
Min = "2",
|
|
Max = "64"
|
|
})
|
|
|
|
panel:AddControl("Label", {
|
|
Text = ""
|
|
})
|
|
|
|
panel:AddControl("Label", {
|
|
Text = "The following option should prevent FPS drops from occuring, however it might have a slight impact on the average FPS while the grid is showing. Do NOT uncheck this option unless you are experiencing very low FPS or fully understands its purpose."
|
|
})
|
|
|
|
panel:AddControl("Label", {
|
|
Text = "NOTE: This option is only effective when the grid is showing, it does not impact regular gameplay!"
|
|
})
|
|
|
|
panel:AddControl("Label", {
|
|
Text = ""
|
|
})
|
|
|
|
panel:AddControl("CheckBox", {
|
|
Label = "Garbage collection boost",
|
|
Command = "snap_gcboost"
|
|
})
|
|
end
|
|
|
|
function OnPopulateToolMenu()
|
|
spawnmenu.AddToolMenuOption("Options", "Player", "SmartSnapSettings", "SmartSnap", "", "", OnPopulateToolPanel, {
|
|
SwitchConVar = 'snap_enabled'
|
|
})
|
|
end
|
|
|
|
hook.Add("PopulateToolMenu", "SmartSnapToolMenu", OnPopulateToolMenu)
|
|
end |