Custom Trigger Box in Hammer.

Alright, here’s the idea, I’ll have stuff randomly spawn in selected areas and I’d like to know if it was possible to tie a brush to an entity and then get this one’s Bounding Box to spawn stuff within it’s perimeter.

Post in the Questions forum next time.

[editline]1st February 2011[/editline]

And yes I do believe it’s possible. I think that you can use Entity:OBBMins()/OBBMaxs() on custom brush entities.

I made one, it spawns npc’s, when I get home i’ll post the code for ya, i think i’m abandoning it, i also made an fdg so it will show in hammer.

Well thanks, I’d gladly take a look at your code.

Here it is it’s called area_npcspawn it works, but as a WIP it spams the console a fair amount, I was making it for a gamemode and have decide to not make that gamemode so I figured I might as well share.

Here’s the Lua, it’s all server side, and may be a little messy I you have questions just post em and i’ll try and answer what I did or why.

/entities/area_npcspawn/init.lua
[lua]

ENT.Type = “brush”

ENT.NPCTypes = {
“npc_antlion”,
“npc_antlionguard”,
“npc_combine_s”,
“npc_fastzombie”,
“npc_headcrab”,
“npc_headcrab_black”,
“npc_headcrab_fast”,
“npc_metropolice”,
“npc_poisonzombie”,
“npc_rollermine”,
“npc_zombie”,
“npc_zombine”,
“npc_zombie_torso”
}

ENT.NPCRndTypes = {
“npc_antlion”,
“npc_combine_s”,
“npc_fastzombie”,
“npc_headcrab”,
“npc_headcrab_black”,
“npc_headcrab_fast”,
“npc_metropolice”,
“npc_poisonzombie”,
“npc_zombie”,
“npc_zombine”,
“npc_zombie_torso”
}

ENT.NPCWeapons = {
“weapon_357”,
“weapon_ar2”,
“weapon_annabelle”,
“weapon_frag”,
“weapon_pistol”,
“weapon_rpg”,
“weapon_shotgun”,
“weapon_smg1”,
“weapon_stunstick”
}

ENT.NPCWeaponsRebel = {
“weapon_ar2”,
“weapon_pistol”,
“weapon_shotgun”,
“weapon_smg1”,
“weapon_crowbar”,
“weapon_stunstick”,
“weapon_rpg”
}

ENT.NPCWeaponsCombine = {
“weapon_ar2”,
“weapon_shotgun”,
“weapon_smg1”
}
ENT.NPCWeaponsPolice = {
“weapon_pistol”,
“weapon_smg1”,
“weapon_stunstick”,
“weapon_shotgun”
}

function ENT:SetRelations( ent )
for i, v in pairs(self.NPCTypes) do
ent:AddRelationship( v … " D_LI 999" )
end
ent:AddRelationship( “prop_physics D_HT 999” )
ent:AddRelationship( “player D_HT 999” )
ent:AddRelationship( “npc_citizen D_HT 999” )
ent:AddRelationship( “npc_alyx D_HT 999” )
ent:AddRelationship( “npc_monk D_HT 999” )
ent:AddRelationship( “npc_turret_floor D_HT 999” )
end

function ENT:PrintKeyValues()

	print(tostring(self) .. "'s Key Values and Variables")
	print("-=-=-=-=---===-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
	print("Key Values")
	print("model: " .. tostring(self.model))
	print("spawn_rate: " .. tostring(self.spawn_rate))
	print("origin: " .. tostring(self.origin))
	print("enabled: " .. tostring(self.enabled)) 
	print("spawnflags: " .. tostring(self.spawnflags)) 
	print("spawn_max: " .. tostring(self.spawn_max)) 
	print("delay_modify: " .. tostring(self.delay_modify)) 
	print("npc_class: " .. tostring(self.npc_class)) 
	print("npc_weapon: " .. tostring(self.npc_weapon)) 
	print("npc_drop: " .. tostring(self.npc_drop)) 
	print("npc_droprate: " .. tostring(self.npc_droprate))
	print("level_modify: " .. tostring(self.level_modify))
	print("level_rate: " .. tostring(self.level_rate))
	print("level_min: " .. tostring(self.level_min))
	print("level_max: " .. tostring(self.level_max)) 
	print("level_rnd: " .. tostring(self.level_rnd)) 
	print("min_rate: " .. tostring(self.min_rate)) 
	print("max_rate: " .. tostring(self.max_rate)) 
	print("maxinplay: " .. tostring(self.maxinplay)) 
	print("npc_spawnflags: " .. tostring(self.npc_spawnflags))
	print("baselevel: " .. tostring(self.baselevel))
	print("-----------------------------------------")
	print("Variables")
	print("def_spawn_rate: " .. tostring(self.def_spawn_rate))
	print("def_baselevel: " .. tostring(self.def_baselevel))
	print("size: " .. tostring(self.size))
	print("dropCount: " .. tostring(self.dropCount))
	print("NextSpawn: " .. tostring(self.NextSpawn))
	print("SpawnTotal: " .. tostring(self.SpawnTotal))
	print("lvlSpawnCount: " .. tostring(self.lvlSpawnCount))
	print("entsTable: ")
	PrintTable(self.entsTable)

end

function ENT:Initialize()

    print("area_npcspawn Initialize")
	self:SetModel(self.model)
     
    self:SetAngles( Angle( 0 , 90 , 0 ) )
     
    self:SetSolid( SOLID_BBOX )
       
    self:SetCollisionGroup( COLLISION_GROUP_WORLD )
	
	-- Set All Key Default Just encase they weren't set in hammer
	
	self.model = self.model or "models/error.mdl"
	self.spawn_rate = self.spawn_rate or 2
	self.origin = self.origin or self:GetPos()
	self.enabled = self.enabled or false 
	self.spawnflags = self.spawnflags or 0
	self.spawn_max = self.spawn_max or 0
	self.delay_modify = self.delay_modify or 0
	self.npc_class = self.npc_class or "npc_zombie"
	self.npc_weapon = self.npc_weapon or ""
	self.npc_drop = self.npc_drop or 0
	self.npc_droprate = self.npc_droprate or 0
	self.level_modify = self.level_modify or 0
	self.level_rate = self.level_rate or 0
	self.level_min = self.level_min or 0
	self.level_max = self.level_max or 0
	self.level_rnd = self.level_rnd or false
	self.min_rate = self.min_rate or 0
	self.max_rate = self.max_rate or 10
	self.maxinplay = self.maxinplay or 2
	self.npc_spawnflags = self.npc_spawnflags or 0
	self.baselevel = self.baselevel or 0

	self.def_spawn_rate = self.spawn_rate
	self.def_baselevel = self.baselevel
	self.size = Vector(0,0,0)
	self.dropCount = 0
	self.NextSpawn = CurTime()
	self.SpawnTotal = 0 
	self.lvlSpawnCount = 0
	self.entsTable = {}

end

function ENT:AcceptInput( name, activator, caller, data)

if !(name == nil) then print(tostring(name)) end
if !(activator == nil) then print(tostring(activator)) end
if !(caller == nil) then print(tostring(caller)) end

if !(name == nil) then 
	
	if name == "Enable" then self.enabled = true end
	if name == "Disable" then self.enabled = false end
	if name == "Reset" then
		self:TriggerOutput("Reset", activator)
		self.SpawnTotal = 0
		self.NextSpawn = CurTime() + 1
		self.spawn_rate = self.def_spawn_rate
		self.baselevel = self.def_baselevel
	end
	if name == "Spawn" then self:TriggerSpawn() end
	
end
data = data or " no data "
print(tostring(self) .. " with Input : " .. tostring(name) .. " : " .. tostring(activator)  .. " : " .. tostring(caller) .. " : " .. tostring(data))

end

function ENT:KeyValue( key, value )

print(tostring(self) .. " with -- Key : " .. key .. "     Value : " .. value)

if key == "model" then self.model = value end
if key == "spawn_rate" then self.spawn_rate = tonumber(value) end
if key == "origin" then self.origin = value end
if key == "enabled" then self.enabled = tobool(value) end 
if key == "spawnflags" then self.spawnflags = tonumber(value) end 
if key == "spawn_max" then self.spawn_max = tonumber(value) end
if key == "delay_modify" then self.delay_modify = tonumber(value) end
if key == "npc_class" then self.npc_class = value end
if key == "npc_weapon" then self.npc_weapon = value end
if key == "npc_drop" then self.npc_drop = tonumber(value) end
if key == "npc_droprate" then self.npc_droprate = tonumber(value) end
if key == "level_modify" then self.level_modify = tonumber(value) end
if key == "level_rate" then self.level_rate = tonumber(value) end
if key == "level_min" then self.level_min = tonumber(value) end
if key == "level_max" then self.level_max = tonumber(value) end
if key == "level_rnd" then self.level_rnd = tobool(value) end
if key == "min_rate" then self.min_rate = tonumber(value) end
if key == "max_rate" then self.max_rate = tonumber(value) end
if key == "maxinplay" then self.maxinplay = tonumber(value) end
if key == "npc_spawnflags" then self.npc_spawnflags = tonumber(value) end
if key == "baselevel" then self.baselevel = tonumber(value) end
if key == "squadname" then self.squadname = tostring(value) end
if key == "squad" then self.squad = tonumber(value) end

if key == "PreSpawn" then self:StoreOutput(key, value) end
if key == "OnSpawn" then self:StoreOutput(key, value) end
if key == "PostSpawn" then self:StoreOutput(key, value) end
if key == "Reset" then self:StoreOutput(key, value) end
if key == "LimitHit" then self:StoreOutput(key, value) end
if key == "OnUser1" then self:StoreOutput(key, value) end
if key == "OnUser2" then self:StoreOutput(key, value) end
if key == "OnUser3" then self:StoreOutput(key, value) end
if key == "OnUser4" then self:StoreOutput(key, value) end

--[[
output PreSpawn(void) : "Fires just before and NPC Spawns"
output OnSpawn(void) : "Fires as a NPC Spawns"
output PostSpawn(void) : "Fires after a NPC Spawns"
output Reset(void) : "Fires When Entity is Reset"
output LimitHit(void) : "Fires When spawn_max is hit"
--]]

end

function ENT:OnRemove()
end

function ENT:TriggerSpawn()

	print("TriggerSpawn-------------------------------------------area_npcspawn-----------------------------------")
	local newNPCClass = ""
	local idx = 0
	local rndIdx = 0
	local npcWeapon = ""
	
	self:TriggerOutput("PreSpawn", self)
	
	-- Pick a good spot inside the volume on the ground to spawn
	local SpawnPos = self:FindSpawn()
	print("Got Spawn loc" .. tostring(SpawnPos))
	--If we can't find a spot then  skip and spawn next time
	if !(SpawnPos == nil) then 
			
		self.SpawnTotal = self.SpawnTotal + 1
		
		if self.npc_class == "random" then
			newNPCClass = GetRandomValue(self.NPCRndTypes)
			print("Random: " .. newNPCClass)
		else
			newNPCClass = self.npc_class
			print("Set: " .. newNPCClass)
		end
		--print("Random Test: " .. GetRandomValue(self.NPCRndTypes))
		print("NPCClass: " .. newNPCClass)
		idx = table.insert( self.entsTable, ents.Create( newNPCClass ))
		print("NPC Created in Table")
		local newNPC = self.entsTable[idx]
		print("npc ref var set")
		newNPC.managed = true
		newNPC.squadset = false
		newNPC:SetPos( SpawnPos )
		print("npc pos set")
		if self.npc_weapon == "random" then
			if newNPCClass == "npc_combine_s" then
				rndIdx, npcWeapon = GetRandomKeyValue(self.NPCWeaponsCombine)
			elseif newNPCClass == "npc_citizen" then
				rndIdx, npcWeapon = GetRandomKeyValue(self.NPCWeaponsRebel)
			elseif newNPCClass == "npc_metropolice" then
				rndIdx, npcWeapon = GetRandomKeyValue(self.NPCWeaponsPolice)
			else
				rndIdx, npcWeapon = GetRandomKeyValue(self.NPCWeapons)
			end
		else
			npcWeapon = self.npc_weapon
		end
		newNPC:SetKeyValue("additionalequipment", npcWeapon)
		newNPC:SetKeyValue("spawnflags", self.npc_spawnflags)
		print("NPC PreSpawn")
		newNPC:Spawn()
		newNPC:CapabilitiesAdd( CAP_SQUAD | CAP_NO_HIT_SQUADMATES | CAP_AIM_GUN | CAP_DUCK | CAP_USE_WEAPONS)		
		print("NPC PostSpawn")
		self:TriggerOutput("OnSpawn", self)			
		

		--Drop Code
		--[[ Values for drops
				0 : "None"
				1 : "Health"
				2 : "Ammo"
				3 : "Weapons"
				4 : "Health & Ammo"
				5 : "Health & Weapons"
				6 : "Ammo & Weapons"
		--]]
		
		print("Drop Code : " .. self.npc_drop)
		print("Current Drop Count : " .. self.dropCount)
		print("Current Spawn Total : " .. self.SpawnTotal)
		print("Current Drop rate : " .. (self.dropCount / self.SpawnTotal))
		print("Drop Rate : " .. self.npc_droprate)
		
		if ((self.dropCount / self.SpawnTotal) < self.npc_droprate) and (self.npc_drop > 0 ) then
		
			if self.npc_drop == 1 then
				--Health drop
				newNPC:CallOnRemove(tostring(newNPC) .. tostring(math.random(4, 20)) .. "health", RndItemDrop)
			elseif self.npc_drop == 2 then
				-- Ammo Drop
				newNPC:CallOnRemove(tostring(newNPC) .. tostring(math.random(4, 20)) .. "ammo", RndAmmoDrop)
			elseif self.npc_drop == 3 then
				--Weapon Drop
				newNPC:CallOnRemove(tostring(newNPC) .. tostring(math.random(4, 20)) .. "weapon", RndWeaponDrop)
			elseif self.npc_drop == 4 then
				-- Health and Ammo Drop
				newNPC:CallOnRemove(tostring(newNPC) .. tostring(math.random(4, 20)) .. "health", RndItemDrop)
				newNPC:CallOnRemove(tostring(newNPC) .. tostring(math.random(4, 20)) .. "ammo", RndAmmoDrop)
			elseif self.npc_drop == 5 then
				-- Health and Weapon Drop
				newNPC:CallOnRemove(tostring(newNPC) .. tostring(math.random(4, 20)) .. "health", RndItemDrop)
				newNPC:CallOnRemove(tostring(newNPC) .. tostring(math.random(4, 20)) .. "weapon", RndWeaponDrop)
			elseif self.npc_drop == 6 then
				-- Ammo and Weapon Drop
				newNPC:CallOnRemove(tostring(newNPC) .. tostring(math.random(4, 20)) .. "ammo", RndAmmoDrop)
				newNPC:CallOnRemove(tostring(newNPC) .. tostring(math.random(4, 20)) .. "weapon", RndWeaponDrop)
			end
			self.dropCount = self.dropCount + 1
		end
		
		--Set Squad with all other  spawned monsters
		newNPC:SetKeyValue("squadname", "eonSquad")
		newNPC.squadset = true
		newNPC:SetName("enemy")
		--And makesure we're friendly with em.
		self:SetRelations( newNPC )
		--Retrive Health from table based off baselevel
		newNPC:SetMaxHealth(newNPC:GetMaxHealth() + LevelTable[self.baselevel].healthTotal)
		newNPC:SetHealth(newNPC:GetMaxHealth() + LevelTable[self.baselevel].healthTotal)
		newNPC.Level = self.baselevel
		if self.baselevel > 1 then
			newNPC.XP = LevelTable[self.baselevel - 1].nextXP
		else
			newNPC.XP = 0
		end
		newNPC.NextLevel = LevelTable[self.baselevel].nextXP
		newNPC.TotalKillCount = 0 
		newNPC.KillCount = 0
		newNPC.Stats = false
		print("Done init on npc")
		
		newNPC:Activate()
		print("NPC Activated!")
		--Spawn Limit Code
		if (self.SpawnTotal >= self.spawn_max) and (self.spawn_max > 0) then 
			self.enabled = false 
			self:TriggerOutput("LimitHit", self)
		end				
		
		--Rate Control Code
		self.spawn_rate = self.spawn_rate + self.delay_modify
		if self.spawn_rate < self.min_rate then self.spawn_rate = self.min_rate end
		if self.spawn_rate > self.max_rate then self.spawn_rate = self.max_rate end
	
		--Level Control Code
		if self.level_modify > 0 then
			self.lvlSpawnCount = self.lvlSpawnCount + 1
			
			if self.lvlSpawnCount > self.level_rate then
				self.baselevel = self.baselevel + self.level_modify
				self.lvlSpawnCount = 0
			end
			
			if self.baselevel < self.level_min then self.baselevel = self.level_min end
			if self.baselevel > self.level_max then self.baselevel = self.level_max end
			
		end
		if self.level_rnd then
			self.baselevel = math.random(self.level_min, self.level_max)
		end
		
		--if math.random(1,100) > 60 then timer.Simple(0.2, newNPC.SetSchedule, self, SCHED_RUN_RANDOM) else timer.Simple(0.2, newNPC.SetSchedule, self, SCHED_IDLE_WANDER) end
		if math.random(1,100) > 60 then timer.Simple(0.5, npcWander, newNPC) end
		
		
		self:TriggerOutput("PostSpawn", newNPC)
		
	end

end

function npcWander( npc )
npc:SetSchedule(SCHED_IDLE_WANDER)
end

function ENT:Think()

-- Table Cleanup
if table.Count(self.entsTable) > 0 then
	for i, v in pairs(self.entsTable) do
		if !(v:IsValid()) or (v == NULL) then  table.remove(self.entsTable, i) end
	end
end

if !self.enabled then return end

-- Spawn Stuff
if CurTime() > self.NextSpawn then
		
		if table.Count(self.entsTable) < self.maxinplay then 
			self:TriggerSpawn()
		end
		
		self.NextSpawn = CurTime() + self.spawn_rate
end

end

function ENT:FindSpawn()

if  self.size == Vector(0,0,0) then
	local X = (self:OBBMaxs().x - self:OBBMins().x)
	local Y = (self:OBBMaxs().y - self:OBBMins().y)
	local Z = (self:OBBMaxs().z - self:OBBMins().z)
	self.size = Vector(X, Y, Z)
end

local hX = self.size.x / 2
local hY = self.size.y / 2
local hZ = self.size.z / 2

local oX = self:LocalToWorld(self:OBBCenter()).x
local oY = self:LocalToWorld(self:OBBCenter()).y
local oZ = self:LocalToWorld(self:OBBCenter()).z

local rX = math.Rand(oX - hX, oX + hX)
local rY = math.Rand(oY - hY, oY + hY)
local rZ = oZ + hZ

local SpawnTrace = {}
SpawnTrace.start = Vector(rX, rY, rZ)
SpawnTrace.endpos = Vector(rX, rY, rZ - self.size.z)
		
local SpawnTraceRes = util.TraceLine(SpawnTrace)

if !SpawnTraceRes.Hit or
   SpawnTraceRes.StartSolid then
	return nil
end
	
fX = SpawnTraceRes.HitPos.x
fY = SpawnTraceRes.HitPos.y
fZ = SpawnTraceRes.HitPos.z

return Vector(fX, fY, (fZ+2))

end

[/lua]

and here’s the fdg file, it basically just provides hammer with the keyvalues and classname, there’s more options like the sphere’s for radius things and such. Just add this in the hammer options with the other fdg’s

area_npcspawn.fdg




// NPC Spawn Area

@SolidClass base(Targetname) = area_npcspawn : "NPC Spawn Volume"
[
	spawnflags(flags) =
	[
		1 : "Only Spawn on Input" : 0   // 0 means the flag is not-ticked by default
	]

	//name(string) : "Short description" : "Default" : "Long description"
	
	//name(string) : "Name" : : "Name of Spawn Area"
	spawn_rate(float) : "Spawn Rate" : 4 : "Rate at which NPCs are Spawned"
	npc_class(string) : "NPC Class" : : "What class of NPC to spawn"
	delay_modify(float) : "Delay Modifier" : 0 : "Amount to modify spawn rate by each time an NPC is spawned."
	spawn_max(integer) : "Spawn Max" : 0 : "Maximum amount of NPCs to spawn before stopping."

	// Outputs
	// output OnSomethingHappened(void) : "Fires when something happens"
	
	output PreSpawn(void) : "Fires just before and NPC Spawns"
	output OnSpawn(void) : "Fires as a NPC Spawns"
	output PostSpawn(void) : "Fires after a NPC Spawns"
	output Reset(void) : "Fires When Entity is Reset"

	// Inputs
	//input DoSomething(void) : "Do something"
	input Reset(void) : "Reset Entity to Orignial State"
	input Enable(void) : "Enables the Entity"
	input Disable(void) : "Disables the entity"
	input Spawn(void) : "Forces spawn of an NPC"
	
]



Basically what this does is send a trace from the top of the volume down in a random location within the volume, and if it hits the world it will spawn an npc. So if you had a bunch of displaced hills and placed this as a cap over the hills but not to the bottom, the npcs would spawn just on the tops of the hills.

Hope this helps.

Well I pretty much already have my system to spawn stuff, what I can’t get though is an accurate OBB… How did you get yours to accurately calculate the size of your trigger entity? Maybe it has something to do with the .fdg?

The info comes from the model, which is passed in the ENT:KeyValue() hook.

[lua]if key == “model” then self.model = value end[/lua]

it’s very odd it passes a model like *1 or *9, etc. and only works during map load, then you can just use normal methods on it to get the Bounding Box info like in the ENT:FindSpawn Function with the OBBMins() and OBBMaxs() but you need to convert them to world coords.

Mmmmm, alright, but then do I have to set the model to something or it’ll automaticaly set itself to my brush’s model?

in the initialize function.
they way it spawns is the KeyValue function then Initialize,
so in KeyValue get the model, and in Initialize set it with self:SetModel(self.model)

What I meant is in Hammer, because my entity is a custom brush, there’s no actual model.

(I tried but model return nil so I definatly need to set it to something in hammer.)

Ok, I see, that’s the bottom part of the post, you need to make an fdg file for the entity you made, and the just tie it whatever brush you want in hammer. You’ll need to add it in the Options menu of hammer click on add under Game Data Files:

Alright thanks, I’ll try to get it working now, I’ll probably have to use a **@SolidClass **because the entity is tied to the brush.

Thanks again.