What would you like to see in a new scripted entity system?

Community questions at the bottom!

I am currently creating my own standalone (addon) scripted entity system based off the benefits and flaws Garry’s Mod’s. What was originally just going to be a weapon base has now expanded into a full entity project that is attempting to be an alternative to GMod’s to standardise creation, and add consistency to facets like networking and inheritance. Since many inconsistencies in GMod’s API come from backwards compatibility, I would like to get this right the first time around. I am planning on supporting all scripted entity types, including animated ents, clientside/serverside only ents, brushes, weapons, vehicles, NPCs, etc.; due to that fact, I want to keep the code as concise and uniform across all of these as possible. Since I am in the process of reforming the structure, I would love to get community feedback on what they would like to see in a new scripted entity system. Here are some current features:

Automatic BaseClass creation

Traditionally, all entities and gamemodes has to call DEFINE_BASECLASS or use the self.BaseClass entity variable to define or use a baseclass. Now, all weapons will have an automatic BaseClass variable provided based on their ENT/SWEP.Base setting. This is due to the entity files being loaded in an environment where BaseClass is a global. Entities can now look as simple as:

[lua]ENT.Base = “gs_baseanim”

function ENT:Initialize()
self.m_iSomeVar = 0

BaseClass.Initialize(self)

end[/lua]

Proper metatable inheritance

Garry’s Mod entity inheritance is currently done in the incorrect order for entities for speed reasons. This is fixed for all entities declared in this structure by storing the ent table in Lua. The new inheritance is:


- Individual entity table
- SENT/SWEP table
- Weapon/NPC/Vehicle/NextBot/CSEnt metatable (if applicable)
- Entity metatable

The new __index ends up being more efficient than Garry’s Mod’s default due to moving the ent table from C to Lua.

Non-networked predicted variables

To achieve proper variable prediction in entities, a NetworkVar/DTVar/NW(2)Var had to be used to make sure client re-predictions didn’t get a future value. PredictedVars works to achieve the same prediction capability without wasting network traffic where applicable. Unlike networked predicted vars, there is currently no plan to type-restrict input values, although, that might change to make them consistent with networked alternatives. Due to this lack of type assignment, default return values for variables has to be inserted to the GetPredictedVar calls.

[lua]SWEP.Base = “gs_baseweapon”

function SWEP:PrimaryAttack()
– Shoot code above

-- Increment total shots by one
-- 1st arg: Entity
-- 2nd arg: Variable name
-- 3rd arg (optional): Default return if the variable has not been set yet, default = nil
-- Returns: Previously set variable or default
local iShots = code_gs.entities.GetPredictedVar(self, "Shots", 0)

-- 1st arg: Entity
-- 2nd arg: Variable name
-- 3rd arg (optional): Variable value, default = nil
code_gs.entities.SetPredictedVar(self, "Shots", iShots + 1)

end[/lua]

Network/predicted variable wrapper

Networking for entities before varied between using NetworkVars (DTVars internally) and NW(2)Vars. The main problem with using DTVars was the inability to tell which slots were already used, making adding/removing vars a constant game of memory. On the other hand, NWVars has some network efficiency concerns, and Get/Set methods were not automatically created. To remedy this, I added a function similar to NetworkVar that does not need to be ran in a hook (like SetupDataTables), and uses NW2Vars internally. It also works for non-networked variables, which will use PredictedVars!

[lua]ENT.Base = “gs_basenpc”

– 1st arg: Table/Entity
– 2nd arg: Is networked
– 3rd arg: Data type (enum, no longer strings!)
– 4th arg: Name for Get/Set functions
– 5th arg (optional): Default value, default = previously defined value in default table
– 6th arg (optional): Callback function for on change, default = nil
code_gs.entities.PredictedAccessorFunc(ENT, true, TYPE_ENTITY, “CurrentTarget”)

function ENT:HasTarget()
return self:GetCurrentTarget():IsValid()
end[/lua]

Strict-type checking

I took a page out of the C Lua interface and ported strict-type checking to combat incorrectly-typed values causing silent issues. This also is a median solution to the global IsValid vs class IsValid argument, making sure type mixing doesn’t occur. Anyone who’s used GMod’s module interface should recognise the syntax from LUA->CheckType/IsType, or luaL_check*/luaL_argerror, where a failed type check will interrupt the stack. Custom types can also be registered.

[lua]-- 1st arg: String
– 2nd arg: Player or number
– 3rd arg (optional): Color
function ENT:DoSomething(sFoo, Bar, cBlah --[[= Color(255, 255, 255)]])
– 1st arg: Variable
– 2nd arg: Argument number
– 3rd arg: Type number or table of types
– 4th arg (optional): Stack level to error at, default = 2
– Returns: Type. This will be always be equal to the used TYPE_ number with one type. With a table, will be the actual type
– If failed, will error with "bad argument #1 to DoSomething (string expected, got " … type(sFoo) … “)”
code_gs.CheckType(sFoo, 1, TYPE_STRING)

-- TYPE_PLAYER doesn't exist normally, so it had to be registered individually
code_gs.CheckType(Bar, 2, {code_gs.TYPE_PLAYER, TYPE_NUMBER})

-- Set the default val if cBlah is nil
if (code_gs.CheckType(cBlah, 3, {TYPE_NIL, TYPE_COLOR}) == TYPE_NIL) then
	cBlah = Color(255, 255, 255)
end

-- More code

end[/lua]

[lua]-- 1st arg: Table with key “foo” with a string type
function ENT:DoSomethingElse(tbl)
code_gs.CheckType(tbl, 1, TYPE_TABLE)

if (not code_gs.IsType(tbl.foo, 1, TYPE_STRING)) then
	-- 1st arg: Argument number
	-- 2nd arg: Sub-error string
	-- Will error with "bad argument #1 to DoSomethingElse (" .. Sub_Error .. ")"
	code_gs.ArgError(1, "bad key \"foo\" to table -- string expected, got " .. type(tbl.foo))
end

end[/lua]

Multi- and cross-inheritance

Entities can inherit from multiple bases like C++, acting more like interfaces than strict bases. They are inherited in numerical order, starting with the first and inheriting on top of that. They can also inherit from entities not of their type to prevent the undefined conflicts that would occur in Garry’s Mod when you register a SENT and SWEP of the same class.

[lua]-- Implements grenade interface on top of the base weapon
SWEP.Base = {“gs_baseweapon”, “gs_baseweapon_grenade”}[/lua]

[lua]-- Inherits from vehicle and grenade interface, from a scripted entity!
ENT.Base = {“gs_basevehicle”, “gs_baseweapon_grenade”}[/lua]

Bases and types

Bases are no longer set by default in an entity: if it has an non-existent or undefined base, the entity won’t be loaded. The ENT.Type key is no longer used – instead, the base inherited from (gs_basebrush, gs_basenpc, gs_basevehicle, gs_baseweapon, etc.) will determine the type, much like a traditional inheritance/casting system.

Viewmodel index stack (for weapons)

Weapons support multiple viewmodels. This is achieved through a a per-weapon index stack that will change the output/effect of called functions on weapons without having to pass around a index argument to every function.


code_gs.entities.PushIndexStack(pWeapon, 0) -- First view model
	print(pWeapon.ViewModel) -- models/weapons/v_pistol.mdl
	pWeapon:DoAttack() -- Will shoot the first view model alone
code_gs.entities.PopIndexStack(pWeapon)

code_gs.entities.PushIndexStack(pWeapon, 1) -- Second view model
	print(pWeapon.ViewModel) -- models/weapons/v_smg1.mdl
	pWeapon:DoReload() -- Will reload the second view model alone
code_gs.entities.PopIndexStack(pWeapon)

The way this is implemented in each weapon is still being played around with, but that is the expected results.

Questions and feedback

This is just a small snippet of the planned features, but my main concern is getting community feedback on the current direction, and a few more concerns:

Entity namespace

Currently, I retained Garry’s Mod current system: weapon files use SWEP and everything else uses ENT. Should this be changed to something else? Should they all use ENT? Should NPCs use SNPC and vehicles SVEH, or left as ENT?

Folder structure

Folder structure when mirrored from ~/garrysmod/ currently looks like:


--> lua
	--> code_gs
		--> lib
		--> ents
			--> entities
				--> gs_baseanim
				--> gs_basebrush
				--> gs_basevehicle
				--> ...
			--> weapons
				--> gs_baseweapon

Note that the “ents” and “lib” folders are the names of the specific addons. This will be separated into multiple folders in ~/addons/ itself. My question is if the structure should be changed from Garry’s Mod’s “entities” and “weapons” folders, or should be preserved. I hypothesised about putting everything into entities like:


--> lua
	--> code_gs
		--> lib
		--> ents
			--> entities
				--> gs_baseanim
				--> gs_basebrush
				--> gs_basevehicle
				--> gs_baseweapon
				--> etc

Or perhaps the opposite route, but it feels a bit redundant:


--> lua
	--> code_gs
		--> lib
		--> ents
			--> animated
				--> gs_baseanim
			--> brushes
				--> gs_basebrush
			--> vehicles
				--> gs_basevehicle
			--> weapons
				--> gs_baseweapon
			--> etc

Library function namespace

Library function are currently put in their respective global/meta tables:

[lua]function math.PoundsToKilograms(flPounds)
return flPounds * 0.4535924277
end

local ENTITY = FindMetaTable(“Entity”)

function ENTITY:SetWaterLevel(iLevel)
return self:SetSaveValue(“waterlevel”, iLevel)
end[/lua]

Should this be changed to be contained in the addon? Ex.

[lua]function code_gs.lib.math.PoundsToKilograms(flPounds)
return flPounds * 0.4535924277
end

function code_gs.lib.entity.SetWaterLevel(self, iLevel)
return self:SetSaveValue(“waterlevel”, iLevel)
end[/lua]

Thank you for reading; answers to the presented questions, further concerns, and suggestions are appreciated!

Imo I don’t really feel that calling all important methods from your library (code_gs.lib.*) sounds like a convenient and cool way to work in the case you want other people to implement your API, I would go with inherit methods from the entity base prebuilt from it or something shorter just like ent.or base

I agree with leaving code_gs.lib.* functions in their respective global tables – the long namespace is really unappealing imo. I am, however, still on the fence about the code_gs.entities.* methods: putting them in a base would allow custom entities to override them which is what I don’t want to happen. Keeping them in code_gs.entities.* will make them act more like utility than entity methods, however.

I can’t see a reason why not. self:SetWaterLevel(…) and self:SetPredictedVar(…) are cleaner to read, faster to type, and simpler to remember than code_gs.entities.GetPredictedVar(self, …) or code_gs.lib.entity.SetWaterLevel(self, …), and if someone wants to override a base method, that’s their own business.

e: Consider vital built-in entity methods like ENT:NetworkVar(): it could’ve been implemented as nwvars.AddNetworkedVar(ent), but it wasn’t, because that would’ve been ugly and unnecessary (and it’d look jarring next to a bunch of ‘self’ method calls). And even for C++ methods which shouldn’t be removed and can’t be recreated in Lua, you still might want to add a wrapper that still calls the base function.

Thank you, your reasoning and example was very solid. I will add them to the base ENT table so that all entities can run self:SetPredictedVar, etc. easily. Coincidentally, this does solve the issue of the index stack being used with non-weapons since I can simply add them to the base weapon.

ur mom lol


(User was permabanned for this post ("Gimmick. Consider your main perma'd" - UncleJimmema))

Welcome to Facepunch. Your feedback is appreciated.

I’d like NextThink to actually work.

Think is used by the entity base to maintain callback events among other variable updates. Custom Think methods have been replaced with a method called “ItemFrame” that can be set to call next with “SetNextItemFrame” without having to return true. Thought it was a nice side-effect of having to reserve Think.

In other words, NextThink is no longer used and replaced with something that works.

please make it so we can find all local players ents on map this is what i nomaly do.


 local barrel1=nil
local barrel2=nil

function discoverthebarrels()

	local tab=ents.FindInSphere(LocalPlayer():GetPos(),200)	
	
	for k,v in pairs(tab) do
		
		if v:EntIndex()>0 then
		
			if v:GetModel()=="models/props_borealis/bluebarrel001.mdl" and v:CPPIGetOwner()==LocalPlayer() then
				
				if barrel1==nil then barrel1=v 
					
				elseif barrel1~=nil and barrel2==nil then barrel2=v end
					
			end
			
		end
		
	end

end


That has more to do with prop ownership, which is up to the implementation of CPPI. Scripted entities only have ownership by default for physics/collision purposes. Off topic, you could always just find entities by model, instead:


function discoverthebarrels()
	local tEntities = ents.FindByModel("models/props_borealis/bluebarrel001.mdl")
	local pPlayer = LocalPlayer()
	
	local tBarrels = {}
	local iBarrels = 0
	
	for i = 1, #tEntities do
		local pEntity = tEntities*
		
		if (pEntity:CPPIGetOwner() == pPlayer) then
			iBarrels = iBarrels + 1
			tBarrels[iBarrels] = pEntity
		end
	end
	
	return tBarrels, iBarrels
end

i was find 2 props then rendering a line between them.



local barrel1=nil
local barrel2=nil

function discoverthebarrels()

	local tab=ents.FindInSphere(LocalPlayer():GetPos(),200)	
	
	for k,v in pairs(tab) do
		
		if v:EntIndex()>0 then
		
			if v:GetModel()=="models/props_borealis/bluebarrel001.mdl" and v:CPPIGetOwner()==LocalPlayer() then
				
				if barrel1==nil then barrel1=v 
					
				elseif barrel1~=nil and barrel2==nil then barrel2=v end
					
			end
			
		end
		
	end

end

discoverthebarrels()

hook.Add("PostDrawOpaqueRenderables", "test", function()
	
	render.DrawLine(barrel1:GetPos(), barrel2:GetPos(), Color(255,2,2,255), false)

end)


You can loop through the resulting table of the function I posted above and draw a line between each two barrels. If you only want the closest two, then you can do distance checks on each of the barrels and find out which are closest to the player.

Feel free to PM me or add on Steam if you need more help.

I like the idea here and so far it’s looking pretty good!

I just wanted to give my opinion about the naming. I like the idea of having code_gs.entities.* since to me it looks more organized and satisfies my OCD, but I do recommend maybe shorting it a little bit. For example changing code_gs.* to cgs.* would make it easier and faster to work with.

Or, you could just use:



sent


Garry didn’t name the hook library like this:



garrynewman.Add( "HUDPaint", "blah", HUD )


It makes sense for the name to actually have relevance to what it’s used for.

[editline]3rd September 2017[/editline]

Doesn’t this make way more sense?



function ENT:DoSomethingElse( tbl )
	sent.CheckType( tbl, 1, TYPE_TABLE )
	if not sent.IsType( tbl.foo, 1, TYPE_STRING ) then
		sent.ArgError( 1, "bad key \"foo\" to table -- string expected, got " .. type( tbl.foo ) )
	end
end


[editline]3rd September 2017[/editline]

Imagine if every Garry’s Mod thing was named like yours:

I think you’re being way too harsh over something that probably isn’t as ego-fueled as you’re suggesting. Honestly it sounds like you’re trying to start drama.

With that said, I don’t think it needs to be named gs, but I do agree that it should be different to prevent conflicts.

code_gs, 1v1 me in Minecraft

I know it’s not really egotistical, I just can’t think of a better word. I just don’t think your name needs to be plastered over the code of others.

to be fair its his project and he can do what he wants with it. if you don’t wanna use his shet then dont lel

My concern: non-unique namespaces are always prone to conflicting. Using your username makes that never occur. I honestly wanted to use GS, gs, or cgs, but GitHub/workshop code search found at least 3 addons using that already as a global table. Any other suggestions to find a middle ground would be great.

[editline]3rd September 2017[/editline]

The methods in code_gs.entities.* will remain there, but will also be available in the meta table so you can do self:*. You will still have to use code_gs.CheckType, but can you can change the name as a local on the top.

[editline]3rd September 2017[/editline]

Gold swords, no armour.

Have you tried using the vararg that is returned from include? I haven’t tried it myself, but I enjoy being able to set the name of my things myself in other languages.

For instance, in JavaScript:


var gulp = require('gulp')

Perhaps there is a way we can do this:


local sent = include('gs/sent')