Elevators, multi-floor

From The DarkMod Wiki
Revision as of 09:42, 9 September 2021 by Thebigh (talk | contribs) (typo)
Jump to navigationJump to search

Multi-floor elevator using custom Dark Mod entities

written by Fidcal based on a summary by greebo

This is the standard way to make an elevator in Dark Mod. There is a prefab you can just insert into your map that works immediately or can be modified so nothing further to do. This article explains how it works if you want to modify it or set up your own:

This method comprises:

  • An elevator entity
  • Entities which mark the place it will stop at each floor (as many floors as you want)
  • Buttons to send it to those markers.
  • Entities and brushes that control pathfinding.

The path cannot curve or rotate between stop positions but it can be vertical, horizontal, or any slope so this method will cover 99.9% of any kind of elevator you are likely to need and is really easy. Describing it might make it seem complicated but really just look at the image and that's all it is.

The exact same method can be used to make say, an ammunition/cargo hoist or dumb waiter (a small lift for transferring items, eg, from kitchen to dining room). So size and shape purely depends on application.

AI useage:

  • AI will use an elevator if it is the quickest way to their destination; they can also interrupt the player and bring the elevator back if a call button is available.
  • Pathfinding will not be created for areas unless AI can also reach them by non-elevator means. Therefore it is important to include aas flood entities in such areas to force pathfinding to be created. These are listed under AI > AAS_flood. There are different types for different AI but aas32_flood is the one to use for humanoid AI (I doubt any other types can use elevators.) Just to clarify: the aas32_flood entity is only needed on floors where there are no other AI and which are only reachable by elevators.


Summary:

  • At each elevator stop position on each floor:
    • Create a brush platform and texture it with textures/editor/aassolid. This remains worldspawn.
    • Clone those brush platforms, resize slightly smaller and make these func_aas_obstacle entities. Add spawnarg start_on with a value of 0 for where the elevator will start and 1 for the others.
    • Create entities atdm:mover_multistate_position. Add spawnarg position with a unique name for each floor.
  • Create an elevator out of brushes or use a model. Convert this to an atdm:mover_elevator
  • Let the elevator target all of the above position entities.
  • Speed is controlled with the property on the elevator move_speed in units/sec.
  • Create the buttons, either models or brushes as follows:
    • You want either a complete set on each floor reachable from the elevator or alternatively, one complete set on the elevator plus a single 'fetch' button' on each floor.
    • convert to atdm:mover_elevator_button
    • Let all buttons target the elevator.
    • Add spawnarg position to each button with position names as in the stop positions above.
    • Buttons that are on the elevator should:
      • be fixed with the bind property and the name of the elevator.
      • have the property/value: ride 1 (this gives it priority over fetch buttons to avoid conflict between different AI or player)
    • Buttons that are on the floors should:
      • have the property/value: fetch 1


Details :

  • ELEVATOR : Check the model list to see if there is an elevator or create one from brushes. If using brushes, convert them to a func_static.
  • Change its classname to atdm:mover_elevator.
  • STOP POSITIONS : Create the stop position markers. Use the atdm:mover_multistate_position entities for that. Suggest you give each a suitable entity name eg, Lift1Pos1, Lift1Pos2, etc. Place one entity per position/floor and add the position spawnarg to each with a unique name for each floor. (These position names need only be unique within this elevator system. Other elevator systems in this same mission can use the same names if you want.) Examples:
    • position Basement
    • position Floor1
    • position Floor2
  • ALIGNING: The elevator will stop at these positions so a tip to line them up nicely:
    • Position the elevator on one floor exactly where you want it for that floor (suggest leave its start floor till last)
    • Place the atdm:mover_multistate_position so its origin is at the same origin as the elevator (maybe copy and paste the origin from the elevator)
    • Move the elevator to the next floor and line it up nicely. If this is a normal vertical elevator then use Alt + Up/Down Arrows to move it in a vertical line.
    • Repeat for next atdm:mover_multistate_position and so on but suggest do the elevator's start floor last so you can leave it in that start position
    • Add to the elevator properties to target ALL of these position entities, (the entity names of course) eg,
      • target1 Lift1Pos1
      • target2 Lift1Pos2
      • target3 Lift1Pos3
  • PATH CONTROL: At each elevator stop position on each floor:
    • Create a brush platform and texture it with textures/editor/aassolid. This remains worldspawn.
    • Clone those brush platforms, resize slightly smaller and make these func_aas_obstacle entities. Add spawnarg start_on with a value of 0 for where the elevator will start and 1 for the others.
  • BUTTONS : Create one button to begin with using a model or brushes.
    • Position it on one of the floors normally so it can be reached both from elevator and from the floor.
    • Convert it to atdm:mover_elevator_button
    • Add the property target with the name of the elevator.
    • give it the property/value: fetch 1 (this prevents conflict - see later)
    • Clone it nearby so you have one to send the elevator to each floor, eg, 3 floors, make 3 buttons. Place them together on that first floor, perhaps on a back panel.
    • To each button add the property position with the name of the floor, which should correspond to the floor they should send or bring the elevator to eg,
      • position Basement
      • position Floor1
      • position Floor2
    • Select the complete set of buttons on that floor, clone them, and copy them to suitable positions on the other floors. Check they still target the elevator and not renamed eg, elevator2. Otherwise they should be good to go.
    • You might also clone a set and put it on the elevator itself, eg, on a post for a simple platform elevator. If so, then give them each the property/value: ride 1 (this gives them priority over fetch buttons to avoid conflict between different AI or player.) You will still normally need at least one fetch button on each floor as well of course.
    • If you need to disable any buttons (eg, derelict warehouse) just remove the target property on that button. This can be used occasionally as a story device to force the player to go to another floor first then use the button on that floor.
  • SPEED : You can adjust the speed if necessary by setting the property move_speed in world units per second.


Additional tips and suggestions:

1) If there's a gap between the edge of the elevator platform and the neighboring floor, it's possible for the AI to step into that gap and momentarily drop down. Avoid this by closing the gap.

2) Make sure the AI has room to stand in front of a fetch button. A button tucked into a corner or surrounded by other architecture that hinders the AI will prevent the AI from using the button if he needs to fetch the elevator.

Moving Parts

For flavour, you can make rotating gears or other mechanical things move when your elevator is in motion. Select the atdm:mover_elevator entity and target any func_rotating entity(s). This will cause that entity to rotate whenever the elevator is moving, and rotates it the opposite direction when the elevator changes direction.

Elevators can also trigger particles and sounds using the same method above. Select the atdm:mover_elevator entity and target the speaker or func_emitter. Both will play while the elevator is in motion and will stop when the elevator stops.


Two stage elevator using sliding door entity (outdated)

A quick and simple way to make a two-floor elevator is just to use the sliding door entity. AI won't be able to use such elevators, however. This method is now superseded by a custom Dark Mod elevator entity but for interest, see A - Z Beginner Full Guide Page 5#Elevators for details.


Three stage elevator using a script (outdated)

Originally written by Dram on http://forums.thedarkmod.com/topic/3865 Ok, assumed knowledge is:

These are covered in the how to make a platform move tutorial, so do that one first and everything here will be simpler to understand.

This elevator basically is like the ones in thief, in that you press a button to go up or down rather then a button to select which floor to go to.

Start

Ok, to start, make 3 floors, each with a panel that has buttons, and the elevator itself. The bottom floor needs one button, the middle floor needs two, and the top needs one. Make all the buttons into func_darkmod_button. For the bottom floor button we have these attributes:

image14ea.jpg

Those attributes are for the bottom button. For the other buttons the only difference is what script function it calls via the "state_change_callback" attribute. The following pic shows the name of each script function in relation to the buttons. Remember that these script function names can be changed to whatever you wish.

Names of script functions relative to buttons

Also make sure your elevator is at the bottom level. For this purpose anyway. Once you understand how the scripts work you'll be able to change where it starts yourself. Now change the elevator into a func_mover, if you have'nt already.

These are the settings on the elevator in this tute:

image39cc.jpg

To explain the settings:

  • name elevator - Name of the entity. Can be whatever you want, just remember the name.
  • accel_time 0.4 - Time it takes to accelerate to full speed. ).4 seconds for this one to tune in with the sound.
  • decel_time 0.4 - As above except for deceleration.
  • move_speed 30 - The speed at which the elevator travels in Doom units/second. Make sure you calculate how long it takes to get to each floor otherwise pressing the button when it has'nt completed it's movement will restart it and cause problems. Basically calculate it for the delay that will be mentioned later on.
  • dmg 1000 - Does 1000 damage to anything that obstructs it - DON'T STAND UNDER IT MWAHAHAHA.
  • snd_move test_elevator_loop - The sound to make while moving. Put your own sound here. The sound has to be a looping sound otherwise it'll play only once.
  • snd_accel test_elevator_stop - The sound to make when accelerating. Does not need to loop.
  • snd_decel test_elevator_stop - As above but for deceleration.

The Script

Now to make the script for it. Remember that you can have many script functions in one map script, just they all have to be unique.

Paste the following code into your map script:

float elevator_moving = 0;
float elevator_state = 0; // 0 is bottom, 1 is middle, 2 is top

void main()
{
}

// The Elevator

void elevator_bottom(entity door, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
  if(bOpen)
  {
     door.Close(1);
     if (elevator_moving == 0)
     {
        if (elevator_state == 1)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( DOWN, 248 );
          sys.wait(9);
          elevator_state = 0;
          elevator_moving = 0;
        }
        else if (elevator_state == 2)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( DOWN, 472 );
          sys.wait(19);
          elevator_state = 0;
          elevator_moving = 0;
        }
        else if (elevator_state == 0)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( UP, 248 );
          sys.wait(9);
          elevator_state = 1;
          elevator_moving = 0;
        }
     }
  }
}

void elevator_middle_a(entity door, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
  if(bOpen)
  {
     door.Close(1);
     if (elevator_moving == 0)
     {
        if (elevator_state == 1)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( DOWN, 248 );
          sys.wait(9);
          elevator_state = 0;
          elevator_moving = 0;
        }
        else if (elevator_state == 2)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( DOWN, 224 );
          sys.wait(9);
          elevator_state = 1;
          elevator_moving = 0;
        }
        else if (elevator_state == 0)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( UP, 248 );
          sys.wait(9);
          elevator_state = 1;
          elevator_moving = 0;
        }
     }
  }
}

void elevator_middle_b(entity door, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
  if(bOpen)
  {
     door.Close(1);
     if (elevator_moving == 0)
     {
        if (elevator_state == 1)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( UP, 224 );
          sys.wait(9);
          elevator_state = 2;
          elevator_moving = 0;
        }
        else if (elevator_state == 2)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( DOWN, 224 );
          sys.wait(9);
          elevator_state = 1;
          elevator_moving = 0;
        }
        else if (elevator_state == 0)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( UP, 248 );
          sys.wait(9);
          elevator_state = 1;
          elevator_moving = 0;
        }
     }
  }
}

void elevator_top(entity door, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
  if(bOpen)
  {
     door.Close(1);
     if (elevator_moving == 0)
     {
        if (elevator_state == 1)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( UP, 224 );
          sys.wait(9);
          elevator_state = 2;
          elevator_moving = 0;
        }
        else if (elevator_state == 2)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( DOWN, 224 );
          sys.wait(9);
          elevator_state = 1;
          elevator_moving = 0;
        }
        else if (elevator_state == 0)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( UP, 472 );
          sys.wait(19);
          elevator_state = 2;
          elevator_moving = 0;
        }
     }
  }
}

Notice that there are four functions - elevator_top, elevator_middle_a, elevator_middle_b, and elevator_bottom.

These are the functions called on by the state_change_callback mentioned earlier.

Now to look at the code in a bit more detail. Things in bold are comments to help you understand wtf it is.

The Code

float elevator_moving = 0; // this variable is at 0 if the elevator is NOT moving
float elevator_state = 0;  // 0 is bottom, 1 is middle, 2 is top 
                           //this variable is used to show which floor the elevator is currently on
void main() as mentioned in the platform tute, this has to be in a map script, but does'nt need to do anything
{
}

The Elevator

Bottom Floor Button.

When the elevator is on this floor and you press this button, the elevator goes up one floor

void elevator_bottom(entity door, boolean bOpen, boolean bLocked, boolean bInterrupted) 
{
  if(bOpen)  // do the following if the button is open (pressed)
  {
    door.Close(1);  // close the button (push it back out). This makes it act like a button
    if (elevator_moving == 0)  // if the elevator is NOT moving"
    {
       if (elevator_state == 1) // if the variable is on 1 then the elevator is on the middle floor (0 is bottom floor)
       {
          elevator_moving = 1;  // set this variable to 1 so that the code knows that the elevator is moving and not to restart the sequence
          sys.wait(2);          // 2 second delay before movement to give the user time to get on the elevator
          $elevator.move ( DOWN, 248 ); // where it says elevator in $elevator.move is the name of your elevator entity.

So if yours is called func_mover_1 then put that in there instead of elevator. As for the movement, this was described in the platform tute, but to summarise, the DOWN is the direction to move, and the 248 is the distance to move in Doom Units. The platform tute explains how to find this distance etc.

          sys.wait(9); 

this delay is here because for this particular elevator it takes 9 seconds to move from the middle floor to the bottom floor. Set this to how long your one takes to reach the bottom floor.

          elevator_state = 0;  // after the elevator reaches the bottom floor set this variable to reflect that
          elevator_moving = 0; // our elevator has stopped moving, so set this variable to reflect that
       }
       else if (elevator_state == 2)  // if the variable is on 2 then the elevator is on the top floor

I think you can figure the rest out for yourself from here. It's all the same pretty much except the variables change for each floor and the distance to move does too

       {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( DOWN, 472 ); // distance to move from top floor to bottom floor
          sys.wait(19);                 // the time it takes for the elevator to reach the bottom floor from the top floor
          elevator_state = 0;
          elevator_moving = 0;
       }
       else if (elevator_state == 0)    // if the variable is on 0 then the elevator is on the bottom floor"
       {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( UP, 248 ); 

we're at the bottom floor so make the elevator go up one floor instead of further down. The distance here is from bottom floor to middle floor

          sys.wait(9);                  // time it takes to get from bottom to middle floor
          elevator_state = 1;
          elevator_moving = 0;
       }
     }
   }
 }

The rest of these functions are like the above one, except they're relevant to each floor

Middle Floor Button

The bottom one in this case. When the elevator is on this floor and you press this button, the elevator goes down one floor

void elevator_middle_a(entity door, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
  if(bOpen)
  {
     door.Close(1);
     if (elevator_moving == 0)
     {
        if (elevator_state == 1)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( DOWN, 248 );
          sys.wait(9);
          elevator_state = 0;
          elevator_moving = 0;
        }
        else if (elevator_state == 2)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( DOWN, 224 );
          sys.wait(9);
          elevator_state = 1;
          elevator_moving = 0;
        }
        else if (elevator_state == 0)
        {
          elevator_moving = 1;
          sys.wait(2);
          $elevator.move ( UP, 248 );
          sys.wait(9);
          elevator_state = 1;
          elevator_moving = 0;
        }
      }
    }
  }

Middle Floor Button. The top one in this case.

When the elevator is on this floor and you press this button, the elevator goes up one floor. Note that the code is very similar to the other button on this floor, except that this one makes the elevator go up rather then down, hence the need for two buttons on the middle floor

void elevator_middle_b(entity door, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
 if(bOpen)
 {
   door.Close(1);
   if (elevator_moving == 0)
   {
     if (elevator_state == 1)
     {
       elevator_moving = 1;
       sys.wait(2);
       $elevator.move ( UP, 224 );
       sys.wait(9);
       elevator_state = 2;
       elevator_moving = 0;
     }
     else if (elevator_state == 2)
     {
       elevator_moving = 1;
       sys.wait(2);
       $elevator.move ( DOWN, 224 );
       sys.wait(9);
       elevator_state = 1;
       elevator_moving = 0;
     }
     else if (elevator_state == 0)
     {
       elevator_moving = 1;
       sys.wait(2);
       $elevator.move ( UP, 248 );
       sys.wait(9);
       elevator_state = 1;
       elevator_moving = 0;
     }
   }
 }
}

Top Floor Button

When the elevator is on this floor and you press this button, the elevator goes down one floor

void elevator_top(entity door, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
 if(bOpen)
  {
  door.Close(1);
  if (elevator_moving == 0)
  {
    if (elevator_state == 1)
    {
       elevator_moving = 1;
       sys.wait(2);
       $elevator.move ( UP, 224 );
       sys.wait(9);
       elevator_state = 2;
       elevator_moving = 0;
    }
    else if (elevator_state == 2)
    {
       elevator_moving = 1;
       sys.wait(2);
       $elevator.move ( DOWN, 224 );
       sys.wait(9);
       elevator_state = 1;
       elevator_moving = 0;
    }
    else if (elevator_state == 0)
    {
       elevator_moving = 1;
       sys.wait(2);
       $elevator.move ( UP, 472 );
       sys.wait(19);
       elevator_state = 2;
       elevator_moving = 0;
    }
  }
 }
}

If you've done the platform tute then you'll understand that code a bit better, so you should be able to tell what to change and where.

That covers elevators. If you understand this code now then you'll see where you need to add what sop that you can have an elevator that goes to multiple floors, not just three. Basically you'd need to add more functions. 2 functions per floor except the top and bottom floor, which have one.

Disabling/Enabling an Elevator

Starting with v2.05, if you want to temporarily cut power to an elevator (which makes it both unusable for the player and for AI), you can set the spawnarg "enabled" to "0" on the elevator.

To restore power, set the "enabled" spawnarg to "1".

Setting the spawnarg is done via a script.