• What would you like to see in a new scripted entity system?
    22 replies, posted
[b]Community questions at the bottom![/b] 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: [b]Automatic BaseClass creation[/b] 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] [b]Proper metatable inheritance[/b] [URL="https://github.com/Facepunch/garrysmod-requests/issues/901"]Garry's Mod entity inheritance is currently done in the incorrect order for entities[/URL] for [URL="https://github.com/Facepunch/garrysmod/blob/master/garrysmod/lua/includes/extensions/entity.lua#L15"]speed reasons[/URL]. This is fixed for all entities declared in this structure by storing the ent table in Lua. The new inheritance is: [code]- Individual entity table - SENT/SWEP table - Weapon/NPC/Vehicle/NextBot/CSEnt metatable (if applicable) - Entity metatable[/code] The new __index ends up being more efficient than Garry's Mod's default due to moving the ent table from C to Lua. [b]Non-networked predicted variables[/b] 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] [b]Network/predicted variable wrapper[/b] 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] [b]Strict-type checking[/b] 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] [b]Multi- and cross-inheritance[/b] 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] [b]Bases and types[/b] 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. [b]Viewmodel index stack (for weapons)[/b] 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]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(pWeap
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
[QUOTE=gonzalolog;52636894]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[/QUOTE] 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.
[QUOTE=code_gs;52636929]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.[/QUOTE] 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.
[QUOTE=Luni;52638263]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.[/QUOTE] 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 [highlight](User was permabanned for this post ("Gimmick. Consider your main perma'd" - UncleJimmema))[/highlight]
[QUOTE=codeisgaylol;52639206]ur mom lol[/QUOTE] Welcome to Facepunch. Your feedback is appreciated.
I'd like NextThink to actually work.
[QUOTE=Promptitude;52639575]I'd like NextThink to actually work.[/QUOTE] 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. [code] 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 [/code]
[QUOTE=blueangel512;52639718]please make it so we can find all local players ents on map this is what i nomaly do. [/QUOTE] 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: [code]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[i] if (pEntity:CPPIGetOwner() == pPlayer) then iBarrels = iBarrels + 1 tBarrels[iBarrels] = pEntity end end return tBarrels, iBarrels end[/code]
[QUOTE=code_gs;52639735]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: [/QUOTE] i was find 2 props then rendering a line between them. [code] 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) [/code]
[QUOTE=blueangel512;52639744]i was find 2 props then rendering a line between them.[/QUOTE] 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: [CODE] sent [/CODE] Garry didn't name the hook library like this: [CODE] garrynewman.Add( "HUDPaint", "blah", HUD ) [/CODE] 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? [CODE] 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 [/CODE] [editline]3rd September 2017[/editline] Imagine if every Garry's Mod thing was named like yours: [t]http://i.imgur.com/WFfFI2X.png[/t]
[QUOTE=MPan1;52642178]Or, you could just get rid of the self-glorification and use: [CODE] sent [/CODE] Garry didn't name the hook library like this: [CODE] garrynewman.Add( "HUDPaint", "blah", HUD ) [/CODE] 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 and seem less egotistical? [CODE] 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 [/CODE] [editline]3rd September 2017[/editline] Imagine if every Garry's Mod thing was named like yours: [t]http://i.imgur.com/WFfFI2X.png[/t][/QUOTE] 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.
[QUOTE=wauterboi;52642251]Honestly it sounds like you're trying to start drama[/QUOTE] 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
[QUOTE=MPan1;52642178]Or, you could just use: [CODE] sent [/CODE] Garry didn't name the hook library like this: [CODE] garrynewman.Add( "HUDPaint", "blah", HUD ) [/CODE] 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? [CODE] 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 [/CODE] [editline]3rd September 2017[/editline] Imagine if every Garry's Mod thing was named like yours: [t]http://i.imgur.com/WFfFI2X.png[/t][/QUOTE] 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] [QUOTE=vrej;52642161]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.[/QUOTE] 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] [QUOTE=MPan1;52642262]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.[/QUOTE] 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: [code]var gulp = require('gulp')[/code] Perhaps there is a way we can do this: [code]local sent = include('gs/sent')[/code]
You're here trying to renovate the entity system and I can't even understand how to make an access string in ulx Kappa Damn son. [QUOTE=MPan1;52642262]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.[/QUOTE] U fun at parties amirite?
[QUOTE=wauterboi;52644090]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: [code]var gulp = require('gulp')[/code] Perhaps there is a way we can do this: [code]local sent = include('gs/sent')[/code][/QUOTE] While ideally I would love the entirety of Garry's Mod to use a C#-styled include system, it would get annoying pretty quickly for people to use for my addon alone. I also don't want to create a new instance every time, which is what include does. I will change it to "gs" in the meantime and hope nothing conflicts.
Honestly I think a lot of Garry's Mod should be rewritten to fit the standards set by things like LuaRocks. Their module system is gouda.
Sorry, you need to Log In to post a reply to this thread.