Hi people. I've made a tool which allows you to create complex patrol routes for NPC's. It uses custom effects to show the nodes on screen, and it works fine for most people. However, some people reported they can't see the nodes, and they get a lua error every time the nodes are supposed to show up. The tool uses this function to create the effects:
local function CreatePatrolPointEffect(pos)
local allents = ents.GetAll()
util.Effect("effect_patrolpoint",EffectData())
local new = ents.GetAll()[#allents +1]
if(!new) then return end
new:SetOrigin(pos)
return new
end
It's a little trick I learned from Silverlan. Since util.Effect() doesn't return anything, I have to use ents.GetAll() to get the effect reference. This function is then used here:
local function ShowPatrolPoints(b)
hook.Remove("RenderScreenspaceEffects","patrolroutes_renderpoints")
for _,ent in ipairs(PointEffects) do ent.pr_remove = true end
table.Empty(PointEffects)
if(!b) then return end
for k,point in ipairs(PatrolPoints) do
PointEffects[k] = CreatePatrolPointEffect(point[1])
PointEffects[k]:SetID(_) -- This line gives an error
PointEffects[k]:SetWait(point[2])
end
-- irrelevant stuff
end
As I said, some people reported the marked line throws an error, saying "attempt to index a nil value". The only thing that could cause that error, is that the game is unable to create the effect. It's weird it only happens to some people. It could be an incompatibility with another addon, but nobody reported more lua errors at map start. Do anyone know in which cases the game wouldn't be able to create a clientside effect?
Honestly, I don't really see why you're doing it this way. It seems pretty hacky, and not really optimal.
I wouldn't even bother creating an effect with this. As you should be able to get away with just creating your own metatable/class and recreate the functions you need from your effect. You can then just add them to your own index table, iterate that table in the proper render hooks and call their render functions.
I've never worked with metatables, and you can't create clientside entities anymore. I use effects because that's the closest thing to a clientside entity (actually, I didn't make that function, my tool is based on another mod, which I asked permission to use). I could have used clientside models instead, but then I couldn't have variables and functions for each node model. I guess that's what metatables are for, but I'm not familiar with them.
I just need to know in which case the game would be unable to spawn an effect.
You cannot target effect entities unless they are a Lua effect - and even in that case, they are not an entity and cannot be targeted externally. The core of your problem is:
local new = ents.GetAll()[#allents +1]
This is not a method for entity creation and will always return nil. ents.GetAll is also not guaranteed to be in the same order after creation.
Well, it works. The tool has been in the workshop for two years now, and it has 5 stars.
local new = ents.GetAll()[#allents +1]
I know this is not an entity creation. The (clientside) entity was created the line above it, it's a lua effect, and it's reference got placed next to the last entity in the list. "#allents" is the number of entities in the map before creating the effect, so the next element in the list has to be the effect I just created. And yes, it has to be an entity, otherwise I wouldn't be able to reference it. I can assure you that it works for me, and many other people, but there are very few people that don't. For some reason util.Effect() fails to create the effect, so #allents+1 returns nothing. I want to know if there's anything that could stop that function from spawning an effect, because even if I could not reference it, the effect doesn't show up in game.
Are you aware entities are purely userdata with lua fields like methods and functions right? I wrote it in next update...You are thinking it incorrectly, you gotta understand how real entities works, once you do, you will see really easy a way to create clientside entities that fit your needings
You want entities to have a function and variables? Create a table with all those functions and variable and then merge it inside a ClientsideModel or ent.CreateProp, nobody disallows you to add functions, variables and any kind of stuff there, since csmodels are entities, you can target SetEntity inside util.Effect so I'm still missing the point about where are you getting struggles
I know there may be other ways to do what I do, better ways, but I didn't want to recode half the tool. This is just a small part of it, used to show the patrol routes in game. There are rare cases in which it doesn't work, and I wanted to see if any of you could give me a hand to understand why. I don't want to know how to make it better, I want to know why sometimes it doesn't work. I made a very specific question: What could make util.Effect() fail to create an effect? I couldn't find anything useful in the wikis, so I asked here. If you can't help me with that, that's ok, but I'd appreciate if you could just answer my question instead of telling me how terrible the code is.
Actually I agree that fact util.Effect sometimes doesn't work at all in clientside, forcing me to call it serverside or even shared, not idea what actually!
What do you want to do actually, something like a tool that spawns an entity and it shows an effect?
The tool allows you to place nodes and link them to create patrol routes. The nodes you have placed are stored in a table, with their current positions. They don't exist serverside until you assign the route to an NPC. The effects are just a way to show them clientside, they are just boxes that change their color when you look at them. They are only visible when a variable is true and the player has the toolgun equipped with this tool selected. It works the way it's done now, but some people say they can't see the nodes, and they get a lua error (the effects is not created, so the variable is nil). I'm talking with one user now who has this problem, trying to figure out if it's a conflict with another addon, but it's really weird. The game shouldn't have problems creating an effect, and it isn't a random chance of failing. It either works for you or it doesn't. The only thing I think that could be happening, is that another addon has a hook that's executed when an effect is created, and it's breaking the tool somehow.
I've done something like this for Monolith, to create roads, a waypoint system, aswell i know what are your intentions, you gotta understand what are the main purpose of it, if you want this for debug and show a preview of it, debugoverlay is nice and enough!
In this case, you don't need an entity, you can play with PostDrawTranslucentRenderables and process your table there, apply some cam3d2d effects and even play with render.* lib, my recomendation about a good looking effect? Create by yourself a trail material animated (Is more easy than you might thing) and then draw a beam from A to B, i know it's a turn down, but you'll find sometimes situations were nothing works and then you will have to rethink everything, like...You can't create a fizzle effect, it's okay, let's not waste more time on it, but what about if you draw some dots from A to B and animate it with pure lua and lerping, a trail, sprites, everything to make it look great!
I still don't see why you're using an effect just to draw some clientside info. It just seems silly.
Maybe try attaching a variable to said effect in your effect lua file, and just try something like this?
(Where SomeVariable is the variable you set on your effect)
--In your effect code:
EFFECT.SomeVariable = true;
--Creation function you have at the bottom:
for k, v in pairs(ents.FindByClass("class CLuaEffect")) do
if (v.SomeVariable && !table.HasValue(PointEffects, v)) then return v; end
end
Because, as code_gs stated, there's no 100% guarantee that ents.GetAll will be in the order of creation.
Because of that, what you're doing is getting the LAST entity in that table, and praying its the correct entity.
I would use a numeric-for loop and iterate backwards since the first 100 or so entries in ents.GetAll are usually ai_nodes - it's a pretty significant constant time save.
The thing is that #allents+1 returns the effect when it works, but when it doesn't it returns nil, which means there isn't anything there. The number of entities is the same before and after doing util.Effect(), so the effect was not created. Otherwise, it would return something else, even if ents.GetAll() was in a different order. I doubt doing a for block to look for the effect would make any difference, I'm pretty sure the effect is never created.
The links between nodes is already done. It's just a simple beam scrolling from the source node to its destination. It's done with a RenderScreenspaceEffects hook and cam.Start3D(). Nothing fancy, but works. The effect part is only to render the nodes. It's probably not the best method, but it was done like that in the tool I used as a base. If you want to have a better idea of how the tool works, here's the link:
http://steamcommunity.com/sharedfiles/filedetails/?id=624692150
Yes, but it doesn't mean that that #allents+1 is your effect, it can be any entity.
When it works, it IS the effect, otherwise most of the clientside rendering part would be broken. PointEffects[k] stores all the references to the node effects, and then I use it to draw the links and some floating texts. None of that would work if the returned value wasn't the effect I just created.
When it does not work, it returns nil, not a different entity.
I'm saying there are cases when it CAN work and still return a different entity because ents.GetAll ordering is not guaranteed.
I don't know what could cause it to fail.
But I took the liberty to turn your effect into a lua object(hope you don't mind), you should be able to use it the same way, without the ents.GetAll hacky method.
Put this in a clientside script:
PATROL_POINTS = PATROL_POINTS || {}
patrolPoint = {}
patrolPoint.__index = patrolPoint
setmetatable(patrolPoint, { __call = function (c, ...) return c.create(...) end})
function patrolPoint.create()
local self = {}
setmetatable(self, patrolPoint)
self:SetPos(Vector(0, 0, 0))
self:Init()
table.insert(PATROL_POINTS, self)
return self
end
function patrolPoint:SetPos(pos)
self.Pos = pos
end
function patrolPoint:GetPos(pos)
return self.Pos
end
function patrolPoint:SetModel(mdl)
self.myModel = ClientsideModel(mdl)
self.myModel:SetNoDraw(true)
end
function patrolPoint:IsValid()
return true;
end
function patrolPoint:DrawModel()
if (self.myModel) then
self.myModel:SetPos(self.Pos)
self.myModel:DrawModel()
end
end
function patrolPoint:Init(data)
self:SetModel("models/hunter/blocks/cube025x025x025.mdl")
--self:SetModel("models/XQM/Rails/trackball_1.mdl")
--self:SetModelScale( 0.4, 0 )
self.pr_visible = true
end
function patrolPoint:SetID(ID) self.pr_id = ID end
function patrolPoint:GetID() return self.pr_id end
function patrolPoint:SetWait(wait) self.pr_wait = wait end
function patrolPoint:GetWait() return self.pr_wait end
function patrolPoint:OnRemove()
if (self.myModel) then
self.myModel:Remove()
end
end
function patrolPoint:SetOrigin(pos) self.pr_origin = pos self:SetPos(pos) end
function patrolPoint:SetVisible(b) self.pr_visible = b end
function patrolPoint:Think()
self:SetPos(self.pr_origin)
return !self.pr_remove
end
hook.Add("Think", "patrolThink", function()
for i = #PATROL_POINTS, 1, -1 do
local v = PATROL_POINTS[i]
if (v) then
if (v.Think && !v:Think()) then
if (v.OnRemove) then
v:OnRemove()
end
table.remove(PATROL_POINTS, i)
end
end
end
end)
local colText = Color(255,255,255,255)
function patrolPoint:Render()
if(!self.pr_visible) then return end
self:DrawModel()
local id = self:GetID()
local wait = self:GetWait()
if(id) then
local ang = LocalPlayer():EyeAngles()
local pos = self:GetPos() +Vector(0,0,30)
ang:RotateAroundAxis(ang:Forward(),90)
ang:RotateAroundAxis(ang:Right(),90)
cam.Start3D2D(pos,Angle(0,ang.y,90),0.5)
draw.DrawText(id,"default",0,0,colText,TEXT_ALIGN_CENTER)
if wait and wait > 0 then draw.DrawText("Wait: "..wait,"default",0,10,colText,TEXT_ALIGN_CENTER) end
cam.End3D2D()
end
end
--Or translucent renderable hook here..
hook.Add("PostDrawOpaqueRenderables", "drawPatrols", function()
for k, v in pairs(PATROL_POINTS) do
if (v && v.Render) then
v:Render()
end
end
end)
Then change your create function to:
local function CreatePatrolPointEffect(pos)
local new = patrolPoint();
if(!new) then return end
new:SetOrigin(pos)
return new
end
At least that way you won't have to worry about util.Effect failing somehow.
Note the code is not tested, so I'm not sure if there are any syntax errors.
Oh, thanks. That wasn't really necessary, but it helps. I'll have to analyze that code to understand how it works, and I should start using metatables from now on. Thanks for taking the time to modify the code.
I told one user that had this issue to try to start the game with "-noworkshop" and my tool as a legacy addon. The error is gone, and the nodes render properly, so apparently it's a conflict with another mod. Finding which one is conflicting will be a bit of a pain. I'll try replacing the effects with metatables, maybe that fixes this weird incompatibility so the tool works for everyone. Thanks people.
Sorry, you need to Log In to post a reply to this thread.