Filename: DCK_ElevatorWithRealDoors.zip
Date    : 2001-05-26
Author  : Decker
E-mail  : decker@post1.tele.dk
Homepage: http://home1.inet.tele.dk/decker/
Files   : .map - Raw mapfile readable by all editors
          .qrk - Usable for QuArK map-editor only
QuArK   : http://www.planetquake.com/quark


How the elevator works
----------------------

Actually there isn't any elevator there, it is four func_train entities which moves together in synchronization (so this elevator might not be that good for online-play).

The elevator and elevator-windows (which are func_trains) only moves up and down to the path_corner they're told to move to. The two-part elevator-doors however, moves either up and down or to the sides, depending on what floor and state the elevator is in.

All the control-button entities are set up as follows (example for floor 1):

  func_button(floor1) ---> func_button(open1) -> "open doors at floor1"
   ^                   \    ^
   |                   |    |
  multisource          |   multisource(isat1)
   ^                   |    ^
   |                   |    |
  'doorsclosed'        |   'atfloor1'
                       |
                       |
                       \-> func_button(moveto1) -> "move elevator to floor1"
                            ^
                            |
                           multisource(isnotat1)
                            ^
                            |
                           'notatfloor1'

When a player presses the func_button(floor1) or any duplicate of a func_button(floor1), it first checks its 'master' multisource to see if its global-state 'doorsclosed' are ON (meaning "all the doors are closed"). If that is true, then the func_button(floor1) will trigger the two other func_buttons, but only _one_ of them will be activated.

NOTE: I wanted to use trigger_relay entities instead of the second pair of func_buttons, but a trigger_relay entity does not respond to an 'master' multisource entity :-( So make sure that the player can not trigger/activate these "relay func_buttons"!

Triggering the func_button(open1), it will first check its 'master' multisource, to see if its global-state 'atfloor1' is ON. This means that the elevator is at floor1, and that the doors can be opened.

Triggering the func_button(moveto1), it will first check its 'master' multisource, to see if its global-state 'notatfloor1' is ON. If it is not ON, then the func_button(moveto1) will not trigger its targets.

Now to how the "open doors at floor1" works:

  multi_manager(open1) ---0.0-> env_global('doorsclosed' OFF)
                        +-0.0-> trigger_changetarget(leftdoor to path_corner leftopenfloor1)
                        +-0.0-> trigger_changetarget(rightdoor to path_corner rightopenfloor1)
                        +-0.1-> func_train(leftdoor begin move)
                        +-0.1-> func_train(rightdoor begin move)
                        +-0.2-> func_door(floordoors open)

The multi_manager(open1) first makes sure that the global-state 'doorsclosed' are set to OFF, so no func_buttons(floor#) can be triggered - if they could, the entire movement-system would go wrong! Next it tells the two elevator-doors (func_trains) that their targets should be changed to other path_corners, making them simulate a door-opening sequence. The multi_manager(open1) then waits 0.1 seconds before actually telling the elevator-doors to start moving, and at 0.2 seconds the floor doors will open.

Since nothing can block the four doors when they are opening, they do this simultaniously. However they can't close simultaniously, because something might block only the floordoors (func_doors) or only the elevator-doors (func_trains).

The solution to this is to only close one pair of doors, and when they are finally closed, make the other pair close. For this I'm using the 'fire at close' functionality of a func_door entity. The floordoors will close automatically after the 'wait' setting, and when they're closed one will trigger the multi_manager which will make the elevatordoors close:

  func_door(closed) -> multi_manager(close1) ---0.0-> trigger_changetarget(leftdoor to path_corner leftclosefloor1)
                                              +-0.0-> trigger_changetarget(rightdoor to path_corner rightclosefloor1)
                                              +-0.1-> func_train(leftdoor begin move)
                                              +-0.1-> func_train(rightdoor begin move)

Now to change the global-state 'doorsclosed' to ON, I've used the 'fire at pass' of a path_corner, so when the elevator-door reaches its destination, it will trigger an env_global switch:

  func_train(reached path) -> path_corner(fire at pass) -> env_global('doorsclosed' ON)

For the "move elevator to floor1" sequence, the following happens:

  multi_manager(moveto1) ---0.0-> multi_manager(all OFF) ---0.0-> env_global('doorsclosed' OFF)
                          |                               +-0.0-> env_global('atfloor1' OFF)
                          |                               +-0.0-> env_global('atfloor2' OFF)
                          |                               +-0.0-> env_global('atfloor3' OFF)
                          |                               +-0.0-> env_global('notatfloor1' OFF)
                          |                               +-0.0-> env_global('notatfloor2' OFF)
                          |                               +-0.0-> env_global('notatfloor3' OFF)
                          +-0.1-> trigger_changetarget(leftdoor to path_corner leftclosefloor1)
                          +-0.1-> trigger_changetarget(rightdoor to path_corner rightclosefloor1)
                          +-0.1-> trigger_changetarget(elevator to path_corner elevfloor1)
                          +-0.1-> trigger_changetarget(elevatorwindows to path_corner elevwindowsfloor1)
                          +-0.2-> func_train(leftdoor begin move)
                          +-0.2-> func_train(rightdoor begin move)
                          +-0.2-> func_train(elevator begin move)
                          +-0.2-> func_train(elevatorwindows begin move)

The multi_manager first calls another multi_manager, which turns all global-states for the elevator control to OFF - so no buttons will work, if they are activated by a player. Then at 0.1 seconds, it sets new targets for the func_trains, so they will move to the choosen floor. And at 0.2 seconds it will trigger the four func_trains to move.

It is important that nothing can block the func_trains, when they are moving up/down the elevator-shaft. If a func_train gets blocked, it will fall out of synchronization with the others, and the whole elevator-illusion looks bad.

When the elevator reaches its destination, I use the 'fire at pass' functionality of another path_corner:

  func_train(reached path) -> path_corner(fire at pass) -> multi_manager(reached1) ---0.0-> env_global('atfloor1' ON)
                                                                                    +-0.0-> env_global('notatfloor2' ON)
                                                                                    +-0.0-> env_global('notatfloor3' ON)
                                                                                    +-0.0-> env_global('doorsclosed' OFF)
                                                                                    +-0.5-> "open doors at floor1"

The multi_manager here, sets the global-states to reflect what floor we're on, and those we're not. Also it sets the 'doorsclosed global-state to OFF because there is a change that the other 'fire at pass' for the elevator-door have set it to ON. At 0.5 seconds the doors on the floor will open.
