Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785

Warning: Division by zero in /home/g/gronnevik/gronnevik/gronnevik.se/www/rjukan/index.php on line 785
The Rjukan Project - Main - Elevator

Menu

Wiki usage

Creative Commons License
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 License.
Edit Sidebar
Main > Elevator

Elevators, with complex behaviour

This tutorial will attempt to teach by example how to create elevators. This requires a good deal of scripting, but do not fear, I will be holding your hand all along the way.

On this page...

Introduction

As a fequent visit to the .MAP forum, I noticed that a lot of new mappers asked about why their elevators behaved strangely. I also noticed that elevators behave strangely not only for a lot of people, but also in a lot of different ways. This is not really so surprising, as elevators include so many different aspects of mapping and scripting for a task that at first may seem very simple.

And it is simple... once you banged your head into the wall, torn out most of your hair, cursed and shouted "Oh why!!!!" to the skies - twice.

So to decrease the loss of hair and the general level of shouting in the world: I decided to make this tutorial. Lets go!

What is an elevator, anyway?

This is my definition of an elevator. It may not be your definition, but it us the definition this tutorial is based on.

An elevator...

The elevator cab

OK, lets get this show on the road. First we build a little elevator and maby an elevator shaft and doors into the elevator shaft and some stuff. At this stage my elevator looks like the pics with the closed doors. Everything is just brushes and models at this point.

Image 1 & 2: The elevator with opened and closed doors

Now select all the brushes that make up the elevator ( the stuff that should travel up and down ) and turn it into a script_object ( right click on the map grid and select script->object ). Remember, only brushes can be used in this step... if you want to include models: go to the next section. Now give the script_object a well named targetname ( with the object selected, press 'n' and enter the key-value pairs at the bottom of the dialog ) .

Example of a targetname for the elevator:

KeyValueExplanation
targetnamelift_cabThe elevator cab that should move between floors.

Using static models in an elevator

As you can see in the pictures I am using some static models as the visible switches inside and outside the elevator ( animate_equipment_electric-switch-nopulse to be exact ). This can be done but as they can not be included in the script_object that makes up the elevator cart, it will require some extra scripting to work like we want it. More about that in the Binding section below. If you want to use animations in a model, you must give them separate targetnames to be able to access them from the script.

Example of targetnames for the three models:

KeyValueExplanation
targetnamelift_switchupThe switch model at the upper floor.
targetnamelift_switchdownThe switch model at the lower floor.
targetnamelift_inswitchThe switch model inside the elevator.

Triggers

For an elevator that moves between two floors, we need three triggers. One inside the elevator, and one each outside on both floors. So place a trigger use brush over all the switches ( with nothing selected, right click on the map grid and select trigger->use ). Place them and drag them to a good size. Something like this:

Image 3: A well placed trigger

Give it some well named targetnames ( with the object selected, press 'n' and enter the key-value pairs at the bottom of the dialog ) .

Example of targetnames for the three triggers:

KeyValueExplanation
targetnameupper_lift_triggerThe call trigger at the upper floor.
targetnamelower_lift_triggerThe call trigger at the lower floor.
targetnamelift_triggerThe trigger inside the elevator.

Waypoints

There are two basic ways to move stuff from a script.

Move it to the location of a waypoint object. Move it in a direction. We are going to use both these ways in different places. Waypoints for the movement of the elevator cart, and directional commands for the elevator doors.

For the movement of the elevator we will use waypoints. Place one waypoint each at the top and bottom of the elevator shaft. Place them at the exact center of the elevator cab as it would be if located at the top or bottom position. Give the two waypoints the following key/value pairs(one each):

KeyValue
targetnamewaypoint_up
targetnamewaypoint_down

( we need these to have targetnames as we want to send the elevator cap to them via scripting )

Make sure the waypoints are exactly at the same X and Y position and only differs in the Z axis ( if you fail to do this: you may get a jerky motion of the elevator cab ).

Bind stuff to the elevator cab

As I told you above: if you want to use models in the elevator cab: you must bind them to the elevator cab before you start using it. If you take a look at the block of code with the label "elevator_preparation" in the script below, you will see some rows like these:

 $lift_inswitch bind $lift_cab
 $lift_trigger bind $lift_cab

...that is haow you keep the entities with the elevator cab as it moves. In short it works like this:

 <ENTITY_TO_BIND> bind <ENTITY_TO_BIND_TO>

This means that after that line is executed: all movements done to the <ENTITY_TO_BIND_TO> will also be done to <ENTITY_TO_BIND>. For this to be possible: both the entity you want to bind, and the entity you want to bind to, must have targetnames. If they don't: you can't access them from the script.

The scripting

This is the complex part. This is where you may run into trouble. Mostly because the mapping part is just about creating a few building blocks... once you start the scripting you will want to give the elevator complex behaviour. The elevator in this tutorial has the following complex ( complex here means more comlex than strictly nessesary for it to work ) behavoiur:

So lets go over these behaviours one by one:

Knowing when the elevator is busy

This is not done by some magical part of the MOH:AA core code. We have to keep track of it all by ourselves.

In the function "elevator_preparation", there is this little line of code:

 level.elepos = 0 // 0=down  1=up  -1=moving

...it creates a variable in the level object, named "elepos" and sets its value to 0. As I explain in the comment: 0=down 1=up -1=moving.

So now we can use this variable to communicate the state of the elevator thoughout the script. If you browse around the script you will notice that the mover parts of the script set the value of "level.elepos" to the appropriate value, and the trigger parts of the script queries the variable to find out what the elevator is up to.

Don't ride until the doors have closed

This is done with a single command and some logic: By executing the waitmove command after the move commands to the elevator doors: we make sure that the following part of the code is not executed until the doors have reached their position. And as the actual movement commands of the elevator is issued aftr this point on both the "lift_move_down" and "lift_move_up" methods, the desired behaviour is obtained.

Manually control of areaportals

As we are controlling the elevator doors manually instead of making them func_door entities, we must control the areaportals manually. I won't go in to this in detail here, instead read the Areaportals tutorial for an insight on how to control them from the script and how they work in the first place.

Animate switches according to operation.

The animate_equipment_electric-switch-nopulse has four animations:

AnimationExplanation
idleThe standard neutral inactive animation for all models.
turnTurns the switch around.
onSets the switch in the ON position.
offSets the switch in the OFF position.

All the animation is controlled from the "activate_all_switches" and "deactivate_all_switches" methods.

Make appropriate sounds

Sound selection

The best is to find a sound in the standard MOH:AA installation to use. That way you don't have to ship big wav files with your map.

Aliascache modification

To get the sounds you want to use work in a multi player map, I use the method described by jv_map in this tutorial TODO: INSERT_LINK_HERE_BONEHEAD. Doing so results in the very long script rows statting with "local.master" at the beginning of the "main" method.

Sound calibration

To get the sounds to fit with the moving parts of the elevator, we set the time property of the moving parts to fit the length of the sounds...

 $lift_cab time 4

...sets the time taken for all move commands to this objec to four seconds.


 main:
   // set scoreboard messages
   setcvar "g_obj_alliedtext1" "Tutorial map to show"
   setcvar "g_obj_alliedtext2" "how to script a usable"
   setcvar "g_obj_alliedtext3" "Elevator."
   setcvar "g_obj_axistext1"   "Script by Bjarne"
   setcvar "g_obj_axistext2"   "of The Rjukan Project"
   setcvar "g_obj_axistext3"   "Have fun, play fair!"
   setcvar "g_scoreboardpic" "none"

   local.master = spawn ScriptMaster
   local.master aliascache snd_binoculars sound/mechanics/Mec_RadioNoise_11.wav soundparms 0.5 0.2 0.9 0.1 160 1600 item loaded maps "dm obj moh"
   local.master aliascache elevator_gate sound/mechanics/Mec_ElevatorGate_03.wav soundparms 1.5 0.0 1.0 0.0 300 3000 auto loaded maps "dm obj moh"
   local.master aliascache elevator_start sound/null.wav soundparms 1.5 0.0 1.0 0.0 800 3000 auto loaded maps "dm obj moh"
   local.master aliascache elevator_run sound/mechanics/Mec_ElevatorRun_01.wav soundparms 1.5 0.0 1.0 0.0 800 3000 auto loaded maps "dm obj moh"
   local.master aliascache elevator_stop sound/null.wav soundparms 1.5 0.0 1.0 0.0 800 3000 auto loaded maps "dm obj moh"
   local.master aliascache alarm_switch sound/mechanics/Mec_Switch_05.wav soundparms 1.5 0.0 1.0 0.0 800 3000 auto loaded maps "dm obj moh"
   local.master aliascache snd_papers sound/items/Snd_Papers_show.wav soundparms 0.9 0.2 1.0 0.0 160 1600 item loaded maps "dm obj moh"
   local.master aliascache med_kit sound/items/Health_MedKit_01.wav soundparms 1.0 0.0 1.0 0.0 160 1600 item loaded maps "dm obj moh"
   local.master aliascache med_canteen sound/items/Health_Canteen_01.wav soundparms 1.0 0.0 1.0 0.0 160 1600 item loaded maps "dm obj moh"

   level waitTill prespawn

   exec global/DMprecache.scr
   level.script = maps/obj/elevator_ripped_out.scr
   exec global/ambient.scr canal

   level waittill spawn

   thread elevator_preparation

   level waittill roundstart
 end

 elevator_preparation:
   iprintln "elevator_preparation starting..."
   //*********  BIND  ***************
   $lift_inswitch bind $lift_cab
   $lift_trigger bind $lift_cab
   $lift_inswitch anim off
   //*********** TRIGGERS ***************
   $upper_lift_trigger nottriggerable
   $lower_lift_trigger nottriggerable
   $lift_trigger nottriggerable
   //***********   Doors   ***************
   $lower_lift_door_right openportal // Opens the areaportal and pierces the area
   $lower_lift_door_right playsound elevator_gate
   $lower_lift_door_right moveBackward 56
   $lower_lift_door_left moveForward 56
   $lower_lift_door_right move
   $lower_lift_door_left waitmove
   level.elepos = 0 // 0=down  1=up  -1=moving

   thread activate_lift_sensors
   iprintln "...elevator_preparation done"
 end

 activate_lift_sensors:
   iprintln "activate_lift_sensors starting..."
   thread ride_switch_sensor
   thread lower_call_sensor
   thread upper_call_sensor
   iprintln "...activate_lift_sensors done"
 end

 ride_switch_sensor:
   iprintln "ride_switch_sensor starting..."
   $lift_trigger triggerable
   while(true) {
     $lift_trigger waittill trigger
     if(level.elepos == 1) {
       waitthread lift_move_down parm.other
     } else {
       if(level.elepos == 0) {
         waitthread lift_move_up parm.other
       } else {
         parm.other iprint "Elevator is busy" 0
       }
     }
     wait 1
   }

   iprintln "...ride_switch_sensor done"
 end

 lower_call_sensor:
   iprintln "lower_call_sensor starting..."
   $lower_lift_trigger triggerable
   while(true) {
     $lower_lift_trigger waittill trigger
     if(level.elepos == 1) {
       thread lift_move_down parm.other
     } else {
       if(level.elepos == 0) {
         parm.other iprint "Elevator is here" 0
       } else {
         parm.other iprint "Elevator is busy" 0
       }
     }
     wait 1
   }
   iprintln "...lower_call_sensor done"
 end

 upper_call_sensor:
 iprintln "upper_call_sensor starting..."
 $upper_lift_trigger triggerable
 while(true) {
 	$upper_lift_trigger waitTill trigger
 	if(level.elepos == 0) {
 		thread lift_move_up parm.other
 	} else {
 		if(level.elepos == 1) {
 			parm.other iprint "Elevator is here" 0
 		} else {
 			parm.other iprint "Elevator is busy" 0
 		}
 	}
 	wait 1
 }
 iprintln "...upper_call_sensor done"
 end

 //************   LIFT MOVE UP ************
 lift_move_up local.user:
   iprintln "lift_move_up starting..."
   level.elepos = -1 // 0=down  1=up  -1=moving
   waitthread activate_all_switches
   // CLOSE LOWER DOORS
   $lower_lift_door_right playsound elevator_gate
   $lower_lift_door_right moveForward 56
   $lower_lift_door_left moveBackward 56
   $lower_lift_door_right move
   $lower_lift_door_left waitmove
   $lower_lift_door_right closeportal
   wait 1
   // MOVE ELEVATOR UP
   $lift_cab playsound elevator_start
   $lift_cab moveto $waypoint_up
   $lift_cab playsound elevator_run
   $lift_cab waitmove
   $lift_cab playsound elevator_stop
   waitthread deactivate_all_switches
   // OPEN UPPER DOORS
   wait 1
   $upper_lift_door_right openportal
   $upper_lift_door_right playsound elevator_gate
   $upper_lift_door_right moveBackward 56
   $upper_lift_door_left moveForward 56
   $upper_lift_door_right move
   $upper_lift_door_left waitmove
   level.elepos = 1 // 0=down  1=up  -1=moving
   iprintln "...lift_move_up done"
 end

 //***********   LIFT MOVE DOWN ************
 lift_move_down local.user:
   iprintln "lift_move_down starting..."
   level.elepos = -1 // 0=down  1=up  -1=moving
   waitthread activate_all_switches
   // CLOSE UPPER DOORS
   $upper_lift_door_right playsound elevator_gate
   $upper_lift_door_right moveForward 56
   $upper_lift_door_left moveBackward 56
   $upper_lift_door_right move
   $upper_lift_door_left waitmove
   $upper_lift_door_right closeportal
   wait 1
   // MOVE ELEVATOR DOWN
   $lift_cab playsound elevator_start
   $lift_cab moveto $waypoint_down
   $lift_cab playsound elevator_run
   $lift_cab waitmove
   $lift_cab playsound elevator_stop
   waitthread deactivate_all_switches
   // OPEN LOWER DOORS
   wait 1
   $lower_lift_door_right openportal
   $lower_lift_door_right playsound elevator_gate
   $lower_lift_door_right moveBackward 56
   $lower_lift_door_right move
   $lower_lift_door_left moveForward 56
   $lower_lift_door_left waitmove
   level.elepos = 0 // 0=down  1=up  -1=moving
   iprintln "...lift_move_down done"
 end

 activate_all_switches:
   iprintln "activate_all_swithches starting..."
   // ACTIVATE SWITCH IN ELEVATOR
   $lift_inswitch playsound alarm_switch
   $lift_inswitch anim turn
   // ACTIVATE UPPER SWITCH
   $lift_switchup playsound alarm_switch
   $lift_switchup anim turn
   // ACTIVATE LOVER SWITCH
   $lift_switchdown playsound alarm_switch
   $lift_switchdown anim turn
   wait 1
   $lift_inswitch anim on
   $lift_switchup anim on
   $lift_switchdown anim on
   iprintln "...activate_all_swithches done"
 end

 deactivate_all_switches:
   iprintln "deactivate_all_swithches starting..."
   // DE-ACTIVATE ALL SWITCHES
   $lift_inswitch anim off
   $lift_switchup anim off
   $lift_switchdown anim off
   iprintln "...deactivate_all_swithches done"
 end

Example file

This is the MAP, BSP and SCR of this tutorial: Attach:elevator_ripped_out.zip

Goodbye

Hope this helped. If not: maby you should start with the easier AlternativeElevator by tltrude.

Cheers -Bjarne Grönnevik

Recent Changes Printable View Page History Edit Page [Attributes] [Printable View] [WikiHelp]
Page last modified on January 31, 2005, at 06:59 PM