[Entity I/O] Determine Bot Health

Discussion in 'Tutorials & Resources' started by happs, Jul 24, 2015.

  1. happs

    happs L1: Registered

    Messages:
    5
    Positive Ratings:
    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: Jul 24, 2015
  2. wiseguy149

    wiseguy149 Emperor of Entities and Amateur of Aesthetics

    Messages:
    103
    Positive Ratings:
    160
    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.