Antispam Prototype

Hey im currently working on an antispam script which should be a bit more intelligent than just preventing ppl from spawning props if they reaced like 5props / sec. Initially just wanted to get a system that prevents ppl from spawning props inside each other because fpps one delays a servercrach effectively just by 1 prop.

Download: https://github.com/SirUke/Antispam-Prototype/

Functionality:
You have 100% spawn capability as rating.
If you spawn a prop it drains the rating depending on the props size, spawnrate, how much props you spawned recently and how much of the same model also the rating the prop drained is saved on the prop.
The less rating you have the more a prop drains it and the slower it will recover. It also recover if your prop is removed. And the recovering only starts after 0.6 sec after your last spawned prop.
If you spawn a prop inside another prop it will get ghosted(i basically took fpp’s ghosting but applied my own rules to it) and you can only unghost it if you drag it with the physgun to a position where it wont be inside another prop.
It wont ghost the prop if you spawn is frozen and you cant unfreeze it inside another prop.
But if you still manage to lag the server it will freeze all props with a certein rating and if the rating is even higher it removes it.

If your rating is lower than 10% it either spawns the props always ghosted or doesnt let you spawn it. The 0.6 wont ignore failed attempts and it should still drain a certain amount.
Players get notified if their props got ghosted/removed and if they spawned a prop that gets ghosted or removed on lag. Also they see their rating.

So if you build like a normal person the rating wont affect you at all. Also it lets you spawn unlimited frozen props and their rating is multiplied by 0.1.

pictures:
Imgur

E2 massively spawning props (rating bottom right corner):
http://i.imgur.com/523Au19l.jpg

Unfrozen props spawned ghosted:
http://i.imgur.com/h3rlYxrl.jpg

Spawning props in other props. (notice the bottom right corner. the small dots indicate whether the spawned prop will get deleted(orange) or frozen(blue) on lag)
http://i.imgur.com/fHgh9bWl.jpg

Me desperatly trying to lag the server to get a screenshot of the notifications:
http://i.imgur.com/X1yytY2l.jpg

Finally got the notifications but with laggy e2s(below chatbox):
http://i.imgur.com/YpjNvxpl.jpg

Code:


local frzrep = 0.5	--props with a rating higher than that will get frozen on laglocal delrep = 1.2	--props with a rating higher than that will get deleted on lag




local lagdt = 1.8 --if a servertick takes longer than that(sec) it is concidered as lag




local minrep = 0.1	--minimum rating a player has to have to be able to spawn
local blockspawn = false --block players from spawning when their rating is too low
						--when set to false it will freeze instead
if SERVER then
	
	AddCSLuaFile()
	
	util.AddNetworkString("uantispam_info")
	util.AddNetworkString("uantispam_pinf")
	
	print("[+] Uke's antispam loaded serverside.")
	
	local SysTime = SysTime
	local CurTime = CurTime
	local player = player
	local math = math
	
	local nr = 1
	local spawnh = spawnh or {}
	local entis = entis or {}
	
	local t = CurTime()
	
	local function cananyway(ply)
		if ply.EV_GetRank and ply:EV_GetRank() == "guest" then return false end
		return true	
	end
	------------------------------------------------------------------
	------------------------------------------------------------------
	for k,v in pairs(player.GetAll()) do
		v.rep = 1
		v.st = CurTime()
		v.ls = CurTime()
		v.spn = 0
		spawnh[v] = spawnh[v] or {}
		v:SetNWFloat( "spawn_rep", v.rep )
	end
	
	hook.Add("PlayerInitialSpawn","spwn1",function(ply)
		ply.rep = 1
		ply.st = CurTime()
		ply.ls = CurTime()
		ply.spn = 0
		spawnh[v] = {}
		ply:SetNWFloat( "spawn_rep", ply.rep )
	end)
	------------------------------------------------------------------
	------------------------------------------------------------------
	
	function unfreeze(ent)
		if ent.uantispam_frzn == true then
			ent.uantispam_frzn = nil
			
			ent:SetRenderMode(ent.uantispam_orm)
			ent.uantispam_orm = nil
			ent:SetMaterial(ent.uantispam_omat)
			ent.uantispam_omat = nil
			ent:DrawShadow(true)
			ent:SetCollisionGroup(ent.uantispam_ocg)
			ent.uantispam_ocg = nil




			ent:SetColor(Color(ent.uantispam_oc.r, ent.uantispam_oc.g, ent.uantispam_oc.b, ent.uantispam_oc.a))
			ent.uantispam_oc = nil
	
	
			ent:SetCollisionGroup(COLLISION_GROUP_NONE)
			ent.uantispam_cg = nil
	
			local phys = ent:GetPhysicsObject()
			if phys:IsValid() then
				phys:EnableMotion(ent.uantispam_mtn)
			end
			ent.uantispam_mtn = nil
		end
	end
	
	local function propIsNotFree(e)
		local trace = { start = e:GetPos(), endpos = e:GetPos(), filter = e, ignoreworld = true }
		local tr = util.TraceEntity( trace, e ) 
		return tr.Hit
	end
	
	hook.Add("PhysgunDrop", "uantispam_pgpu",function(ply, e)
		if e.uantispam_frzn == true then
			
			if propIsNotFree(e) then
				local phys = e:GetPhysicsObject()
				if phys:IsValid() then
					phys:EnableMotion(false)
				end
			else
				e.uantispam_mtn = false
				unfreeze(e)
			end
		end
	end)
	
	_R.PhysObj.oldfrz = _R.PhysObj.oldfrz or _R.PhysObj.EnableMotion
	
	hook.Add("CanPlayerUnfreeze","dgsgahrthh",function( ply, ent, phys )
		if propIsNotFree(ent) then return false end
	end)
	
	hook.Add("EntUnfreeze","dgsgahrthh",function( ent, phys )
		if propIsNotFree(ent) then return false end
	end)
	
	function _R.PhysObj:EnableMotion(bool)
		local ent = self:GetEntity( )
		
		local canperformaction = true
		
		if bool then
			canperformaction = hook.Call("EntUnfreeze",GAMEMODE,ent,self)
		else
			canperformaction = hook.Call("EntFreeze",GAMEMODE,ent,self)
		end
		
		if canperformaction == nil or canperformaction == true then
			self:oldfrz(bool)
		end
	end
		
	local function freeze(ent,phys)		
		ent:DrawShadow(false)
		ent.uantispam_oc = ent.uantispam_oc or ent:GetColor()
		ent:SetColor(Color(100,160,255,60))
		
		ent.uantispam_omat = ent.uantispam_omat or ent:GetMaterial()
		ent:SetMaterial("models/shiny")
		
		ent.uantispam_orm = ent.uantispam_orm or ent:GetRenderMode()
		ent:SetRenderMode(1)
		
		ent.uantispam_ocg = ent:GetCollisionGroup()
		ent:SetCollisionGroup(COLLISION_GROUP_WORLD)
		ent.uantispam_cg = COLLISION_GROUP_WORLD




		ent.uantispam_mtn = phys:IsMoveable()
		phys:EnableMotion(false)




		ent.uantispam_frzn = true
	end
	
	local function spawned(v,m,mul,e)
		if v.st == 0 then v.st = CurTime() end
		local ls = v.ls
		local st = CurTime()
		v.ls = st
		v.spn = v.spn + 1
		if spawnh[v][m] ~= nil then
			spawnh[v][m] = spawnh[v][m] + 1
		else
			spawnh[v][m] = 1
		end
		local c = (e:OBBMaxs() - e:OBBMins()):Length() * 0.00033
		local n = spawnh[v][m]
		local dt = 1-math.Clamp(st-ls,0,1)
		local spm = (n*0.7)+(v.spn*0.4)
		local mr = math.Clamp(((1.022+c)^(1+spm))-1 - ((1-v.rep) * (1-dt)),0,1) * mul
		
		v.rep = math.Clamp(v.rep - mr,0,1)
		
		v:SetNWFloat( "spawn_rep", v.rep )
		
		return mr, c, spm
	end
	
	hook.Add("EntityRemoved","regain",function(e)
		local v = entis[e]
		if v ~= nil and v ~= {} and v.spwnr ~= nil and IsValid(v.spwnr) then
			local ply = v.spwnr
			local mr = v.mr
			ply.rep = math.Clamp(ply.rep + mr*0.6,0,1)
		end
	end)
	
	hook.Add("Think","repthink",function()
		local lt = t
		t = RealTime()
		local dt = t-lt
		
		for k,v in pairs(player.GetAll()) do
			v:SetNWFloat( "spawn_rep", v.rep )
			if v.ls ~= nil and v.ls + 0.6 < t then
				if v.rep < 1 then
					v.rep = math.Min(v.rep + (dt*(v.rep+0.05))/3,1)
				elseif spawnh[v] ~= {} then
					spawnh[v] = {}
					v.spn = 0
					v.st = 0
				end
			end
		end
		
		if dt > lagdt then
			local frz = {}
			local del = {}
			for k, v in pairs(entis) do
				local ent = v.ent
				if v.spm > frzrep then
					if ent == nil or not IsValid(ent) then
						entis[k] = nil
					else
						frz[v.spwnr] = frz[v.spwnr] or 0
						del[v.spwnr] = del[v.spwnr] or 0
						local phys = ent:GetPhysicsObject()
						if phys:IsValid() and v.spm < delrep then
							if ent.uantispam_frzn == nil or ent.uantispam_frzn ~= true then
								freeze(ent, phys)
								frz[v.spwnr] = frz[v.spwnr] + 1
							end
						else
							ent:Remove()
							del[v.spwnr] = del[v.spwnr] + 1
						end
					end
				end
			end
			for k,v in pairs(del) do
				if IsValid(k) and k ~= nil and v > 0 then
					net.Start("uantispam_info")
						net.WriteVector(Vector(1,v,0))
					net.Send(k)
				end
			end
			for k,v in pairs(frz) do
				if IsValid(k) and k ~= nil and v > 0 then
					net.Start("uantispam_info")
						net.WriteVector(Vector(0,v,0))
					net.Send(k)
				end
			end
		end
	end)
	
	hook.Add("PlayerSpawnObject","spwn1",function(ply, m, n)
		if ply.rep < minrep and not cananyway(ply) and blockspawn then
			return false
		end
	end)
	
	local function spawnHub(ply,e,m,mul)
		e:SetCollisionGroup(COLLISION_GROUP_WORLD)
		hook.Call("postspawned",GAMEMODE,ply,e,m,mul)
	end
	
	local function spawnHub2(ply,e,m,mul)
		local mr, spm, c = 0, 0, 0
		
		local phys = e:GetPhysicsObject()
		if phys:IsValid() and not phys:IsMoveable() then
			mul = mul * 0.1
		end
		if m == "" then
			if ply.rep < minrep then
				mr, spm, c = spawned(ply,type(e),0.4*mul,e)
			else
				mr, spm, c = spawned(ply,type(e),mul,e)
			end
		else
			if ply.rep < minrep then
				mr, spm, c = spawned(ply,m,0.4*mul,e)
			else
				mr, spm, c = spawned(ply,m,mul,e)
			end
		end
		
		e:SetCollisionGroup(COLLISION_GROUP_NONE)
		
		local spm = ((mr*0.5)+(spm*0.3)+(c*0.2))*0.6




		if propIsNotFree(e) or (not blockspawn and ply.rep < minrep and not cananyway(ply)) then
			if phys:IsValid() and phys:IsMoveable() then
				freeze(e,phys)
			end
		end
		
		if spm > delrep then
			net.Start("uantispam_pinf")
				net.WriteBit(true)
				net.WriteEntity( e )
			net.Send(ply)
		elseif spm > frzrep then
			net.Start("uantispam_pinf")
				net.WriteBit(false)
				net.WriteEntity( e )
			net.Send(ply)
		end
		
		entis[e] = {["ent"] = e, ["mr"] = mr,["spm"] = spm, ["spwnr"] = ply}
	end
	
	hook.Add("postspawned","afafgsgshr",function(ply,e,m,mul)
		timer.Simple(0.001,function() spawnHub2(ply,e,m,mul) end)
	end)
	
	hook.Add("PlayerSpawnedEffect","spwnd",function(ply,mdl,ent)
		spawnHub(ply,ent,mdl,0.6)
	end)
	hook.Add("PlayerSpawnedProp","spwnd",function(ply,mdl,ent)
		spawnHub(ply,ent,mdl,1)
	end)
	hook.Add("PlayerSpawnedRagdoll","spwnd",function(ply,mdl,ent)
		spawnHub(ply,ent,mdl,8)
	end)
	hook.Add("PlayerSpawnedVehicle","spwnd",function(ply,ent)
		spawnHub(ply,ent,"",2.5)
	end)
end




if CLIENT then
	local chat = chat
	local math = math
	local surface = surface
	local ScrW, ScrH = ScrW, ScrH
	local CurTime = CurTime
	local FrameTime =  FrameTime
	local msgs = {}
	local prps = {}
	local pi = math.pi
	local LP = LocalPlayer()
	
	print("[+] Uke's antispam loaded clientside.")
	
	hook.Add("HUDPaint","sdaf",function()
		local rep = LocalPlayer():GetNWFloat( "spawn_rep" ) or 1
		
		local txt = "Spawn Capability: "..tostring(math.floor(rep*100)).."%"
		if rep < minrep then
			local flsh = (math.sin(SysTime()*30)+1)/2
			surface.SetTextColor(Color(205*flsh + 50,50,50,255))
		elseif rep < 1 then
			local flsh = (math.sin(SysTime()*8)+1)/2
			local m = 200*(1-rep)
			local n = 255-m
			surface.SetTextColor(Color(m*flsh + n,m*flsh + n,n,255-235*rep))
		else
			surface.SetTextColor(Color(255,255,255,20))
		end
		surface.SetFont("ChatFont")
		local tsx,tsy = surface.GetTextSize(txt)
		local tpx, tpy = ScrW() -tsx- 10, ScrH() - tsy - 10
		surface.SetTextPos(tpx, tpy)
		surface.DrawText(txt)
		
		local tr = LP:GetEyeTrace().Entity
		
		if IsValid(tr) and tr ~= nil and not tr:IsWorld() then
			if uantispam_b ~= nil then
				surface.SetDrawColor(255,255,255,255)
				if tr.uantispam_b == 1 then
					surface.SetMaterial(Material("icon16/bullet_orange.png"))
				else
					surface.SetMaterial(Material("icon16/bullet_blue.png"))
				end
				
				surface.DrawTexturedRect(tpx-12,tpy,16,16)
			end
		end
		
		local mc = #msgs
		local cx, cy = chat.GetChatBoxPos( )
		local csx,csy= 200, 200
		local ft = FrameTime()
		local ct = CurTime()
		for k,v in ipairs(msgs) do
			local t = v.t
			local mt = v.t+13-ct
			if mt <= 0 then table.remove(msgs, k) else
				local k = mc - k
				local dx, dy = cx+20, cy+csy+4+22*(k-1)
				local x, y = v.pos.x, v.pos.y
				x = Lerp(ft, x, dx)
				y = Lerp(ft, y, dy)
				v.pos.x, v.pos.y = x, y
				
				local cy = math.Clamp(mt,0,1)
				local a = 220 * cy
				local Col = Color(255,255,255,a)
				local STR = ""
				if v.type == 0 then
					Col = Color(100,160,255,a)
					STR = v.n.." entities frozen due to lag."
				elseif v.type == 1 then
					Col = Color(160,90,40,a)
					STR = v.n.." entities removed due to lag."
				end
				draw.RoundedBoxEx( 8, x, y, 224, 18, Col, (k == 0 and true or false), (k == 0 and true or false), (k == mc-1 and true or false), (k == mc-1 and true or false) )
				draw.DrawText(STR,"ChatFont",x+6,y+2,Color(255,255,255,a),TEXT_ALIGN_LEFT)
			end
		end
		
		for k,v in ipairs(prps) do
			local vel = math.Min(v.vel + ft,1)
			v.vel = vel
			local v2 = vel < 0 and vel/0.6 or vel
			local cos = math.cos(vel*(pi/2))
			local co2 = math.cos(v2*(pi/2))
			local x, y = tpx - 12 - cos*100, tpy
			
			local a = 55+200*co2
			
			surface.SetDrawColor(255,255,255,a)
			if v.del == true then
				surface.SetMaterial(Material("icon16/bullet_orange.png"))
			else
				surface.SetMaterial(Material("icon16/bullet_blue.png"))
			end
			
			surface.DrawTexturedRect(x,y,16,16)
			
			if vel == 1 then table.remove(prps, k) end
		end
		
	end)
	
	net.Receive("uantispam_info", function()
		local v = net.ReadVector()
		local x, y = chat.GetChatBoxPos( )
		local sx,sy= 200,200
		local k = #msgs
		table.insert(msgs, {["type"] = v.x, ["n"] = v.y, ["pos"] = {["x"] = x - 400, ["y"] = y + 4 + sy}, ["t"] = CurTime()} )		
	end)
	
	net.Receive("uantispam_pinf", function()
		local b = net.ReadBit()
		local e = net.ReadEntity()
		e.uantispam_b = 1
		table.insert(prps, {["del"] = (b == 1 and true or false), ["vel"] = -0.6} )		
	end)
	
end

Whats to do:
-maybe more work on balancing(line 265- -)
-getting indicators for the rating-status of the prop youre looking at (orange/blue dots) to work
-maybe more input to evaluate for the rating
-better method of rating the props after actions like freezing them are applied to them(242++, 274, 299++)
(the hook + timer come from both of my attempts to solve it, i like the hook verion more but its still faster than a e2 freezing a prop right after spawn)
-rating spammed ragdolls doesnt work properly

My initial goal is more than achieved but maybe you got some useful additions so please give me feedback:
-What do you think?
-What is good/bad, why and how can i improve it?
-Ideas/Suggestions/Improvements?

P.S. hating the preview function of threads while previewing the thread it deletes the contents of the textbox. Had to rewrite half of my post and makes me avoid the function instead I choose to post and edit afterwards. (happened not only this time)

Good work. I’ve always said a simple spawn delay (like in assmod) will not work and is very annoying. More intelligent solutions are needed. FPP does a decent job, but the heuristics could be improved.

Try to make it interfere with actual building or prop activities as little as humanely possible. So far you seem to be doing a decent job (hard to judge).

Have a use case that will make the problem harder: a prop kill fight between two players.
Say you have a DarkRP server where the admin agrees to have a fight in a certain area of the map between two good prop killers. This is rare, but it has happened in the past and it’s loads of fun.

Prop kill fights are paired with rapid spawning of props, usually at most two in one second when attacking. Props are either frozen immediately thrown or accidentally dropped. Frozen props block other props as it’s a defense mechanism. Moving props can obviously kill players.

Have another use case:

When building a base some players open the Q menu once and spawn a bunch of the same props for easy building. Assmod antispam fucked you over here. FPP’s antispam’s levels are set in such way that this action (on average speed) will not trigger the antispam. Your antispam must come with well tweaked default settings. People rarely ever change those settings, a good default is vital.

Some more comments on your code:
you’ve got quite big functions and huge pyramids. Make utility functions, split the work up in several functions. That will make it easier to modify, easier to read. You could have one function for maintaining the antispam counter, another for deciding when to start freezing/removing. Yet another one to send the net message in the right format etc.

Pyramid functions can also be straightened out by changing things like “if then if then if then if then” into “if then return end if then return end …”, use the continue keyword in loops and that kind of stuff.

Keep up the good work!

Thanks for your feedback!

Hm propfights sound funny. But automatically detecting when spam is good/bad seem impossible for me instead I could:
-provide hooks where modders are required to return false in to allow ppls to spam
-whitelist for ppl who are allowed to spam; editable in the menu and/or provide functions to edit the list for example with commands in the admin mod
-sopport for evolve/ulx ranks and a rank whitelist

thats what i can think of but what coder wants to adapt their addons to my antispam so the only solution left is the integrated whitelist to allow ppl to spam

at the moment the antispam is only active for guests if you’re runiing evolve (line 29++)
the ghosting is active for anyone

Yeah already thought of this case thats why I implemented and made ghsting instead of blocking a default setting. I think it’s even useful that spammed props are ghosted if you move them anyways. So they’re held in place until you need them. Also if you’re spawning them next to each other thay wont drift away.

I tried to make utility functions where I cought some same functions like the propIsNotFree(e) function. Haven’t actually thought of doing it for similar occurrences.

I’ll tweak it if I got a settingspanel working with the neccessary variables which outputs how often i can spawn what sized prop until the rating is under the minimum.

Add to github? (:

Again, good work!

The prop kill use case can probably be dealt with mostly with good defaults. FPP acts decent in prop kill settings.

For the evolve thing, you could also use
[lua]
or ply:GetUserGroup() == “user”
[/lua]
the guest thing from evolve is retarded, it was a mistake of OverV.

Ok I don’t know if it really was neccessary but heres the git: https://github.com/SirUke/Antispam-Prototype (perhaps it will get more useful if the antispam grows more lua files)
But I don’t know how often/long I can work on it.

I guess the props you use to kill each other have approximately the size of the blue barrel or less that wouldn’t be a big deal to let ppl spawn more small props as I think they can’t spawn enough at the moment anyway. But I’m afraid the ghosting would ruin it though if the prop is spawned inside another prop.

But for guests and respected the usergroup is ‘user’. Also I don’t know why it’s retarded?

Evolve is retarded because it thinks users are called guests while they aren’t.
Respected players should have usergroup called “respected”, “vip” or similar.

Doesn’t look very good against burst lag before the timers can fire.

Well it’s just a name and you can change it. Or am I not getting the point?

Props are spawned nocollided until the timer fires.

Is there a hook that gets called like think but at the very end of a tick?

“burst lag”, if you get some, would probably be caused by non precached models being spawned. If someone is allowed to spawn 50 such new models with a keybind, it could cause issues… Do you precache models on your server?

Hm I don’t think so. But if I precache every model it would take ages to load and I bet >90% wouldn’t even be used.
So what can I do about it and how?

Well, each time a new model is spawned you’ll get a little lag while it loads. You could set up a table and count how many times certain models are spawned… Save it to a flag-file, record it over the course of a week or so… You could precache the top used models if there are any that stand out.

Models that are already spawned on the map will just mean that loading in takes a little longer, those you should ignore, only count models that aren’t loaded at map spawn… Doing that could eliminate “some” of the “lag”…

You could also log models used by individual players, and based on which players are in; precache the most likely props those players will spawn… You could get some decently accurate predictions if you log data and track trends.

That would be really cool; having a precacher that conforms to your personal building patterns.

Shouldnt take too much to implement that but i want to get my settingsmenu ready first.
But at the moment I wont like to save the props for each player. I’d rather precache porps that everyone uses often because having tables with hundreads of values for hundreds of players… I don’t know how good this would be.

However I’d like to show you how far I got:

http://i.imgur.com/ciFxK2Sl.jpg

http://i.imgur.com/tWwAwuKl.jpg

In case you wonder what the wand in the bottom left corner is for(if you even noticed):
http://i.imgur.com/1X0Q6Bhl.jpg

At the moemnt only the few sliders for the graph-panel are working. Can horizontal/vertical lines and fields. (difference for the fields is that i can make them start at different window borders)

The 2 graphs are just for the screenshot while the rainbow one is the actual balancing-curve. Haven’t even optimized them to fit the panel right.

Why am I working on the settingsmenu instead of improving the antispam?
Well the antispam is kinda working so far but I wanted some graphs to see what I’m tweaking in order to do so. Also I like working on ui stuff.

Git has been updated: https://github.com/SirUke/Antispam-Prototype

http://www.blaulichtentchen.info/resources/beta.jpg

I’ve hit beta!

Settings work. (open with console command “uantispam_settingsmenu” ill add a button to open it later)
Settings are networked between clients and server. Only superadmins may change them. And they’re saved loaded. Automatically saves 4sec after the last change has been made.

Set to let you spawn some small (~8-12 bluebarrels with max speed) and a few big sized props(~3).
But reloads really fast for small props.

The rainbow turns yellow as the graph reaches the blue(freeze) line and turns red near the red line(delete).
http://i.imgur.com/5RrJI97l.jpg

http://i.imgur.com/MYGWeaul.jpg

Whats still on my agena:
-Button in the options tab to open the settingspanel
-maybe lower the blue line so big props always get frozen on lag
-options to control the spawn capability recharging
-the precacher you wanted which i think is quite useful (1 question: do I have to precache as the map loads or can I always precache unprecached models?)

Btw the GitHub now really pays off! Also I didnt expect it to be so easy to use.

Would be nice if someone could test it and tell me if errors occur and give me feedback.

Patched. Also updated GitHUB: https://github.com/SirUke/Antispam-Prototype/

Added broken button to open the settingspanel (too tired to fix now) use the consolecmd instead.
Tweaked the balance. Added options and graph for recharging behaviour.

Later today I can move on to the precaching I guess and make a nice image and upload it to the workshop.

Also added names to the graphs.
And the graphs fidelity scales slowly with the fps the menu drains in case youre running gmod on awooden computer which will lag in the menu.

And thanks to Bolli for: http://forum.facepunch.com/showthread.php?t=1427411&p=46095902#post46095902
The grid now adapts to the level of zoom.

At this point i’d say it’s ready to be used on servers. But with some caution.

http://i.imgur.com/d9yKNrTl.jpg

Ok its released to the workshop.

http://cloud-2.steampowered.com/ugc/35224754612703069/EB21A170F23E4058522E7DABEC0FC61EE02DAD34/

Btw. I also released an updated version of the radial menu if you’d like to check that out: