Egg War: Mechanical logic and other complexities

A Boojum Snark

Toraipoddodezain Mazahabado
aa
Nov 2, 2007
4,775
7,669
An in-depth explanation of the entities in Egg War

Reading this, you should be familiar with naming player entities, !targets, and User I/O. If not...
• The player's entity (type: 'player') can be given a name like any other using AddOutput to change the targetname key on the entity.
• Using !activator as an output target will send it to whatever entity started the chain. If a player walks into a trigger and four entities later one targets !activator, it is sent back to the player.
• Using !self will cause an entity to send an output to itself, useful for when you don't want to give it a name or the name will be changing.
• Every entity has FireUser1(2/3/4) inputs and OnUser1(2/3/4) outputs. When an entity recives FireUser it sends the corresponding OnUser output, that's all.

________________________________________________

Part 1: Dynamic Eggs
Simple stuff first. I wanted the eggs hovering on the capture point look the same as the ones brought to it, so I needed to dynamically set them according to which of the 18 eggs was captured.

Each flag has the following outputs, the InValue Parameter being the same as the skin number used for the egg. The outputs are sent to all of the team's cases, but only one will exist.
info_e01_flag.png


Each team has a stack of 12 logic_case and 11 point_template. Only the first case exists when the game starts, the rest are removed into the template. There are cases set up for each possible skin value, and OnUser1 is used to make the egg visible and spawn the next logic_case regardless of which InValue is sent.
info_e02_cases.jpg


When the template spawns the next logic_case, it Kills both itself and the previous logic_case.

________________________________________________

Part 2: Tiny People and Unique Naming
This will get really complicated, so hopefully starting simple and building up each layer of complexity should make it followable.

The Shrinking
Basics
Shrinking the player is the easiest thing since Valve added it themselves for, obviously, the halloween shrinking spells. There are two ways to get it done though:
• SetModelScale, the same input that prop_dynamic has, and will change the size of the player. The practical range is from 0.22 (below, the camera can clip through the floor) up to somewhere around up to 3.76, above which errors flood the console. You can set it to 0 and be infinitely small (invisible) without error, but it looks awful.
Condition 75, is used for the actual spell and can be applied for a specific duration with trigger_add_tf_player_condition. Scale used is 0.5, it forces thirdperson, and your head grows big.

Problems
Both of these have problems for what I wanted to do, however. When you make someone larger using SetModelScale they will simply get stuck if they are near anything their new bounding box would clip into, even as innocuous as uneven ground. This is resolved by condition 75, which has unstuck code that nudges the player into free space (or if none is found, kills them).
However, the condition also forces thirdperson which is completely unacceptable for running through tiny tunnels, so it can't be used alone. (SetForcedTauntCam can't return you to first person, either)

Solution
Thus, SetModelScale has what we want for going small, and Condition 75 has what we want for expanding to normal, it was just a matter of figuring out how to use them as such.

Using a trigger_add_tf_player_condition set to 75 with a duration of 0 worked for this as it would instantaneously and imperceptibly shrink and expand the player, thus utilizing the unstuck code we wanted. Shrink the player with a direct input, expand them with a trigger_ATPC.

Where and When?
I have the shrink occurring in one location as players pass through it, but the expand needs to happen everywhere at certain times. To determine who should be expanded (and thus controlling when) the trigger_ATPC needs to be filtered by player name.

info_01_filter_expand.png

(wildcards work in filters!)

Using that, only players with a name beginning with expand will be affected. But I can't just have a giant trigger covering the whole map, because filters are only checked when an entity enters the trigger, so they would never be re-checked after spawning.
To resolve this, the trigger_ATPC is a 64 unit thick brush that covers the entire length and width of the map, parented to a door that moves the height of the map. It sweeps across the map once every second, expanding any eligible players it finds.

How long? Duration and timing outputs
So far, it is pretty simple to set up since you could just use delayed outputs on the shrinking trigger to name someone expand and have their time run out. Of course, I always want more and simple is never an option. I wanted a modifiable duration because I wanted to give a small time bonus for capturing an egg while you are still tiny. Thus delayed outputs are no longer feasible.

Since I only want the bonus for tiny people, it also means we need three names instead of two: normal players, tiny players, tiny players who are out of time and are tagged for expansion. The reason for the interim "to be expanded" name instead of going directly from tiny to normal is that the trigger_ATPC would continually be unsticking players as they walk around resulting in annoying jitters. They need to be hit once and then filtered out.

For adjustable time, I need to use a logic_timer which can be started, stopped, and arbitrarily extended without modifying the base time period.

At the capture point I have a func_flagdetectionzone to catch egg runners, but this entity itself can't be filtered, so players are checked manually by a filter via inputs.

info_02_bonusfilter.png


So if the player is both tiny AND Blu team then they "pass" the filter and OnPass will be fired. Matching setup for Red. FireUser4 will be explained later on, for now just know it is what actually increases the time.

Player Outputs
So how do I link a logic_timer to the player and juggle three names? Now the fun starts...

The player is given the following User outputs via AddOutput:
Output | Target | Input | Parameter
OnUser1|namer_button_P1|PressIn
|
OnUser2|!self|AddOutput|targetname tiny_P1
OnUser2|namer_timer_P1|Enable
|
OnUser3|!self|AddOutput|targetname normal_P1
OnUser3|namer_timer_P1|Disable
|
OnUser4|namer_timer_P1|AddToTimer|15

No screenshot because they don't exist in Hammer except as input parameters on other entities (which we will get to) and I wanted to keep it simple to read.

OnUser1 and the associated button will be explained soon.

OnUser2 is used when shrinking, assigning the player a tiny name and turning their timer on.
The shrinking trigger is pretty straightforward:
info_03_shrinktrigger.png

A bonus to this is that Enabling a timer that is already Enabled resets it, so tiny players jumping into the shrink vortex are refreshed to full time without any extra effort.

OnUser3 is for when the player returns to a normal state, which can occur by either running out of time, or death (in which case the timer needs to be stopped).
The first is done with a trigger_multiple identical to the trigger_ATPC and moving along with it (for some reason, the ATPC won't send OnStartTouch despite having it.
info_04_trigger_normal.png

Death is handled with trigger_multiple that covers the entire map. When a player dies they stop touching it and thus:
info_05_trigger_death.png


OnUser4 is pretty self-explanatory. It extends the timer duration as mentioned earlier.

Finally, the timer itself is what sets the expansion name.
info_06_logic_timer.png



Unique Player Naming
Ok, so that is how all the shrinking logic works, but how do we actually give players those outputs and keep everyone separate from each other?

Things that must be considered:
• Each player must have a unique name and matching set of entities.
• It must be global because names persist through death, teamchange (including spectator), and class change.
• If someone leaves the server their name is freed for reuse, but we need to know which one it is.
• The system must maintain integrity throughout rapid player joining and leaving.

Mechanical Logic - a relic of the golden era lives again
Back in the days before HL2, all entities could do is trigger another, no various outputs or numerous logic entities. Instead you would do stuff like an env_beam that randomly strikes a selection of func_button targets because there was no logic_case.

This is based off that concept because it greatly simplifies what would otherwise be an incredibly complex logic system [edit: turns out not quite, see caveat below and later post]. But simple doesn't always mean conservative entity use...

info_07_mechanical.jpg


33 func_button, 32 logic_relay, 32 logic_timer, 1 env_laser and an info_target for it.

A player "enters" the system when they spawn, touching a trigger_multiple. It uses OnTrigger with a 0.5 second reset delay rather than OnStartTouch because we want to discretely handle each player one at a time (this is why eggwar has a short setup time, to let the system iterate a full 32 players before they leave the spawn).
The trigger attempts to fire every relay at once but only one will be Enabled. The trigger also starts disabled, but is enabled by the logic_auto after 1 second because the system needs a moment to be initialized.
We also only want this trigger to work on new players that have not been assigned a name, so we filter it to players with blank targetnames. (Who knew blank name filtering worked? Something I learned out of this)
info_08_filter_nameless.png


The relays are what assign initial names and the previously discussed outputs to the player, with P# for 1-32. Note that they all start disabled.
info_09_logic_relay.png


In addition to what we covered about player outputs, the relay disables itself and tells the laser to turn on. The laser's target is at the other end of the row of buttons, but the difference between env_beam and env_laser is that beams go through objects and lasers will stop at the first solid object. This is the key to the whole system.

The buttons are set to Toggle and Damage Activates, and move upward equal distance to their height. When the laser activates it will damage the first button in the way, enabling the relay below, turning off the laser, and moving the button out of the line of fire.
info_10_func_button.png


Now a different relay is enabled, and when the trigger activates for the next player, they will be assigned another name.

Leavers
If you have a habit of looking at server logs you may have seen the line Firing: (game_playerleave) whenever someone leaves the server. I suspect this is another relic left in the code from Goldsrc when all entity could do was trigger another one. What this means is, whatever a play leaves the server sends an "input-less fire" to any entities named game_playerleave. The is the same as if you use ent_fire without specifying an input. In both cases, Source sends the generic Use input.

This can be utilized through a func_button by having it Start Locked and the OnUseLocked output, one of the few things in the game that respond to a Use input. When the button is fired it will first disable the naming trigger to isolate the system from new players, disable all the relays, reset all the buttons to their Out position, and send FireUser1 to every player entity. As shown earlier, the players were given an OnUser1 that sends PressIn to their corresponding button. After a short delay the laser is turned on and the trigger is reenabled.
info_11_game_playerleave.png


To put it more simply, what this does is cause every player to "check in" with the system and verify they are still on the server. Anyone who left is not around to send the input to their button, leaving it in line of fire for the laser to pick as the next available name.

Additionally, the logic_auto will Use this button at the start of the round to initialize the system and enable the first relay.

Visualizing the System
To help understand it, I have a video of the system in action. The green cubes are visual indicators of a relay being enabled, they are what I used for debugging when I was creating the system.

It starts with two buttons pressed in, and one relay enabled (first relay was used on me). Then I add 11 bots, kick 1, add 1, kick 2 at once, then add a bunch more.

https://www.youtube.com/watch?v=tfyDaP-kvHo

Alternative method using logic_branch
Check post #11 below for details on another way to do it which I figured out after the fact.

________________________________________________

Hopefully you made it through all that okay and learned something you can find useful someday.
The system could have a lot of uses, but the nuance of all the settings for the specific situations mostly prevent me from supplying any easy to use prefab.
 
Last edited:
Mar 23, 2013
1,013
347
I'd like to mention that changing the targetname of players can eventually conflict with other server plug-ins. Once I had to scrap a script which changes the targetname of a player, since it didn't work because the plug-ins of the server changed it to whatever they needed to.
 

A Boojum Snark

Toraipoddodezain Mazahabado
aa
Nov 2, 2007
4,775
7,669
Man, I use player naming for so many things big and small that my gut reaction to that is "Get out of my entities!" ...plugins should have so many other ways to accomplish it with actual code at their disposal that maps take precedence in what "should" be using names.
 

xzzy

aa
Jan 30, 2010
815
531
No one really uses server plugins for the power of good anyways. It's all donator perks and advertisements, so screw 'em.

It would almost be worth it to make a setup that randomly garbles the string just to mess with those mods but I suppose that might be considered juvenile.
 

Penguin

Clinically Diagnosed with Small Mapper's Syndrome
aa
May 21, 2009
2,039
1,484
you're crazy like a fox
 

A Boojum Snark

Toraipoddodezain Mazahabado
aa
Nov 2, 2007
4,775
7,669
Well, despite thinking about logic entities a bunch beforehand, I guess it took actually building the mechanical system (and then walking through it again to write this thread) and some subconscious stewing for me to realize the same construct can be done just as easy, if not easier, with the logic_branch. All my previous (more complex) ideas were different logic flows.

The logic (I am getting tired of that word) of the system is effectively the same, just implemented differently. The 32 buttons and relays are replaced by 32 branches. Rather than having relays enable and buttons be pressed, the true/false state can be used to mean "Is this name available?"

info_12_logic_branch.png


All branches start out set to True (1). All the outputs that were previously on the relay are now OnTrue. Instead of a player triggering all relays simultaneously and "hitting" the enabled one, a Test is performed on the first relay in line. Being set to True, it assigns outputs to the player and set itself to False (rather than disabling and firing the laser to enable another relay).
The next player causes OnFalse to fire, sending them to the second branch where True is found. So on and so forth down the line.

When someone leaves, the game_playerleave button now sets all branches to True, and players check in to set their own branches back to False.

Is one better than the other? I'm not really sure. One uses a third less entities (we need 32 timers either way). The mechanical system is "instant" in that all relays are triggered at once and only the available one responds and the next relay is "instantly" chosen by the laser. Whereas the branch system has no preemptive choosing and instead iterates through the entire chain starting from 1 for each player, which is effectively instant anyway (the console reports the whole IO string occuring in the same hundredth of a second).

I don't know which one I'll end up using in the map, but I'll leave the original post alone for the knowledge it can provide, with this post supplementing it.
 
Last edited:

A Boojum Snark

Toraipoddodezain Mazahabado
aa
Nov 2, 2007
4,775
7,669
I mentioned them only in passing because they aren't very special, but they are:
OnMultiNewRound > game_playerleave > Use
OnMultiNewRound > trigger_namer > Enable | 1 second delay
 

Werewolf

Probably not a real Werewolf
aa
Apr 12, 2011
873
309
Is it possible to remove an assigned players targetname without them leaving the server? Or would I just need to give them the targetname "player' to clear it?

I'm looking to use this system as part of my race track lap counter (See thread here) but I'd like to clear players targetnames when the race is over so that the names can be used by the next set of racers on the track.
 

YM

LVL100 YM
aa
Dec 5, 2007
7,135
6,056
clear is with "addOutput targetname " the space at the end is very important, that's what gives them a blank name.