I've been working on a training map, and I came across an interesting solution that I thought I would share.
My goal was to figure out how much health a bot had when it died. My first attempt was to have a bot_generator, math_counter, point_hurt, and a logic_timer. The OnTimer event would increment the counter and hurt the bot. The timer would start with the bot_generator's OnSpawned output and stop on the OnBotKilled output. Because of the various delays in the deliveries of events in the entity I/O system, this approach was only approximate and did not converge to the same value.
I decieded to try adding a trigger_multiple and use the OnTouching and OnNotTouching outputs. My timer was repurposed to trigger the TouchTest input. If the bot was present, the trigger_multiple would hurt the bot by one, and increment the counter by one. Otherwise, it would disable the logic_timer. This approach looked promising by giving consistent results. However it suffered from a flaw in my environment. I was working on a medic training map, and if the player was healing the bot during the test, the medigun would give the bot health faster than the logic_timer could take it away. This resulted in erronous values for the health counter.
I was curious how the logical entities were implemented. I discovered when an entity is updated, and a listener is attachaed, the entity I/O system immediately calls the listener.
CPP source
For my final iteration, I decided to rewrite it using logical_branch and logical_branch_listener.
When the bot_generator spawns a bot, the bot spawned becomes the !activator inside the OnSpawned output. I give the bot a targetname so that it can be referenced from a filter, which is used to limit the player's inside the trigger volume. The point_hurt entity is set to damage the bot's targetname.
I created a spawn_relay for convience. I want the targetname to always be the first entity message during OnSpawned. So I created a relay with a min delay.
The branch_has_died captures the events of if the bot has died. branch_activate is initially 0, but changes to one once begun.
The entity hurt_loop_relay, when triggered, hurts the bot and then fires TouchTest on the trigger_multiple. It is important that this entity has fast refire enabled. The trigger_multiple is filtered on the targetname of the bot. If the bot is detected, it adds 1 to the counter, and triggers the hurt_loop_relay. If it is not detected, it disables the relay to prevent pending events from triggering it.
To prime the loop, a logic_timer is used to enable the loop_relay, and initialize the math_counter.
To stop the loop, I created a listener on the prevously mentioned logic_branch's: branch_activae, and branch_has_died. And a new one branch_hurt_loop.
This new one is intialized from the logic_timer and only disabled when all three conditions are true: the system is active, the bot has died and the loop is active. Once triggered the hurt_loop relay is disabled, preventing the counter from increasing, and the branch_hurt_loop is set to zero preventing the listener from activating. It was needed because it was impossible to determine whether the bot_generator's OnBotKilled output or the trigger_multiple's OnNotTouching output is fired first. Both could trigger the logic_branch_listener, and the first invocation needed to disable the listener before the second would arrive.
The result is a very fast loop that damages the bot by 1 and increases a counter by 1, stopping when the bot is no longer alive.
Example .VMF file:
http://pastebin.com/ADP87seN
Compiled map file:
https://www.dropbox.com/s/uenbjgxqj58wm8n/health_counter_tests.zip
requires: sv_allow_point_servercommand always
issues: I had some issues when I made the trigger was too big or crossed areaportals or hint brushes. OnNotTouching would be generated from TouchTest on the trigger_multiple. I ended up creating multiple solid brushes and adding them to the same trigger_multiple entity. By creating a new brush, then control click new brush and existing trigger_multple, hit control+T, click yes add to existing entitiy. This seemed to solve the problem.
My goal was to figure out how much health a bot had when it died. My first attempt was to have a bot_generator, math_counter, point_hurt, and a logic_timer. The OnTimer event would increment the counter and hurt the bot. The timer would start with the bot_generator's OnSpawned output and stop on the OnBotKilled output. Because of the various delays in the deliveries of events in the entity I/O system, this approach was only approximate and did not converge to the same value.
I decieded to try adding a trigger_multiple and use the OnTouching and OnNotTouching outputs. My timer was repurposed to trigger the TouchTest input. If the bot was present, the trigger_multiple would hurt the bot by one, and increment the counter by one. Otherwise, it would disable the logic_timer. This approach looked promising by giving consistent results. However it suffered from a flaw in my environment. I was working on a medic training map, and if the player was healing the bot during the test, the medigun would give the bot health faster than the logic_timer could take it away. This resulted in erronous values for the health counter.
I was curious how the logical entities were implemented. I discovered when an entity is updated, and a listener is attachaed, the entity I/O system immediately calls the listener.
CPP source
For my final iteration, I decided to rewrite it using logical_branch and logical_branch_listener.
PHP:
entity
{
"classname" "bot_generator"
"targetname" "bot_gen"
"actionOnDeath" "2"
"spectateOnDeath" "1"
connections
{
"OnSpawned" "spawn_relay,Trigger,,0.01,-1"
"OnSpawned" "!activator,AddOutput,targetname imabot,0,-1"
"OnBotKilled" "branch_has_died,SetValueTest,1,0,-1"
"OnBotKilled" "hurt_loop_relay,Disable,,0,-1"
}
}
entity
{
"classname" "logic_relay"
"targetname" "spawn_relay"
connections
{
"OnTrigger" "branch_active,SetValue,1,0,-1"
"OnTrigger" "branch_has_died,SetValue,0,0,-1"
"OnTrigger" "hurt_delay,Enable,,0,-1"
}
}
entity
{
"classname" "filter_activator_name"
"targetname" "filter_bot"
"filtername" "imabot"
}
entity
{
"classname" "point_hurt"
"targetname" "repeatable_hurt"
"Damage" "1"
"DamageTarget" "imabot"
}
When the bot_generator spawns a bot, the bot spawned becomes the !activator inside the OnSpawned output. I give the bot a targetname so that it can be referenced from a filter, which is used to limit the player's inside the trigger volume. The point_hurt entity is set to damage the bot's targetname.
I created a spawn_relay for convience. I want the targetname to always be the first entity message during OnSpawned. So I created a relay with a min delay.
PHP:
entity
{
"classname" "logic_branch"
"targetname" "branch_has_died"
}
entity
{
"classname" "logic_branch"
"targetname" "branch_active"
}
The branch_has_died captures the events of if the bot has died. branch_activate is initially 0, but changes to one once begun.
PHP:
entity
{
"classname" "math_counter"
"targetname" "dmg_counter"
}
entity
{
"classname" "logic_relay"
"targetname" "hurt_loop_relay"
"spawnflags" "2"
"StartDisabled" "1"
connections
{
"OnTrigger" "repeatable_hurt,Hurt,,0,-1"
"OnTrigger" "poll_alive,TouchTest,,0,-1"
}
}
entity
{
"classname" "trigger_multiple"
"targetname" "poll_alive"
"filtername" "imabot"
"spawnflags" "1"
"wait" "1"
connections
{
"OnNotTouching" "branch_has_died,SetValueTest,1,0,-1"
"OnNotTouching" "hurt_loop_relay,Disable,,0,-1"
"OnTouching" "hurt_loop_relay,Trigger,,0,-1"
"OnTouching" "dmg_counter,Add,1,0,-1"
}
solid
{
...
}
}
The entity hurt_loop_relay, when triggered, hurts the bot and then fires TouchTest on the trigger_multiple. It is important that this entity has fast refire enabled. The trigger_multiple is filtered on the targetname of the bot. If the bot is detected, it adds 1 to the counter, and triggers the hurt_loop_relay. If it is not detected, it disables the relay to prevent pending events from triggering it.
PHP:
entity
{
"classname" "logic_timer"
"RefireTime" "5"
"spawnflags" "0"
"StartDisabled" "1"
"targetname" "hurt_delay"
connections
{
"OnTimer" "!self,Disable,,0,-1"
"OnTimer" "hurt_loop_relay,Enable,,0,-1"
"OnTimer" "branch_hurt_loop,SetValueTest,1,0,-1"
"OnTimer" "dmg_counter,SetValueNoFire,0,0,-1"
"OnTimer" "hurt_loop_relay,Trigger,,0.01,-1"
}
}
To prime the loop, a logic_timer is used to enable the loop_relay, and initialize the math_counter.
PHP:
entity
{
"classname" "logic_branch"
"targetname" "branch_hurt_loop"
}
entity
{
"classname" "logic_branch_listener"
"Branch01" "branch_active"
"Branch02" "branch_has_died"
"Branch03" "branch_hurt_loop"
"targetname" "hurt_loop_end_condition"
connections
{
"OnAllTrue" "hurt_loop_relay,Disable,,0,-1"
"OnAllTrue" "branch_hurt_loop,SetValueTest,0,0,-1"
}
}
To stop the loop, I created a listener on the prevously mentioned logic_branch's: branch_activae, and branch_has_died. And a new one branch_hurt_loop.
This new one is intialized from the logic_timer and only disabled when all three conditions are true: the system is active, the bot has died and the loop is active. Once triggered the hurt_loop relay is disabled, preventing the counter from increasing, and the branch_hurt_loop is set to zero preventing the listener from activating. It was needed because it was impossible to determine whether the bot_generator's OnBotKilled output or the trigger_multiple's OnNotTouching output is fired first. Both could trigger the logic_branch_listener, and the first invocation needed to disable the listener before the second would arrive.
The result is a very fast loop that damages the bot by 1 and increases a counter by 1, stopping when the bot is no longer alive.
Example .VMF file:
http://pastebin.com/ADP87seN
Compiled map file:
https://www.dropbox.com/s/uenbjgxqj58wm8n/health_counter_tests.zip
requires: sv_allow_point_servercommand always
issues: I had some issues when I made the trigger was too big or crossed areaportals or hint brushes. OnNotTouching would be generated from TouchTest on the trigger_multiple. I ended up creating multiple solid brushes and adding them to the same trigger_multiple entity. By creating a new brush, then control click new brush and existing trigger_multple, hit control+T, click yes add to existing entitiy. This seemed to solve the problem.
Last edited: