[Entity I/O] Determine Bot Health

happs

L1: Registered
Nov 6, 2014
5
3
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.


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:

wiseguy149

Emperor of Entities and Amateur of Aesthetics
May 12, 2009
103
220
That's some clever use of AddOutput to give an entity something to filter by. AddOutput has the potential to be one of the most powerful and useful tools for a mapper playing around with entities, yet it's so rarely used.