• Why are scripted entities so stupid..?
    3 replies, posted
If I create more than one of the same entity in a single map, all the keys and values of all the entities get run for EVERY entity. (The keys and values run for the last processed entity override any keys/values of the other entities) For example, if I have an entity in my map with the keyvalue pair 'spawn_time' set to 5, and I copy that entity and change the 'spawn_time' value for the new copied entity to 20, when I run the map EVERY entity of that type has 'spawn_time' set to 20. The 5 value gets overridden when it obviously shouldn't be. Any explanation or way to get it to work the way it SHOULD? I would report it as a bug but it's been this way for as long as I can remember... WHY? [editline]11:00PM[/editline] My entities in hammer: [code]entity { "id" "1515" "classname" "zombie_spawner" "spawn_count" "100" "spawn_interval" "2" "spawn_max_alive" "6" "spawn_radius" "82" "targetname" "spawn_1" "zombie_health" "1.0" "zombie_type" "deadly" "origin" "-864 -160 8" editor { "color" "0 255 0" "visgroupshown" "1" "visgroupautoshown" "1" "logicalpos" "[500 -14268]" } } entity { "id" "3637" "classname" "zombie_spawner" "spawn_count" "10" "spawn_interval" "15" "spawn_max_alive" "2" "spawn_radius" "82" "targetname" "spawn_2" "zombie_health" "4.0" "zombie_type" "deadly" "origin" "-1184 -160 8" editor { "color" "0 255 0" "visgroupshown" "1" "visgroupautoshown" "1" "logicalpos" "[500 -14268]" } }[/code] Entities as reported in game: [code] ] lua_run spawns = ents.FindByClass("zombie_spawner") > spawns = ents.FindByClass("zombie_spawner")... ] lua_run PrintTable(spawns[1].SpawnParam) > PrintTable(spawns[1].SpawnParam)... Type = deadly Amount = 10 Interval = 15 Radius = 82 HealthMultiplier = 4 MaxAlive = 2 ] lua_run PrintTable(spawns[2].SpawnParam) > PrintTable(spawns[2].SpawnParam)... Type = deadly Amount = 10 Interval = 15 Radius = 82 HealthMultiplier = 4 MaxAlive = 2[/code] My KeyValue hook code: [code]function ENT:KeyValue(key, value) //Msg( self:GetClass() .. " says: " .. key .. " should be ".. value.."\n") if( key == "zombie_type" ) then self.SpawnParam["Type"] = string.Trim(string.lower(value)) elseif( key == "zombie_health" ) then self.SpawnParam["HealthMultiplier"] = math.Clamp(tonumber(value), 0.1, 4.0) elseif( key == "spawn_max_alive" ) then self.SpawnParam["MaxAlive"] = math.Clamp(tonumber(value), 1, 16) elseif( key == "spawn_count" ) then self.SpawnParam["Amount"] = math.Clamp(tonumber(value), 1, 1000) elseif( key == "spawn_interval" ) then self.SpawnParam["Interval"] = math.Clamp(tonumber(value), 0.5, 300) elseif( key == "spawn_radius" ) then self.SpawnParam["Radius"] = math.Clamp(tonumber(value), 0, 1024) end end[/code]
Okay, I guess SENTs aren't really implemented in a very OO fashion. I've found a way to make entities work like this by creating subtables in the SpawnParam table for each entity, as well as one for the default settings, but think this is kind of redundant. Oh well, thanks for the help guys. Here's how I got it working, if it could help anyone in the future... [code]ENT.Type = "point" ENT.Base = "base_point" ENT.SpawnParam = { } ENT.SpawnParam["DefaultSettings"] = { Type = "slave", HealthMultiplier = 1.0, MaxAlive = 1, Amount = 10, Interval = 5.0, Radius = 128 } ENT.SpawnVals = { } ENT.SpawnVals["DefaultSettings"] = { SpawnCount = 0, ActiveZombies = {} } function ENT:TimerID() return "zombiespawn_" .. self:EntIndex() end function ENT:GetSpawnParam(ParamKey) if(ParamKey == nil) then return self.SpawnParam[self:EntIndex()] else return self.SpawnParam[self:EntIndex()][ParamKey] end end function ENT:GetSpawnVals(ParamKey) if(ParamKey == nil) then return self.SpawnVals[self:EntIndex()] else return self.SpawnVals[self:EntIndex()][ParamKey] end end function ENT:KeyValue(key, value) Msg( self:GetClass() .. "["..self:EntIndex().."] - KeyValuePair " .. key .. " & ".. value.."\n") if( self.SpawnParam[self:EntIndex()] == nil) then self.SpawnParam[self:EntIndex()] = { } table.Merge(self.SpawnParam[self:EntIndex()], self.SpawnParam["DefaultSettings"]) end if( key == "zombie_type" ) then self.SpawnParam[self:EntIndex()]["Type"] = string.Trim(string.lower(value)) elseif( key == "zombie_health" ) then self.SpawnParam[self:EntIndex()]["HealthMultiplier"] = math.Clamp(tonumber(value), 0.1, 4.0) elseif( key == "spawn_max_alive" ) then self.SpawnParam[self:EntIndex()]["MaxAlive"] = math.Clamp(tonumber(value), 1, 16) elseif( key == "spawn_count" ) then self.SpawnParam[self:EntIndex()]["Amount"] = math.Clamp(tonumber(value), 1, 1000) elseif( key == "spawn_interval" ) then self.SpawnParam[self:EntIndex()]["Interval"] = math.Clamp(tonumber(value), 0.5, 300) elseif( key == "spawn_radius" ) then self.SpawnParam[self:EntIndex()]["Radius"] = math.Clamp(tonumber(value), 0, 1024) end end function ENT:ConfirmDead( zombie ) Msg("Zombie #" .. zombie:EntIndex() .. " is now dead. :( \n") for k,v in pairs(self.SpawnVals[self:EntIndex()].ActiveZombies) do if(v == zombie) then table.remove(self.SpawnVals[self:EntIndex()].ActiveZombies, k) Msg(self:EntIndex()..": Removed...\n") break end end end function ENT:CreateZombie() local zombie = ents.Create( "npc_zombie2" ) // -- change to "zombie_" .. self.GetSpawnParam()["Type"] to add types local sPos = self:GetPos() local sAng = math.random(0,360) local sDist = math.random(0, self:GetSpawnParam()["Radius"]) sPos = Vector(sPos.x + math.sin(sAng) * sDist, sPos.Y + math.cos(sAng) * sDist, sPos.z - 1) zombie:SetPos(sPos) zombie:Spawn() local oldhealth = zombie:GetMaxHealth() zombie:SetMaxHealth(oldhealth * self:GetSpawnParam()["HealthMultiplier"]) zombie:SetHealth(zombie:GetMaxHealth()) local tList = team.GetPlayers( 1 ) // 1 = TEAM_ALIVE if( #tList > 0 ) then local target = tList[math.random(1, #tList)] zombie:AddRelationship(target:GetClass() .. " D_HT 99") zombie:SetEnemy(target) end self.SpawnVals[self:EntIndex()].SpawnCount = self.SpawnVals[self:EntIndex()].SpawnCount + 1 if(self:GetSpawnVals()["SpawnCount"] >= self:GetSpawnParam()["Amount"]) then self:StopSpawning() end table.insert(self.SpawnVals[self:EntIndex()].ActiveZombies, zombie) end function ENT:Initialize() if( self.SpawnVals[self:EntIndex()] == nil ) then self.SpawnVals[self:EntIndex()] = { } table.Merge(self.SpawnVals[self:EntIndex()], self.SpawnVals["DefaultSettings"]) end timer.Adjust("zombiespawn_" .. self:EntIndex(), self:GetSpawnParam()["Interval"], 0, self.CreateZombie, self) end function ENT:StartSpawning() timer.Adjust(self:TimerID(), self:GetSpawnParam()["Interval"], 0, self.CreateZombie, self) timer.Start(self:TimerID()) end function ENT:StopSpawning() timer.Stop(self:TimerID()) end[/code]
In lua, all variables (even encased in functions) are on the global scope, unless you explicitly set them as local, I believe (according to the Lua documentation). SO you could try to set each property of your prototype as local, then use sets and gets for them. Just a suggestion.
The problem is, you are initializing the SpawnParam table on the ENT table. This ENT table is like a base class to all sents that derive off it, so all sents which do so share the SpawnParam table. When one of them gets the key value data, it overrides the shared table. What you need to do, is initialize the table in KeyValue using self. Like this: [lua]ENT.Type = "point" ENT.Base = "base_point" local DefaultSpawnParam = { Type = "slave", HealthMultiplier = 1.0, MaxAlive = 1, Amount = 10, Interval = 5.0, Radius = 128 } function ENT:TimerID() return "zombiespawn_" .. self:EntIndex() end function ENT:GetSpawnParam(ParamKey) return self.SpawnParam[ ParamKey ] or self.SpawnParam end function ENT:GetSpawnVals(ParamKey) return self.SpawnVals[ ParamKey ] or self.SpawnVals end function ENT:KeyValue(key, value) Msg( self:GetClass() .. "["..self:EntIndex().."] - KeyValuePair " .. key .. " & ".. value.."\n") if (!self.SpawnParam) then self.SpawnParam = table.Copy( DefaultSpawnParam ) end if (!self.SpawnVals) then self.SpawnVals = {} end if( key == "zombie_type" ) then self.SpawnParam["Type"] = string.Trim(string.lower(value)) elseif( key == "zombie_health" ) then self.SpawnParam["HealthMultiplier"] = math.Clamp(tonumber(value), 0.1, 4.0) elseif( key == "spawn_max_alive" ) then self.SpawnParam["MaxAlive"] = math.Clamp(tonumber(value), 1, 16) elseif( key == "spawn_count" ) then self.SpawnParam["Amount"] = math.Clamp(tonumber(value), 1, 1000) elseif( key == "spawn_interval" ) then self.SpawnParam["Interval"] = math.Clamp(tonumber(value), 0.5, 300) elseif( key == "spawn_radius" ) then self.SpawnParam["Radius"] = math.Clamp(tonumber(value), 0, 1024) end end function ENT:ConfirmDead( zombie ) Msg("Zombie #" .. zombie:EntIndex() .. " is now dead. :( \n") for k,v in pairs(self.SpawnVals.ActiveZombies) do if(v == zombie) then table.remove(self.SpawnVals.ActiveZombies, k) Msg(self:EntIndex()..": Removed...\n") break end end end function ENT:CreateZombie() local zombie = ents.Create( "npc_zombie2" ) // -- change to "zombie_" .. self.GetSpawnParam("Type") to add types local sPos = self:GetPos() local sAng = math.random(0,360) local sDist = math.random(0, self:GetSpawnParam("Radius")) sPos = Vector(sPos.x + math.sin(sAng) * sDist, sPos.Y + math.cos(sAng) * sDist, sPos.z - 1) zombie:SetPos(sPos) zombie:Spawn() local oldhealth = zombie:GetMaxHealth() zombie:SetMaxHealth(oldhealth * self:GetSpawnParam("HealthMultiplier") zombie:SetHealth(zombie:GetMaxHealth()) local tList = team.GetPlayers( 1 ) // 1 = TEAM_ALIVE if( #tList > 0 ) then local target = tList[math.random(1, #tList)] zombie:AddRelationship(target:GetClass() .. " D_HT 99") zombie:SetEnemy(target) end self.SpawnVals.SpawnCount = self.SpawnVals.SpawnCount + 1 if(self:GetSpawnVals("SpawnCount") >= self:GetSpawnParam("Amount") then self:StopSpawning() end table.insert(self.SpawnVals.ActiveZombies, zombie) end function ENT:Initialize() self:KeyValue( nil, nil ) -- This will initialize our tables for us, without doing anything else if (!self.SpawnVals.ActiveZombies) then self.SpawnVals.ActiveZombies = {} end if (!self.SpawnVals.SpawnCount) then self.SpawnVals.SpawnCount = 0 end timer.Adjust("zombiespawn_" .. self:EntIndex(), self:GetSpawnParam("Interval"), 0, self.CreateZombie, self) end function ENT:StartSpawning() timer.Adjust(self:TimerID(), self:GetSpawnParam("Interval"), 0, self.CreateZombie, self) timer.Start(self:TimerID()) end function ENT:StopSpawning() timer.Stop(self:TimerID()) end[/lua] Untested, should work. I might have missed something though. [editline]10:24AM[/editline] You might want to standardize your use of self.SpawnVals against self:GetSpawnVal rather than mixing and matching everywhere too.
Sorry, you need to Log In to post a reply to this thread.