The Entity Guru: The Car: The Chairman: The Derby

Discussion in 'Mapping Questions & Discussion' started by A Boojum Snark, Jun 10, 2010.

  1. A Boojum Snark

    aa A Boojum Snark Toraipoddodezain Mazahabado

    Positive Ratings:
    I don't believe he expected me to do it, or if he had any bit of expectation the statement was probably in jest. None the less, I deemed it possible and jumped to the challenge without telling him.

    and so, I give you...

    >> ZIP | BZ2 <<

    About the map:
    There are four Chairman cars. You may race them, or just drive them down the road.
    Car features include:
    • 100% free-roaming control mode.
    • In-car 360 degree first-person view.
    • Third-person chase-cam toggle.
    • Speed-sensitive pitch-shifted engine sound.
    • Headlights.

    There is a starting signal you may use for racing with friends, though it does not restrict movement! Feel free to verbally abuse friends who begin prematurely.
    The first car across the finish is also recognized with a celebratory trophy, celebratory confetti, and celebratory cheering from a disembodied crowd.

    The cars are capable of passing through nearly every type of map object. Therefore travel has been restricted to the road by rigging up a kill mechanic if you drive into the trees. Please stay on the road for your own safety.

    Player models were left visible on purpose, despite sticking through the floor and roof of the car.

    Thanks to Rexy for making the excellent model of the Chairman.

    Known issues:
    IMPORTANT EDIT: Apparently if anyone is a car when waiting for players ends, it will crash. Seems to be a fourth player-exit scenario I did not think of because I always skip that timer. Use mp_waitingforplayers_cancel 1 to cancel it so you can play on your own.

    Also, the map does not appear to function properly on dedicated servers, so don't try running it on one. I will investigate this issue later.
    /end important edit

    If you use the eject, suicide, or get killed while in the chase-cam view, you will NOT exit the camera view. This appears to be an issue with the way things are coded and I was unable to avoid it. The chase-cam was too neat of a feature to leave out due to this one thing, and the issue could be partially fixed by giving the camera a timeout, which I didn't opt to do since this is mostly a proof-of-concept and can't be taken seriously.
    This does not occur if you crash into the trees and die.

    Colliding with other cars only occurs because of contacting the other player. It may or may not cause a jam, but in my experience it usually does not. But do try to keep your distance for courtesy.

    When inside the car, the camera will clip through the roof if you are a Heavy, Medic, Sniper or Spy. The reason is their camera positioning is higher than other classes, and I didn't want to make the others lower to fix the clipping.

    The entity mechanics:
    A few small notes:
    • the game_ui entity
    This is what allows us to interface with the commands the player sends. It has outputs such as OnPressedAttack and OnUnpressedMoveLeft. A fairly simple entity in concept, just send it an Activate and it goes to work.

    • naming the player entity
    When the player activates the game_ui, they are given an entity name via the AddOutput input with a parameter of targetname racer_#_player, where # is the number for that particular car. When they leave the car, this is "reset" by simply re-naming them "null". This is not a special name, just what I use. It could also be bob or uselessplayer or awlnignlis.

    The Core: the free-roaming train
    This is where the biggest piece of magic resides. Trains can only go on predefined paths, right? Not quite...


    A path_track cannot be parented because, like several things, they cease to work when you do. However, nearly all such things can be pseudo-parented with the wonderful entity logic_measure_movement. This entity will track the relative motion of one entity and correspondingly move another. In this case, the func_brush in the image is being measured, and the "goal" path is being moved along with it, effectively parenting without destroying it.
    Now a func_brush doesn't do much, so it is parented to the momentary_rot_button, which is in turn parented to the func_tracktrain (not shown). Once the train passes the starting paths, it will attempt to reach the goal path, but be unable to since it will always be moved further away. A wonderful carrot-on-a-stick mechanic.

    All we have to do now is link the aforemention game_ui's OnPressedForward output to make the train move forward, and OnPressedMoveLeft/Right get linked to the m_r_b which will shift the goal path to the side, causing the train to turn in an effort to chase it. It is important to note the train responds very quickly to this, so in my setup the m_r_b only moves 2.5 degrees to the left or right, and the train still has a tight turning radius if moving slowly.

    I did not wish to implement a move backward feature, but this could be accomplished by having another path between the two mentioned, which gets logic_measure_movement-linked only after the train has passed it, resulting in a double-ended carrot-on-a-stick.

    The speed-sensitive pitch-shifted sound
    Now, before you say it, yes, the func_tracktrain "move sound" can do this by itself. However the sound only plays while the train is in motion, which would be pretty dumb to have the engine sound starting and stopping every time you do! So I set out to do it manually...


    Here we make use of the point_velocitysensor entity. The problem is it can only measure along one axis. However, if we set two p_vs to measure directly along the X and Y axes, there is a right angle between them we can use to solve for the real speed.
    The good ol' Pythagorean Theorem says legA² + legB² = hypotenuse², so I have each p_vs first set it's value into a math_counter, then send the value again as a multiplication function, resulting in the square. Each of these counters then sends its value to a third where they get added, resulting in the square of the true speed.
    Now we don't have the means to easily take the root of a number, but this isn't needed since we scale the number into the desired pitch range via a math_remap anyway. It ends up slightly inaccurate, but it's better than stop-and-start sound.

    Additionally, I have the p_vs' disable themselves every time they send an output, to give the math a chance to compute, and then reenable themselves. This is because they are one of a handful of entities that spew outputs multiple times per second whenever they are active, which would cause the counter to get out of sync.

    The boundaries
    Because a func_tracktrain is used, there is very little that can impede it's progress as it has very little physical interaction with the world. After all, it's meant to stay on pre-defined routes. It will collide with physics-based objects, but such collisions are very "sticky" and not ideal for use when the player is driving the train, let alone racing.

    I opted for a more malevolent method: simply kill the player. This is not as simple as one may expect though, the player has been parented to the train, and any parented objects lose all trigger interaction, meaning a trigger_hurt just will not do.


    What we have here is a func_physbox which will follow the train around by use of another logic_measure_movement. The important part of this is the material used: toolsnpcclip. It is important because it is non-solid to pretty much everything (no NPCs in TF2), but it retains trigger-interaction, which a lot of non-solid things/materials do not.
    Now we have an object on our train/car which can interact with boundary triggers, but how does that help us kill the player? This is where the rarely used but irreplaceable UserIO come into play. All the boundary triggers are given the output OnStartTouch > !activator > FireUser1, and then the physbox for each car is given an output of OnUser1 > racer_#_player > SetHealth > -10000. In this way we can use the physbox to "forward" the input along to the correct player, since the trigger does not know who it is.

    The player exit fail-safes
    There are three ways the player can disengage from the car system:
    • Deactivating the game_ui by hitting jump (one of the game_ui flag options).
    • Dying, either by suicide, another player, or out-of-bounds.
    • Leaving the server.
    Each of these presents unique situations that must be handled accordingly, and was in fact the most challenging part of this whole setup.

    The first is the easiest as game_ui has the PlayerOff output we can use to disable all the various systems related to the car, be it lights or sounds or cameras.

    Death presents an interesting case, because dying does not deactivate the game_ui you are linked to. To handle this there is a trigger_multiple over the player that is filtered via filter_activator_name to the corresponding racer_#_player. This has two OnEndTouchAll outputs.


    It will trigger the relay shown there, which in turn tests a logic_branch. The relay is used as an intermediary because if the player leaves the trigger by directly deactivating the game_ui we don't want any of this to happen, so the game_ui disables the relay on PlayerOff.
    The purpose of the logic_branch is to then determine whether it was a death or a disconnection. This is important because of a bug in the game_ui code: trying to deactivate an inactive one will cause a crash. As said previously, dying does not deactivate, but obviously leaving the server would.

    How we make this determination is by actually using the player to send an output. When they first boarded the car, AddOutput was used to give them OnUser1 > racer_#_suicidebranch > SetValue > 1 (1 = true = suicide/death, not disconnect). Back to the trigger, you'll see that before it fires the relay it attempts to tell the player (represented here by an info_null, an entity which is removed during compile) to FireUser1. Therefore if the player disconnected and does not exist, the output will never be sent and the branch will remain false, causing it to only reset the car and not attempt to deactivate the game_ui r release the player.

    The chase-cam, a minor snag

    I found that the point_viewcontrol is yet another entity that ceases to function properly when parented. Once again I used a logic_measure_movement to link it to the car, except this time I used an info_teleport_destination as the tracking entity because it's a simple point entity but still has angle info, which is necessary for the camera to be aligned properly as the angles of something an l_m_m is moving do not matter, they get matched to the tracked entity's angles. The i_t_d is also parented to the m_r_b used for steering, to give a slight sway in the camera when turning.

    Last words:
    There may still be a few obscure bugs lurking amidst this complicated system. I don't recommend running it on a heavily populated server since there are only 4 cars and no means to prevent havoc from ensuing. It is mostly as a proof of concept and having a little fun with friends.

    Yes, I did manage it in under 48 hours :p I spent probably 6-7 hours hunting down the player-exit crashing bugs. This post was all written up after the 48, though.

    Attached Files:

    • Thanks Thanks x 52
    • Like Like x 1
    Last edited: Mar 25, 2017
  2. The Political Gamer

    aa The Political Gamer

    Positive Ratings:
    Holy fuck.
    • Thanks Thanks x 2
  3. Prestige

    aa Prestige im not gay anymore

    Positive Ratings:
    It's too easy to CRASH.
    Last edited: Jun 10, 2010
  4. Seba

    aa Seba DR. BIG FUCKER, PHD

    Positive Ratings:
    Jizz in my pants.
  5. Bermuda Cake

    Bermuda Cake L9: Fashionable Member

    Positive Ratings:
    boojum, you complete maniac

    I love you
  6. Rexy

    aa Rexy The Kwisatz Haderach

    Positive Ratings:
    Indeed, holy fuck! Gotta try this out.

    Edit: I would have detailed the interior more if I knew it was going to be used like this!
    Last edited: Jun 10, 2010
  7. Micnax

    aa Micnax Back from the dead (again)

    Positive Ratings:
    You're fucking crazy

    Brilliant, but crazy
  8. DaBeatzProject

    aa DaBeatzProject

    Positive Ratings:
  9. DJive

    aa DJive Cake or Death?

    Positive Ratings:
  10. Washipato

    Washipato L3: Member

    Positive Ratings:
    Mad crazy, I love this kind of stuff
  11. absurdistof

    aa absurdistof

    Positive Ratings:
    Boojum, you know what this means.. The gauntlet is down my friend. Let us hope these ashes may yield a phoenix that may shine brighter then this light.
  12. Steff0o

    Steff0o L6: Sharp Member

    Positive Ratings:
    btw, is it possible to have more cars?
  13. littleedge

    aa littleedge L1111: Clipping Guru

    Positive Ratings:
    I found it creative. But eh.

    Show Valve.
  14. grazr

    aa grazr Old Man Mutant Ninja Turtle

    Positive Ratings:
    Couldn't you prevent players clipping each other by forcing players onto one team? Making it safer to actually pass and race each other.
  15. HeaH

    HeaH L8: Fancy Shmancy Member

    Positive Ratings:
    My head just exploded
  16. A Boojum Snark

    aa A Boojum Snark Toraipoddodezain Mazahabado

    Positive Ratings:
    Steff: entirely possible, it just means a lot more stuff going on.

    grazr: no. It's the players colliding with another player's train, not with each other.
  17. Steff0o

    Steff0o L6: Sharp Member

    Positive Ratings:
    does that mean a bigger change of crashing?
  18. Dr. Spud

    aa Dr. Spud Grossly Incandescent

    Positive Ratings:
    Really crazy stuff!

    Does it not work to parent a physbox to the cars to make them collide with things?
  19. A Boojum Snark

    aa A Boojum Snark Toraipoddodezain Mazahabado

    Positive Ratings:
    Haha! When I was working on it I actually thought "rexy is going to hate me for putting players so close to the low-res interior"
    Though actually, a topless version would be best so your head/camera doesn't stick through the roof :p

    Steff: yes and no. It won't make it inherently crashier, but that which may cause a crash will be happening more often.

    Spud: Physics things don't like being parented, they have to be constrained. I didn't actually tried that, but the things I did try lead me to believe it will end up with the same result: poor collisions and/or sticky collisions.
  20. Mick-a-nator

    aa Mick-a-nator

    Positive Ratings:
    314 how do you come up with these? and how can you remember enough entities and their functions to put together all of that?