--[[ 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