Mapping & Hammer

Elevators and Trains in Hammer: func_door to func_tracktrain

Elevators and trains in Source and GMod maps: which entity fits, setup with keyvalues, call buttons, path_track routes, causes of crushing and stutter.

You build a two-stop elevator from a func_door or func_movelinear, and multiple floors or trains from a func_tracktrain that follows a chain of path_track entities. Players get crushed when a block damage value is set and the entity keeps pushing while it is blocked. The stutter while riding is described by the Valve wiki as a general trait of vertically moving platforms in multiplayer Source games. It names no fix, so test the lift early. An elevator usually gets stuck because something is in the way or the route is chained incorrectly.

Which entity for which job

EntityMovementFitsLimits
func_doorbetween two positions, distance = brush size minus Lipsimple lift with two stopscannot stop in between
func_movelinearalong one direction over Move Distance, any position in between via SetPositionlift with two stops, platforms, gatesbuggy according to the Valve wiki when parented to another func_movelinear
func_tracktrainalong a chain of path_trackmultiple floors, trains, curvesmore work, stops slightly short of its target at higher speed

func_tanktrain is a train that shoots at the player and can be killed, so it does not fit here. func_train and func_plat are not in GMod's FGD.

A two-stop elevator

A func_movelinear is the least work:

  1. Build the cabin floor as a brush, press Ctrl+T and choose func_movelinear. Name: lift.
  2. Set Move Direction (Pitch Yaw Roll) to Up. The field then reads -90 0 0.
  3. Enter the distance between the two floors in units under Move Distance, and the pace under Speed.
  4. Build the walls and ceiling of the cabin as a func_brush and enter the name lift under Parent.
  5. Place a func_button on each floor as a call button and connect it to the lift.
Entity        My Output       Target Entity   Target Input
button_up     OnPressed       lift            Open
button_down   OnPressed       lift            Close
lift          OnFullyOpen     door_top        Open
lift          OnFullyClosed   door_bottom     Open

Open travels to the end position, Close back to the start position. OnFullyOpen and OnFullyClosed report the arrival, which is what you use to open the floor doors. How outputs, delays and names work in detail is explained in the article on inputs and outputs in Hammer.

With a func_door the maths is different: a door always travels by its own brush size, minus Lip. A floor 16 units thick that should rise 256 units therefore needs a Lip of -240. Add Delay Before Reset (-1 stay) set to -1, the Toggle flag and an unticked Touch Opens.

Doors and buttons on the cabin

Everything that should ride along gets the name of the moving entity in its Parent field. A child keeps its offset to the parent and follows every movement.

  • Cabin doors: two func_door entities with Parent = lift. Do not use a second func_movelinear here, because of the parenting bug from the table.
  • Buttons in the cabin: func_button with Parent = lift. With the Don't move flag, the button does not travel into the wall when pressed.
  • Locking buttons: On OnPressed, send the Lock input to all buttons (lift_button*), and Unlock again on OnFullyOpen and OnFullyClosed.
  • Sandbox tools: GMod's FGD offers Allow Physics Gun and Sandbox Tool Whitelist for these entities. Set the former to No so that nobody in Sandbox grabs the lift with the Physics Gun.

Trains and multiple floors with func_tracktrain

  1. Place the first path_track and give it a name such as track_1. If you clone it while holding Shift, Hammer counts the name up and fills in Next Stop Target by itself.
  2. Build the train so that its front points along the X axis, to the right in the top view. When it spawns, it turns towards the next path_track.
  3. Turn it into a func_tracktrain and enter the first path_track under First Stop Target. The train spawns there.
  4. Max Speed (units / second) caps every speed. Initial Speed (units / second) 0 means the train stands still at first.
  5. Set the No User Control flag. For an elevator, add Fixed Orientation so the cabin does not rotate.
  6. Start it with the StartForward input. At a stop, the path_track sends Stop to the train through OnPass.

For a loop, enter the first path_track as the Next Stop Target of the last one. New Train Speed on a path_track changes the speed from there on, but only takes effect with No User Control.

In GMod, func_tracktrain also has the MoveToPathNode input. It travels to the named path_track on the same path and stops there. That makes an elevator with many floors simple:

Entity          My Output                    Target Entity   Target Input     Parameter   Delay
button_floor3   OnPressed                    lift_door       Close                        0.00
button_floor3   OnPressed                    lift            MoveToPathNode   floor_3     1.00
lift            OnArrivedAtDestinationNode   lift_door       Open                         0.00

The Valve wiki lists two more quirks: a func_tracktrain only updates its direction ten times per second, so fast trains overshoot the track in tight curves. And Reverse or StartBackward can fail if New Train Speed is 0 on the path_track entities involved.

Why elevators crush, stutter or get stuck

Crushing. Each of the three entities has its own damage value: Blocking Damage on func_door, Block Damage on func_movelinear, Damage on Crush on func_tracktrain. The default is 0 everywhere. For the door and func_movelinear the FGD states the value per frame, so small numbers kill quickly. How the entity behaves when blocked can be seen in the public Source SDK code: a func_door reverses, unless Delay Before Reset is negative or Force Closed is set. A func_movelinear never reverses and keeps pushing. With 0 the lift deals no block damage, but a player in the shaft can hold it up. So either keep 0 and close the shaft with floor doors that only open once the lift has arrived, or set a damage value and let the lift win. On a func_door you can react yourself with OnBlockedClosing and OnBlockedOpening.

Stutter. The Valve wiki states that any vertically moving platform seems jerky to its riders in multiplayer games, and gives the network physics of players as the reason. A tip from the discussion page of the Valve wiki's elevator tutorial, not documented and not something every map confirms: do not let players stand directly on the func_tracktrain, but on a func_brush parented to it. For func_brush, the FGD describes the Solid BSP keyvalue for exactly this case, a brush in the hierarchy of a moving object that players stand on. Check the result on a real server with ping, as described in playtesting a GMod map with players.

Props on board. A prop_physics is simulated by physics and, according to the Valve wiki, behaves oddly when you parent it. Decoration in the cabin therefore belongs on the lift as a parented prop_dynamic. Loose physics props on a moving platform can slide around and get wedged. That is experience from practice, not documented behaviour. The prop types are compared in prop_static, prop_dynamic and prop_physics.

Getting stuck. Is something in the way? Does a path_track have the Disabled or Disable train flag set, or an empty Next Stop Target? Were buttons locked but never unlocked, because the lift was blocked and OnFullyOpen never came? On a func_tracktrain, the Is unblockable by player flag helps against players blocking it.

FAQ

Why does my func_tracktrain not stop exactly at the path_track?

The train stops just before its target, more noticeably at higher speed. Lower the speed before the stop, or move the path_track by the missing distance until the cabin sits flush.

Why is the cabin lit wrongly during the ride?

Brush entities carry precomputed lightmaps from their starting position, which no longer match once the cabin travels into a differently lit area. The remedy: build the visible cabin as a prop_dynamic and parent it to an invisible brush. More on this in the Source engine lighting guide.

Can players drive a train themselves?

Yes, as long as the No User Control flag on the train is off. According to the Valve wiki, a player standing on a func_tracktrain can then control it. A func_traincontrols whose Train Name and Parent both point at the train limits this to its brush: the player has to stand inside it and on the train.