Team Fortress 2

Team Fortress 2

Not enough ratings
MvM - Sigsegv Popfile Mod - PointTemplates
By Mince
A tutorial on using PointTemplates in Sigsegv's Popfile Mod, which is a plugin created by Sigsegv and extended by Rafradek that adds a plethora of new functionalities and possibilities for mission making.

This is a tutorial on a rather small subsection of a larger plugin, whose syntax can be viewed in
this demo file[drive.google.com]. Experience with regular mission creation and the mod's custom syntax is assumed.
4
2
   
Award
Favorite
Favorited
Unfavorite
Source Entities - The Basics
Before we can start with PointTemplates, you need to have a basic understanding of Source mapping and its entities if you want to create anything of any complexity. If you're already a mapper and are familiar with the I/O system and Source entities, you can safely skip the Source Entities sections. The only thing this section assumes is that you're already familiar with the basics of Hammer, if not, you can find plenty of tutorials about it's usage on YouTube.

Maps in Source are comprised of a lot of things, most important in the context of PointTemplates are entities. These are what you're going to be modifying and creating when you use the features of the mod that work directly with the map.

Inputs and Outputs

Every entity has a set of inputs and outputs that allow it to communicate with other entities in the map. The inputs of an entity are what allow other entities to tell it to do certain things, while it's outputs let it tell other entities to do things based on certain events.

For example, lets look at the math_counter entity, which holds a number that can be modified in different ways. One of it's inputs is called Add, which increments it's internal counter by the specified value. Let's say I have some other piece of logic that wants to add 5 to the counter, it would simply send the Add input to the math_counter with a parameter of 5.

In Hammer that would look something like this:

Don't worry about OnStartTouch, it's just the output of another entity which allows it to communicate with our math_counter. As I said earlier outputs allow entities to talk to each other based on some event. In the case of math_counter, it has a few outputs, one of them is called OutValue, which fires whenever the counter value changes, so we could use that if we wanted to tell some other entity to do something in that situation.

What inputs and outputs an entity has depends on the class of the entity, so don't expect them all to have the same set.

To view most of what you can do with an entity, you can visit the Valve Developer Community. It has articles documenting information about most entities that exist in Source, along with other topics. You can search for entities in the search bar at the top right, try searching for math_counter and browse through it's inputs and outputs!

An alternative way to view information about an entity is in the Help dialog box in Hammer, which is located at the top right of the object properties window (the one you get to by double clicking an entity or pressing ALT + Enter with it selected.)

Occasionally one source of info will have more information than the other, so It's a good idea to check both if you really need to know what an entity can do.

KeyValues

Along with a set of inputs and outputs, entities have keyvalues, which are information about each entity that modify things about it, such as it's targetname, or it's color. These are viewable in the same locations I listed in the previous section.

Keyvalues have two different names in Hammer, one which is displayed by default in Hammer, and the keyvalue's real name. The default one is simply there to look prettier, as the real keyvalue names can't contain things like spaces. You can view the real keyvalue names in Hammer by clicking on the SmartEdit button in the top right of Object Properties. They're also typically listed in the VDC website in parenthesis next to the display name. (But they're not always listed)

Knowing the real name of keyvalues is important for us because we're going to be using them when working with entities in the popfile, rather than the display name.

All keyvalues have a non display name, if neither Hammer nor the VDC list it, there is one place that will always without fail list the real name of the keyvalue, which is in the .vmf file.

To view the real name of a keyvalue, modify a keyvalue from the default in Hammer to ensure that it appears in the .vmf, then save the map. You can find your .vmf files in Team Fortress 2/sourcesdk_content/tf/mapsrc. Open the file with a text editor, and search for the entity you modified (typically by it's targetname if you gave it one, or alternatively by it's classname).

You'll see blocks like this:
entity { "id" "4334" "classname" "prop_dynamic" "angles" "0 0 0" "body" "0" "DisableBoneFollowers" "0" "disablereceiveshadows" "0" "disableshadows" "0" "effects" "0" "ExplodeDamage" "0" "ExplodeRadius" "0" "health" "0" "MaxAnimTime" "10" "maxdxlevel" "0" "MinAnimTime" "5" "mindxlevel" "0" "minhealthdmg" "0" "model" "models/player/items/all_class/executionerhood_spy.mdl" "modelscale" "1.5" "PerformanceMode" "0" "physdamagescale" "1.0" "pressuredelay" "0" "RandomAnimation" "0" "renderamt" "255" "rendercolor" "255 255 255" "renderfx" "0" "rendermode" "0" "SetBodyGroup" "0" "shadowcastdist" "0" "skin" "0" "spawnflags" "0" "texframeindex" "0" "origin" "-592 156 40" editor { "color" "220 30 220" "visgroupshown" "1" "visgroupautoshown" "1" "logicalpos" "[0 1000]" } }

Conveniently, copying keyvalues from the .vmf into PointTemplate entity blocks is the easiest and quickest way to create templates, so you should get used to browsing through .vmf files and their syntax. We'll go over all that later.

Entity Types

There are 2 types of entities in Source, brush entities and point entities.

Point entities occupy a specific point in the map, and encompass things like logic entities (invisible entities that only serve to fire outputs / accept inputs), props, etc. It's easier to think of them as not brush entities.

Brush entities occupy volumes of space in the map, and depending on the entity can either have a visible texture, or invisible tool texture applied to it. Do note however any brush entities created through PointTemplates may only have the tools/toolstrigger texture applied to them.

It's worth knowing the difference between these as you need to include a few extra keyvalues for brush entities to function through PointTemplates.

Along with brush entities and point entities, there's another category of entities that is worth knowing about, internal entities.

Internal entities are processed by VBSP (the tool that turns a .vmf into a .bsp) and either deleted or merged into another entity. Because of this they don't exist while the map is running, therefore it's meaningless to create them through PointTemplates, as the map has already been compiled and the game wouldn't know what to do with the raw entity.

Here is the full list of internal entities on the VDC:
https://developer.valvesoftware.com/wiki/Internal_entity

Special Targetnames

Aside from targeting entities by their targetname keyvalue, there are a few other ways to send inputs to them. The most important ones for our purposes are:
  • Classname - Target all instances of a classname.
  • !activator - Target the entity that began the current I/O chain.
  • !self - Target the entity that is sending the current output.
  • !parent - Target the parent entity of the entity sending the current output. (Note that this is only used in Sigmod, don't use it in Hammer.)

Here's more information about special targetnames:
https://developer.valvesoftware.com/wiki/Targetname
Source Entities - Outputs & AddOutput
Output Syntax

The .vmf syntax for outputs can seem scary and unintelligible at first, but it's just a text version of what's in Hammer, and it's not too difficult to understand if explained properly.

I'm going to be going over the syntax relating to how it looks in the .vmf, because that's the format that you'll be using when we move on to PointTemplates. Both keyvalues and outputs follow a "key" "value" format, with outputs the key is the output name, and the value is the rest of the output information.

KeyValue syntax : "<key>" "<value>"
Output syntax : "<output>" "<target>,<input>,<parameter>,<delay>,<max times to fire>"

Example:
"classname" "logic_relay" "spawnflags" "0" "targetname" "dispensertank_create_relay" connections { "OnTrigger" "dispensertank_healarea,SetParent,!activator,0,-1" "OnTrigger" "dispensertank_cartdispenser,SetParent,!activator,0,-1" "OnTrigger" "!self,Kill,,0.05,-1" "OnTrigger" "dispensertank_model,SetParent,!activator,0,-1" "OnTrigger" "dispensertank_model,Disable,,0,-1" "OnTrigger" "dispensertank_model,Skin,1,0,-1" "OnTrigger" "dispensertank_cartdispenser,SetDispenserLevel,3,0,-1" }

The keyvalues at the top are straightforward, classname, spawnflags, and targetname are the keys, and they have their corresponding values.

With the outputs below, lets focus on the first one. We can see that OnTrigger is the key, and inside the value, it follows the output syntax as follows:
  • dispensertank_healarea is the target entity.
  • SetParent is the input.
  • !activator is the parameter.
  • 0 is the delay.
  • -1 is the max times to fire (infinite).

Each subvalue in the value for outputs is separated by a comma as you can see in the example above. If one of the subvalues isn't required (very typically the parameter), add another comma to denote that there's nothing for the parameter. You can view an example of this in the third output above. Between those commas is where parameter would be.

AddOutput

Now that you know how outputs are formatted, we can move on to AddOutput.

AddOutput is a special input available to every single entity which can change the keyvalues of that entity after it has been created.

The parameter for this input is simply the key value syntax we went over earlier, except with a few modifications.

Example:
"targetname" "panictank_init_relay" "OnTrigger" "!parent,AddOutput,OnHealthBelow90Percent !self:SetSpeed:85:0:-1,0,-1" "OnTrigger" "!parent,AddOutput,OnHealthBelow70Percent !self:SetSpeed:100:0:-1,0,-1" "OnTrigger" "!parent,AddOutput,OnHealthBelow50Percent !self:SetSpeed:120:0:-1,0,-1" "OnTrigger" "!parent,AddOutput,OnHealthBelow30Percent !self:SetSpeed:150:0:-1,0,-1" "OnTrigger" "!parent,AddOutput,OnHealthBelow20Percent !self:SetSpeed:180:0:-1,0,-1" "OnTrigger" "!parent,AddOutput,OnHealthBelow10Percent !self:SetSpeed:220:0:-1,0,-1"

Don't panic! (heh) The syntax may seem scary again, but lets break it down and analyze it based on our established output syntax, here it is for reference again:

Output syntax : "<output>" "<target>,<input>,<parameter>,<delay>,<max times to fire>"

Looking at the first output, we can see it starts out alright, the key is the OnTrigger output. Going into the value, the target is !parent, after that we can see the input, which is AddOutput, so far so good.

Then we reach the scary part, however, it's fairly easy to explain. As I said earlier the parameter of AddOutput is the syntax for adding a keyvalue, except they aren't enclosed in quotes, and the subvalues are separated by colons instead of commas. So we just have to go through the output syntax again!

First we see the key for the output, OnHealthBelow90Percent, then a space and then the value starts. !self is the target, SetSpeed is the input, 85 is the parameter, 0 is the delay, and -1 is the max times to fire. That ends the keyvalue inside parameter! We then continue on with our original keyvalue, ending it with a 0 delay and -1 max times to fire.

The reason why the subvalues inside parameter are delimited by colons is because if it used commas, that's what the outer subvalues use! The parser wouldn't know which was which, and it would be really confusing to look at, so think of it as a way to differentiating the two.

The syntax for regular keyvalues is the same, <key> <value>, but of course it's a lot easier to read than output keyvalues:
"OnTrigger" "!activator,AddOutput,rendermode 1,0,-1"

Keep in mind that things like this are also valid:
"OnTrigger" "!activator,AddOutput,rendercolor 255 0 0,0,-1"

The value for rendercolor is 255 0 0, spaces inside the value are allowed in this context (since you can't use quotations in the value).

It's worth noting that the comma then colon syntax is for outputs in the .vmf and in entity keyvalues for PointTemplates, however Hammer and output blocks in Sigmod use the colon then comma syntax, here's an example:

// On a tank_boss, when it reaches 50% health any full healthkits picked up afterwards will color players green OnSpawnOutput { Target "!activator" Action AddOutput Param "OnHealthBelow50Percent item_healthkit_full:AddOutput:OnPlayerTouch !activator,AddOutput,rendercolor 0 255 0,0,-1:0:-1" Delay 0 }

Yes this is confusing and inconsistent, but it's how it is.

One final thing for this section, special targetnames change who they reference when using AddOutput. When you add an output to an entity, you need to consider what the special targetnames would reference from that entity.

Using the example below:
"OnTrigger" "!parent,AddOutput,OnHealthBelow90Percent !self:SetSpeed:85:0:-1,0,-1"

!parent refers to the parent of the entity firing that OnTrigger output, however, !self does not refer to that child entity that fired OnTrigger. We created that output inside of the parent entity, therefore !self would refer to that parent entity, if you used !parent instead of !self in that position that would refer to the parent's parent.
Source Entities - Misc
Entity Rotation
Knowing how to choose angle values for rotating entities is a useful skill if you plan on creating things like props through PointTemplates, you could always guess the values, but that's time wasted you could be using doing other things.

Changing the rotation of entities is done through the angles keyvalue, which takes a value consisting of 3 floats corresponding to pitch, yaw, and roll.

Example :
"angles" "0 180 0" // Turn around

  • Pitch - Rotation along the Y axis
  • Yaw - Rotation along the Z axis
  • Roll - Rotation along the X axis

While this is technically correct, I don't think it's very useful in remembering how to actually rotate objects. I find it much easier to have an idea of how an object rotates in relation to these 3 terms.

Here is an easy way of remembering which way the different terms rotate by moving your head:

Pitch is how you rotate your head when you tilt it up and down, as if you're nodding to a question.
Yaw is how you rotate your head when you move it side to side, as if saying no to a question.
Roll is how you tilt your head sideways toward your shoulder, as if you don't understand a question.

Entities start at angle 0 0 0, and most Source models reflect this by pointing in the direction of the positive X axis (to the right in the top view in Hammer). We can see this in action with this tank model:
-------------------------------------------------------------------------------------------------------

If we wanted to tilt the tank downwards by 45 degrees, we would set the pitch to 45. Which would be an angles keyvalue of "angles" "45 0 0".
-------------------------------------------------------------------------------------------------------

If we wanted to make it turn around from the current direction it's facing, we would set the yaw to 180. ("angles" "0 180 0")
-------------------------------------------------------------------------------------------------------

And finally, if we wanted to make it tilt sideways, we could change the roll. ("angles" "0 0 45")
-------------------------------------------------------------------------------------------------------

The direction in which entities rotate positively in Source is a bit odd. For pitch and roll it's clockwise, however for yaw its counter-clockwise. To remember this easier, let's go back to our head technique (no pun intended).

Pitching your head downwards is a rotation of a positive value (e.g. 45), while yawing to the left is positive, and rolling to the right is positive. If you're confused by this just go in Hammer and see which rotations positive values give you versus negative ones. You can remember it simply as pitch yaw roll - down left right.

One last bit of information, generally when you rotate an object, the axis of rotation go along with it, so to find out how an object would rotate while rotated, you would pretend you're facing that direction. However, this does not apply to yaw. Try rotating a model to look straight down (90 0 0), then apply a yaw rotation to it, it won't rotate how you expect (it acts like roll).

This is because it adheres to the global z direction, not the object's. The reason for this is if it did rotate with the object, if there were any rotations for pitch or roll, it would be impossible for the object to face different directions in the world. If this confuses you, an easy way to get around thinking about this is to simply do your yaw rotations first.

For some exercises in getting it right visually in your head, try thinking of an object in a random rotation, then try to replicate it with values in Hammer. It shouldn't take too long for you to get a feel on how things rotate.
PointTemplates - The Basics
PointTemplates is a block used to define point_template entities which can be spawned throughout the map.

The point_template entity groups a set of entities into a template, allowing you to instantiate (make new copies of) that group in the map as many times as you want.

Each template block inside of PointTemplates can contain up to 16 entity blocks, named by their classname, each of which contains a list of keyvalues required for them to function.

Here's an example of a PointTemplates block and it's sub blocks:
PointTemplates [$SIGSEGV] { ResupplyCabinet { prop_dynamic { "targetname" "locker_model" "model" "models/props_gameplay/resupply_locker.mdl" "disableshadows" "1" "solid" "6" "origin" "16 0 0" "angles" "0 0 0" } func_regenerate { "associatedmodel" "locker_model" "filtername" "filter_red" "TeamNum" "2" "spawnflags" "1" "mins" "0 -64 0" "maxs" "64 64 128" "OnStartTouch" "locker_model,SetAnimation,open,0,-1" "OnEndTouchAll" "locker_model,SetAnimation,close,0,-1" } } SpawnPoints { NoFixup 1 info_player_teamspawn { "targetname" "spawnbot_pumpkin" "StartDisabled" "1" "spawnflags" "511" "TeamNum" "2" "origin" "4200 4200 600" } } SoldierMask { prop_dynamic { "modelscale" "1.5" "rendercolor" "63 254 36" "DisableBoneFollowers" "1" "disableshadows" "1" "model" "models/player/items/all_class/executionerhood_spy.mdl" "solid" "0" "spawnflags" "0" "StartDisabled" "0" } } }

By default, point_templates add a global template instance ID to the end of the targetname of every entity within the template. The format for this instance ID is <targetname>&<4 digit ID>.
For example, if I had an entity called my_entity, the new targetname would look like my_entity&0008.

This name fixup is also applied to the Outputs of the entities inside the template. For example, if I have 2 entities inside a template, a relay and a prop, if the relay uses the targetname of the prop in one of it's outputs like so:
PointTemplates [$SIGSEGV] { MyTemplate { logic_relay { "targetname" "template_relay" "OnTrigger" "template_prop,DisableCollision,,0,-1" } prop_dynamic { "targetname" "template_prop" "model" "models/props_gameplay/resupply_locker.mdl" "disableshadows" "1" "solid" "6" "origin" "16 0 0" "angles" "0 0 0" } } }

The template_prop inside of the value for the OnTrigger output would be fixed up to be an instance ID, something like template_prop&0021.

This name fixup doesn't really matter inside the template, since targetname references are changed to the proper instance id automatically for you. However, if you needed to trigger that relay outside of the template, this is where you run into problems. You won't be able to target it using the targetname you gave it because it's been changed.

If you don't want this name fixup to happen, add NoFixup 1 at the top of the template, like this:
PointTemplates [$SIGSEGV] { MyTemplate { NoFixup 1 logic_relay { "targetname" "template_relay" "OnTrigger" "template_prop,DisableCollision,,0,-1" } prop_dynamic { "targetname" "template_prop" "model" "models/props_gameplay/resupply_locker.mdl" "disableshadows" "1" "solid" "6" "origin" "16 0 0" "angles" "0 0 0" } } }

Getting The KeyValues

It isn't necessary that you memorize or manually fill in every single keyvalue for every single entity. Copying keyvalues straight from a .vmf and pasting them into an entity block works perfectly fine, though it might include some unecessary / redundant keyvalues like classname and id. You only need the keyvalues directly inside the entity and any outputs in connections, dont include anything else like the solid or editor blocks.

-------------------------------------------------------------------------------------------------------
Here you would only copy keyvalues from filtername to wait, along with the onstarttouch outputs. Everything else you would ignore.

Entity Types

Aside from internal entities which I mentioned in the Source Entities section, you can use just about any entity in templates, including brush entities!

The only thing extra that you need to include for brush entities to work are two keys called "mins" and "maxs", which take "x y z" values relative to the origin of the entity. These are two opposite points of a brush, the rest of the points are inferred to define the edges of the solid. Here's an example:
-------------------------------------------------------------------------------------------------------

The brush doesn't have to be centered around the origin, something like this is also perfectly valid:
-------------------------------------------------------------------------------------------------------

All that matters is you define the two points based on the origin. If you cant quite visualize it in your head when you go to create your own, you can open up Hammer and place the brush how you want, using mins maxs values based on any 2 opposite points of the brush.

The only restrictions concerning brush entities are:
  • You can't rotate them.
  • You can't give them a custom texture, they will always be invisible with tools/toolstrigger.
  • They will always be a rectangular prism / cube, you cannot make complex shapes such as cylinders.

If you're able to have your brush centered around it's origin, you should, as there's a very easy way to find out the mins maxs values in Hammer for a centered brush rather than measuring manually.

Select your brush in Hammer and look at the bottom right of the window, you should see a group of numbers to the left of the grid snap that looks like this:
256w 256l 160h @(0 800 80)

The 3 numbers to the left are the dimensions, and the 3 numbers to the right make up the origin. To find out the values for mins and maxs, halve the dimensions, give mins the negative of those halves, and give maxs those halves directly. So using the above example, these would be the keyvalues:
"mins" "-128 -128 -80" "maxs" "128 128 80"

Finally, here's an example of a brush entity in a template:
trigger_add_tf_player_condition { "targetname" "dutchman_curse_trigger" "condition" "86" "duration" "5" "filtername" "filter_red" "spawnflags" "1" "StartDisabled" "1" "mins" "-1024 -1024 -1024" "maxs" "1024 1024 1024" }
PointTemplates - SpawnTemplate
Having templates defined in PointTemplates is all well and good, but nothing is going to happen until you actually spawn them in the map. This is done through the SpawnTemplate keyvalue.

SpawnTemplate has 2 forms, a block and keyvalue form:
SpawnTemplate "MyTemplate" [$SIGSEGV] // Assumes origin and angles "0 0 0"

SpawnTemplate [$SIGSEGV] { Name "MyTemplate" Origin "-1000 200.50 384" Angles "90 0 90" }

Along with different forms, there are different contexts you can use this keyvalue in to have different meanings. These places are:

WaveSchedule
Using SpawnTemplate directly inside of WaveSchedule will spawn the template when the mission is loaded.

Wave
Using SpawnTemplate directly inside of a Wave will spawn the template when the wave starts.

You can specify a Delay keyvalue in the block form to define a delay before spawning after the wave has started.

Tank
Using SpawnTemplate directly inside of a Tank will spawn the template at the tank's origin when it spawns, and parent all entities in the template to the tank, so that they will move with it if able to.

Origin and Angles are now relative to the tank's origin and angles, rather than the world.
(ex. Origin "0 0 250" is 250 HU above the tank's origin.)

TFBot
Using SpawnTemplate directly inside of a TFBot (or bot template) will spawn the template at the bot's origin when it spawns, and parent all entities in the template to the bot, so that they will move with it if able to.

Origin and Angles are now relative to the bot's origin and angles, rather than the world.
(ex. Origin "0 0 250" is 250 HU above the bot's origin.)

You can specify a Bone keyvalue in the block form to define a bone to spawn at rather than the bot's origin. The Origin and Angles keyvalues will be relative to this location rather than the bot's origin if Bone is specified.
(ex. Bone "bip_head")


Env_entity_maker

Aside from SpawnTemplate, there's one other way to spawn instances of point_templates, which is the entity env_entity_maker. This is the entity maps use to spawn point_templates in different places.

In the context of PointTemplates, it's useful if we need to spawn a template based on the I/O of other entities, rather than spawning a template during the situations described above for SpawnTemplate. For example, if I wanted to spawn a template every time a player enters a trigger_multiple, this would be a use case for env_entity_maker.

You can include more, but for most use cases you only need 2 keyvalues for an env_entity_maker, targetname and EntityTemplate:
env_entity_maker { "targetname" "teleporter_minify_maker" "EntityTemplate" "teleporter_minify_template" }

Keep in mind that the block name you give templates is that point_template's targetname, and as such it's very much possible to use it as the EntityTemplate for an env_entity_maker.

You use the ForceSpawnAtEntityOrigin input of env_entity_maker to spawn the template at a certain entity.
Or you could use ForceSpawn, which would spawn the template at the entity maker's origin and angles, but you would need to make sure the entity maker was placed in the map where you wanted it.

Here's a full example of env_entity_maker in use:
PointTemplates [$SIGSEGV] { SomeOtherTemplate { trigger_multiple { "targetname" "spawn_spell_trigger" "filtername" "filter_red" "spawnflags" "1" "StartDisabled" "0" "wait" "0" "mins" "0 0 0" "maxs" "9216 8192 4096" "OnStartTouch" "prop_maker,ForceSpawnAtEntityOrigin,!activator,0,-1" } } MiscEntities { NoFixup 1 env_entity_maker { "targetname" "prop_maker" // point_template itself is not affected by name fixup, // only entities within it // so it's ok to use MyPropTemplate's targetname here "EntityTemplate" "MyPropTemplate" } } MyPropTemplate { tf_spell_pickup { "targetname" "spellbook" "StartDisabled" "0" "AutoMaterialize" "1" "body" "0" "disablereceiveshadows" "0" "disableshadows" "0" "effects" "0" "maxdxlevel" "0" "mindxlevel" "0" "renderamt" "255" "rendercolor" "255 255 255" "renderfx" "0" "rendermode" "10" "SetBodyGroup" "0" "shadowcastdist" "0" "skin" "0" "TeamNum" "0" "texframeindex" "0" "tier" "0" } } } SpawnTemplate "SomeOtherTemplate" [$SIGSEGV] SpawnTemplate "MiscEntities" [$SIGSEGV]

Don't forget that you can edit the origin and angles of entities individually within a template, so for example, if I wanted that tf_spell_pickup to spawn higher off the ground at the player, I could give it the keyvalue "origin" "0 0 32", which would raise it by 32 HU.

Keep in mind, the origin of entities inside templates are not relative to the template, they are still based on the world. If the point_template is located at origin "4000 -2000 512", to raise an entity inside that template by 32 HU, you would need to give it an origin of "4000 -2000 544". When that template is spawned at a location that entity will be 32 HU above where it spawned.

The original position of a template is meaningless, only the difference between it's own position and the entities within the template is relevant, as those offsets will remain when it's actually spawned. This is why it's pointless to have your templates be anywhere other than the origin
(0 0 0).

KeepAlive

Now that you know you can parent templates to entities, It's time to discuss another keyvalue available for template blocks inside of PointTemplates, which is KeepAlive. Normally, when an entity dies, any children it has are killed along with it.

What KeepAlive does is it lets parented entities inside of a template live, even after the parent has died. Once the parent is dead the entities will remain in their current position until they are killed or are given another parent. This feature might seem a bit obscure, but it's quite useful in certain situations.

Here's a short example:
PointTemplates [$SIGSEGV] { // SpawnTemplate inside boss TFBot SomeBossParent { KeepAlive 1 // Remain when boss dies trigger_add_tf_player_condition { "targetname" "swim_curse_trigger" "condition" "86" "duration" "5" "filtername" "filter_red" "spawnflags" "1" "StartDisabled" "0" "mins" "-1024 -1024 -1024" "maxs" "1024 1024 1024" } } }
PointTemplates - Template Outputs
Aside from the NoFixup and KeepAlive keyvalues, there are a few output blocks you can use inside of templates when specific events occur.

OnSpawnOutput

This output is fired each time the template is spawned.
The !activator of an OnSpawnOutput is the parent of the template (if it exists).

Here's an example:
PanicTank { OnSpawnOutput { Target panictank_init_relay Action Trigger } logic_relay { "targetname" "panictank_init_relay" "OnTrigger" "!parent,AddOutput,OnHealthBelow90Percent !self:SetSpeed:85:0:-1,0,-1" "OnTrigger" "!parent,AddOutput,OnHealthBelow70Percent !self:SetSpeed:100:0:-1,0,-1" "OnTrigger" "!parent,AddOutput,OnHealthBelow50Percent !self:SetSpeed:120:0:-1,0,-1" "OnTrigger" "!parent,AddOutput,OnHealthBelow30Percent !self:SetSpeed:150:0:-1,0,-1" "OnTrigger" "!parent,AddOutput,OnHealthBelow20Percent !self:SetSpeed:180:0:-1,0,-1" "OnTrigger" "!parent,AddOutput,OnHealthBelow10Percent !self:SetSpeed:220:0:-1,0,-1" } }

In this case it's used to modify the tank it spawned with.

OnParentKilledOutput

This output is fired when the parent of the template dies.
The !activator of an OnParentKilledOutput does not exist, so there is no point in targeting it.

Here's an example:
DemoPumpkin { KeepAlive 1 OnParentKilledOutput { Target "pumpkin_teleport_maker" Action ForceSpawnAtEntityOrigin Param "pumpkin" Delay 0.00 } OnParentKilledOutput { Target "pumpkin_death_relay" Action Trigger Delay 0.01 } OnParentKilledOutput { Target "pumpkin" Action Kill Delay 0.02 } prop_dynamic { "targetname" "pumpkin" "DisableBoneFollowers" "1" "disableshadows" "1" "model" "models/harvest/pumpkin/pumpkin_small.mdl" "solid" "0" "spawnflags" "0" "StartDisabled" "0" } }

In this case it's used to do some logic when the parent dies, then kill the template entities.

Both output blocks support the keyvalues:
  • Target
  • Action
  • Param
  • Delay
PointTemplates - ShootTemplate
The Basics
The ShootTemplate block allows you to create bots that spawn a template every time they fire, along with some extra attributes to control how that template moves with the projectile.

This may seem a bit of an obscure feature, and it is, but it is useful for creating some quite humorous bot ideas. Two notable ones that I've implemented are:
  • A bot that shoots pumpkins (grenades) which spawn a midget bot on explosion.
  • A bot that throws prop sentries which turn into real sentries after they "explode" (again, technically a grenade).

There are two ways you can use this block. The first is to spawn a template and parent the template to the projectile, the second is to spawn a template and give it appropriate keyvalues for speed and the like to let it move on it's own. You are almost always going to be using the former, as most entities aren't capable of the physics simulation required for the latter.

Please also keep in mind that you cannot use ShootTemplate with melee, you can only make it look like the bot is using melee using is_passive_weapon.

To get started, here is the full list of keyvalues available for ShootTemplate:
  • Name
    - Name of the point_template to be spawned
  • Offset
    - "x y z" position offset from the spawn point
  • Angles
    - "pitch yaw roll" angle offset from the spawn angles
  • AttachToProjectile
    - Parent the spawned entities to the fired projectile.
    - Cannot be used with OverrideShoot.
    - Does not work with bullets (they are actually rays, not projectiles).
  • OverrideShoot
    - Stop the fired projectile from spawning (also kills bullets).
    - Cannot be used with AttachToProjectile.
  • Speed
    - Speed which should be applied to spawned entities.
    - Only works with OverrideShoot.
  • Spread
    - Angular spread of spawned entities.
    - Only works with OverrideShoot.

If you want more details about the syntax (default values, etc), view the demo file.

To know what will move if you use OverrideShoot, you should think of what has physics applied to it in game. If you can think of situations where the entity is physically simulated, you can probably use it without issue (tf_projectile_jar for example). In general, projectiles, mvm cash, tf_ammo_pack, and mannpower powerups come to mind, but sadly that's probably the majority of what you can use.

If you don't plan on using any entity with physics simulations, then you most likely want to use AttachToProjectile instead. If you don't want the projectile to be shown, you can always use "custom projectile model" "models/empty.mdl" in the ItemAttributes for the weapon.

Note that the template entities that are spawned for both methods are located where projectiles normally come from players (their view height), and are in line with the player's view angles, any specification of the Offset or Angles keyvalues will be relative to those values respectively.

KeepAlive
As you know already, when you kill a parent entity, this also kills any child entities along with it. Previously I've shown that you can use KeepAlive to prevent templates from being killed after their parents are killed. Along a similar train of thought, this key can also be used with templates attached to projectiles to allow them to remain once their parent projectile is destroyed.

This simple behavior might be all that you need for what you're trying to accomplish with a bot idea, however there are some more advanced uses of the key when it comes to spawning other templates at the point of death for a projectile.

Because the entities you spawned with ShootTemplate remain with KeepAlive, you can use them as destination entities for the ForceSpawnAtEntityOrigin input of env_entity_maker. Using this, you can have any entity you want appear when the projectile dies, you can even have bots "spawn" there!

This topic might be a bit difficult to understand with just text, so I'll go over some examples step by step to explain how they work in the next section.
PointTemplates - ShootTemplate (Ex. #1)
This section contains some explanations of examples for ShootTemplate.

I'll be using the bots that I mentioned I made in the previous section as examples here, as they both make use of KeepAlive to spawn templates at the projectile death point.

The first example I'll start with is the bot that throws sentry guns, as it's a bit easier than the second example.

The first question to ask is whether to use AttachToProjectile or OverrideShoot. Obviously there are no flying sentries in the game, so AttachToProjectile it is.

ShootTemplate [$SIGSEGV] { Name EngineerSentry AttachToProjectile 1 }

Concerning the sentry attached to the projectile, for both gameplay and practical problems, the EngineerSentry template will contain a prop_dynamic with the model of a sentry rather than an actual obj_sentrygun.

Keep in mind if you don't want anything parented to your projectile (you just want to spawn something where it dies), you can simply use a prop_dynamic with no model in the template.

Here's the template with a few extra goodies I'll explain in a moment:
EngineerSentry { KeepAlive 1 OnParentKilledOutput { Target "sentry_random_level" Action PickRandom Delay 0.00 } OnParentKilledOutput { Target "engineer_sentry_prop" Action Kill Delay 0.01 } OnParentKilledOutput { Target "sentry_random_level" Action Kill Delay 0.01 } logic_case { "targetname" "sentry_random_level" "OnCase01" "sentry_lvl1_maker,ForceSpawnAtEntityOrigin,engineer_sentry_prop,0,-1" "OnCase02" "sentry_lvl1_maker,ForceSpawnAtEntityOrigin,engineer_sentry_prop,0,-1" "OnCase03" "sentry_lvl1_maker,ForceSpawnAtEntityOrigin,engineer_sentry_prop,0,-1" "OnCase04" "sentry_lvl1_maker,ForceSpawnAtEntityOrigin,engineer_sentry_prop,0,-1" "OnCase05" "sentry_lvl1_maker,ForceSpawnAtEntityOrigin,engineer_sentry_prop,0,-1" "OnCase06" "sentry_lvl1_maker,ForceSpawnAtEntityOrigin,engineer_sentry_prop,0,-1" "OnCase07" "sentry_lvl1_maker,ForceSpawnAtEntityOrigin,engineer_sentry_prop,0,-1" "OnCase08" "sentry_lvl1_maker,ForceSpawnAtEntityOrigin,engineer_sentry_prop,0,-1" "OnCase09" "sentry_lvl2_maker,ForceSpawnAtEntityOrigin,engineer_sentry_prop,0,-1" "OnCase10" "sentry_lvl2_maker,ForceSpawnAtEntityOrigin,engineer_sentry_prop,0,-1" "OnCase11" "sentry_lvl3_maker,ForceSpawnAtEntityOrigin,engineer_sentry_prop,0,-1" } prop_dynamic { "targetname" "engineer_sentry_prop" "model" "models/buildables/sentry1.mdl" "skin" "1" "spawnflags" "0" "solid" "0" "DisableBoneFollowers" "1" "disablereceiveshadows" "1" "disableshadows" "1" "origin" "0 0 0" "angles" "0 0 0" } }

At the top we can see our friend KeepAlive, any entities spawned in this template will remain when the parent dies.

At the bottom we have our prop_dynamic that will be spawned from ShootTemplate, not much special to note aside from solid is set to 0 (no collisions). Above that is a logic_case, this is being used to pick a random level sentry to create at the prop. (sentry_lvl1_maker and others are env_entity_makers, I'll show you what they look like in a bit)

When the parent projectile dies, we have a few things we need to do:
  • Tell an env_entity_maker to spawn it's associated template at one of the orphaned entities (our prop)
  • Kill the prop
  • Kill any extraneous entities from the template that are no longer needed

In the case of this example, we can see that we do all of those things from the OnParentKilled outputs. The only difference is there's an extra step for the first one.

The first output tells the logic_case to pick a random output to fire, which all tell an env_entity_maker to spawn it's template at our engineer_sentry_prop.

After this, our prop_dynamic and logic_case serve no further purpose. To prevent them from clogging up entity slots, we murder them both in cold blood in the last two outputs.

Here is one of the templates that are spawned by those env_entity_makers:
sentry_lvl1_template { KeepAlive 1 // Re-orient sentrygun (it inherits angles of prop sentrygun) OnSpawnOutput { Target "engineer_sentry" Action AddOutput Param "angles 0 0 0" Delay 0.00 } // Set builder of sentry to !activator (parent of template) (nothing) // This prevents bots from getting stuck / blocked by sentries OnSpawnOutput { Target "engineer_sentry" Action SetBuilder // The name's builder, Bob the Builder. Delay 0.00 } obj_sentrygun { "targetname" "engineer_sentry" "defaultupgrade" "0" "spawnflags" "0" "TeamNum" "3" "skin" "1" "SolidToPlayer" "0" "disablereceiveshadows" "1" "disableshadows" "1" "origin" "0 0 0" "angles" "0 0 0" } }

This is what was spawned at our prop earlier, as you can see it's just an obj_sentrygun, the outputs above are just there to fix a few issues.

However, there is one very important thing to note about this template, it also has KeepAlive. This is required for templates that are spawned at entities that still exist because of the use of KeepAlive.

The last bit of logic is just the env_entity_makers:
Misc { NoFixup 1 env_entity_maker { "targetname" "sentry_lvl1_maker" "EntityTemplate" "sentry_lvl1_template" } env_entity_maker { "targetname" "sentry_lvl2_maker" "EntityTemplate" "sentry_lvl2_template" } env_entity_maker { "targetname" "sentry_lvl3_maker" "EntityTemplate" "sentry_lvl3_template" } } } SpawnTemplate "Misc" [$SIGSEGV]

Nothing special here, the only difference between the sentry templates is the defaultupgrade key in obj_sentrygun, sadly this is the only way to do this as you can't set the level of sentryguns after they spawn through entity I/O.
PointTemplates - ShootTemplate (Ex. #2)
The second example is the bot that shoots pumpkins which spawn bots when they explode.

Please note that spawning bots at the projectile death location is a bit more complicated than the previous example of simply spawning an entity there.

Before we dive into the logic, I think it would be beneficial to explain how this works in a simplified manner. When the projectile that our bot fired explodes or dies or whatever, we have a few more things to do than normal:
  • Spawn a point_teleport at the projectile death position.
  • Enable a previously disabled spawnpoint, where our bots were waiting to spawn at.
  • Disable it again after a short amount of time (we only want one bot to spawn)
  • Kill the point_teleport
  • Kill our orphaned projectile prop (like our sentry prop in the previous example)
  • Kill anything else we don't need anymore

So based on this, aside from our ShootTemplate logic, we need a few things set up before our logic can function correctly:
  • A spawn point which starts disabled (you can create your own if you want).
  • A map wide trigger_multiple which will detect our bots when they spawn.
  • A filter to target the correct bots (In the example I use filter_tf_bot_has_tag).

There are also some considerations to be made for the fire rate of the bot with ShootTemplate and the WaitBetweenSpawns of the WaveSpawn you're using for the bots to be teleported. You want all of the teleport logic to be finished and another teleport bot ready to spawn before your shooter is ready to fire again. It's a delicate balance, however we'll go over this when the time comes, for now let's start with the logic.

First, we need a spawn point for our teleported bots:
SpawnPoints { NoFixup 1 info_player_teamspawn { "targetname" "spawnbot_pumpkin" "StartDisabled" "1" "spawnflags" "511" "TeamNum" "2" "origin" "4200 4200 600" } }

Second, we need a map wide trigger_multiple to detect when those bots spawn:
Misc { NoFixup 1 OnSpawnOutput { Target "outputs_relay" Action Trigger } logic_relay { "targetname" "outputs_relay" "OnTrigger" "buildings_spawn_trigger,AddOutput,OnStartTouch filter_pumpkin_bot:TestActivator::0:-1,0,-1" } }
Here I simply use a trigger_multiple already in the map and add what I need to it. It tells the filter (which we'll go over in a second) to test against this bot.

Here's the filter, I want any bots with bot_pumpkin in their template to be teleported:
filter_tf_bot_has_tag { "targetname" "filter_pumpkin_bot" "Negated" "0" "require_all_tags" "1" "tags" "bot_pumpkin" "OnPass" "pumpkin_teleport,Teleport,,0,-1" }
It's part of a larger template called PumpkinTeleportLogic, we'll go over the rest of the stuff in there in a bit. It has an output that tells a point_teleport to Teleport if the bot passes the filter, we'll go over that point_teleport later.

Next we make sure these templates spawn when the mission starts:
SpawnTemplate "Misc" [$SIGSEGV] SpawnTemplate "SpawnPoints" [$SIGSEGV] SpawnTemplate "PumpkinTeleportLogic" [$SIGSEGV]

Let's start with the ShootTemplate logic now, the block itself is nothing special:
ShootTemplate [$SIGSEGV] { Name DemoPumpkin Offset "0 0 -16" AttachToProjectile 1 }

Here's what DemoPumpkin looks like:
DemoPumpkin { KeepAlive 1 OnParentKilledOutput { Target "pumpkin_teleport_maker" Action ForceSpawnAtEntityOrigin Param "pumpkin" Delay 0.00 } OnParentKilledOutput { Target "pumpkin_death_relay" Action Trigger Delay 0.01 } OnParentKilledOutput { Target "pumpkin" Action Kill Delay 0.02 } prop_dynamic { "targetname" "pumpkin" "DisableBoneFollowers" "1" "disableshadows" "1" "model" "models/harvest/pumpkin/pumpkin_small.mdl" "solid" "0" "spawnflags" "0" "StartDisabled" "0" } }
As in the previous sentry gun example, we want this prop_dynamic to stay when the projectile dies so that we can target it, so we have KeepAlive at the top. In the first OnParentKilledOutput we create our point_teleport, which we haven't yet created the entities for, so let's do that now.

Firstly we need an env_entity_maker along with an associated template to spawn our point_teleport.

This env_entity_maker goes in PumpkinTeleportLogic so that it's spawned and ready to use:
env_entity_maker { "targetname" "pumpkin_teleport_maker" "EntityTemplate" "pumpkin_teleport_template" }

We also need to create the template:
pumpkin_teleport_template { NoFixup 1 KeepAlive 1 point_teleport { "targetname" "pumpkin_teleport" "target" "!activator" "origin" "0 0 32" } }
This template has NoFixup because we need to reference it outside of the template, and KeepAlive to allow it to spawn at the prop_dynamic.

You're probably wondering why target is !activator. If you look back at the filter_tf_bot_has_tag, it's OnPass output tells this point_teleport to Teleport it's target. The target now refers to the !activator of the current logic chain (the entity that started all these outputs), lets look back to see what that is.

Working backwards:
  • pumpkin_teleport received the Teleport input from filter_pumpkin_bot
  • filter_pumpkin_bot fired it's OnPass output after recieving a TestActivator input from buildings_spawn_trigger (our mapwide trigger_multiple)
  • buildings_spawn_trigger fired it's OnStartTouch output after receiving a StartTouch input from player (a bot that just spawned and entered the trigger)

Only when the bot has the tag bot_pumpkin will this entire chain of events happen (OnPass won't happen otherwise), and the bot will be teleported, cool huh?

The other keyvalue in the point_teleport of note is origin, it's been set to be 32 HU above where it spawns (so teleported bots don't get stuck in the ground).

Now that we have this entity maker and it's template created, we can go over the rest of the outputs in DemoPumpkin. Recall from our list at the beginning that we need to enable and disable our special spawnpoint, then clean up any entities that are left. We can create a relay to do this, and indeed, the next OnParentKilledOutput triggers a relay called pumpkin_death_relay, let's create that.

This relay will go in PumpkinTeleportLogic:
logic_relay { "targetname" "pumpkin_death_relay" "OnTrigger" "spawnbot_pumpkin,Enable,,0.01,-1" "OnTrigger" "spawnbot_pumpkin,Disable,,0.06,-1" "OnTrigger" "pumpkin_teleport,Kill,,0.1,-1" }
This does exactly what we need, enabling the spawnpoint and letting a bot spawn, before disabling it and killing pumpkin_teleport.

The last OnParentKilledOutput in DemoPumpkin simply kills the still alive pumpkin prop_dynamic.

Finally, the last thing to do is create a WaveSpawn for your teleport bots with a WaitBetweenSpawns that is greater than the total time the teleport logic takes to do it's thing, but shorter than the fire interval of the ShootTemplate bot.

For example, the teleport logic above takes 0.1 seconds to complete. The WaitBetweenSpawns should be > 0.1. Technically it can be > 0.05 because that's the amount of time the spawnpoint is enabled, but I prefer to give a little wiggle room instead of worrying about min maxing the times. Of course you can always modify the output delay times above to better suit your needs.

As for the fire rate of the ShootTemplate bot, you can find this out by going to the wiki for that weapon and looking at the attack interval, then modifying it based on your fire rate attributes.

If you used a 0.2 WaitBetweenSpawns the final attack interval should be > 0.2 seconds.
2 Comments
Hell-met 25 Nov, 2020 @ 4:23am 
p good
Hell-met 25 Nov, 2020 @ 4:21am 
stink