Qualmis Mapping Box - 1

Home
News
=> 1
Maps



 

Mapscripting for W:ET v0.88

This document will provide you with a step by step guide on creating mapscripts refering to custom mapfiles for Wolfenstein: ET.


Part 1:

Content of Part 1:
  1. basic explanations about (map)scripting



Purpose of Mapscripts

Wolfenstein Entity Scripting, or simply Mapscripting, gives you the ability to dynamically control the behaviour of entities existing in your map.

Example: you can move entities a long a certain path f.i. a tank, you can let planes fly, end maps according to a wall which has been dynamited, set spawnpoints according to each different scenario etc..

Basically a script is responsible for three things:
  1. game settings such as maptime
  2. controling the behaviour of entites such as flying planes
  3. setting cvars
But point 3 is not very often used. Probably 1 out of 100 maps is setting a server cvar from within the mapscript.

Additional Information Before Getting Started

Editor:

Script files can be written with every editor supporting the ASCII charset.

Entities:

..are gameobjects with a lot of attributes and stuff. They forfill a certain task. For mapping/scripting we need to assign in most cases only one of these values in radiant to an entity: scriptname, targetname, target.

But they are quite a big thing. You can consider the whole Q3 world consisting out of them.

First Mapscript

In order to run a scriptfile you need to:
  1. place it in etmain/maps
  2. rename it to nameOfTheMap.script (f.i. if the mapname is mymap.bsp, then the name of the script-file must be mymap.script)
  3. fill in some code into that script (will be covered later)
  4. place a script_multiplayer in your map (see Appendix A)
  5. assign a key-pair to that entity (see Appendix B)
That key-pair is the following:
 
key: scriptname
value: game_manager

The code for step 3 could look like this:

CODE:

game_manager
{
    spawn
    {
        wait 500     
        wm_allied_respawntime 1
        wm_axis_respawntime 1
        wm_setwinner -1 
        wm_set_round_timelimit 10000
    }
}


Remember these 5 steps always when you want to write a new mapscript for a new map. Basically a script_multiplayer shouldnt miss in any of your maps. It has to be always one, not more.


Part 2:

Content of Part 2:
  1. Structure of Mapscripts
  2. How scripts get entered



Routines

Every ET Mapscript consists out of numerous, so called, routines. A routine is nothing but a block inside the script with a special name, an opening bracket, and a closing bracket, which both define the borders of all content which could be placed in it.

An example:

CODE:
NameOfRoutine
{


Entities, particularly those you insert into your map with Radiant, in general are able to communicate with the script. The crux is that every entity can have its own routine, which in case an event is happening to the entity, gets searched for a point of reference inside the script. Hence a routine inside the script is a reference to an entity inside the map.

Now every entity which is able to communicate with the script must be specially prepared. The key thing to prepare an entity is to assign a scriptname key-pair in Radiant to it. The value of that scriptname assignment defines the NameOfRoutine. If there is something faulty with that, the routine will not be considered as belonging to that entity.

Assigning a scriptname key-pair works the same as in Appendix B.

Subroutines

Inside every one of these routines there can be subroutines. They get defined the same way as routines, that means we mark the start of the subroutine by a name, then follow it by an opening bracket, and define the end of the subroutine again by a closing bracket:

CODE:
NameOfRoutineX
{

    NameOfSubroutine
    {
    }



Subroutines are special. First they are those who get filled with commands lateron. They are the only ones which really contain code. Code outside subroutines will cause errors. Also a subroutine, in its nature and what its name might let us guess, always have to be inside routines. A subroutine beeing alone, somewhere in nirvana of the script, that means everywhere but not inside a routine, will cause an error. Subroutines inside subroutines will also not work.

Secondly a subroutine can be connected to an event. Thats probably the most important you have to understand. Every entity has its own collection of events and those subroutines, every single one of them, handle one of these events. That specifically means that when an event is happening for an entity, the entities routine gets searched for a subroutine, which is responsible for that event. When the subroutine is available it gets entered and the code gets executed in there. For a list of events see Appendix D.

Thirdly when a subroutine has no real connection to an event, then its introduced by the keyword "trigger" and in most cases gets used to define subroutines on your own. Take a look at Appendix C for more information on that when you come to the examples section. Because they are not really important for us for now.

At last you have to make yourself clear that the name of a subroutine beeing connected to an event is as fixed as a nail beeing stomped into a wall. That means you cant change the name.

Summing Example (theoretical)

Lets summarize this knowledge on routines and subroutines, and show a script, which gives you an insight of how it might look.

For that example i have three entities in my map:

1: script_multiplayer
2: trigger_multiple
3: func_explosive

A script could look like this: no commands are shown, only the rough sketch should be shown

CODE:

game_manager
{
    spawn
    {
    }
}
my_trigger_multiple
{
    spawn
    {
    }
    activate
    {
    }
}
my_func_explosive
{
    spawn
    {
    }
    death
    {
    }
}


So you see there...

Routines:
  • game_manager
  • my_trigger_multiple
  • my_func_explosive
Subroutines:
  • spawn
  • death
  • activate
So lets just imagine we are a player and are currently playing on our custom map at a random server. We assume everything in the map and the mapscript is prepared and working like we expect. Ill explain the happenings chronologically.

At mapstart what happens is that the spawn subroutine of every entity gets executed. This is a standard process beeing performed by the engine at every mapstart.

Inside these spawn subroutines you always have to make sure that you set a wait statement of at least 200 milliseconds as a very first statement/command. Otherwise you might run into problems i wont explain for now. Just use google or, if you dont find useful informations, visit a forum, ask and hope for friendly people . But following this rule blindly for the beginning should be sufficient. How to set the wait-statement will be covered later.

Then, when the spawn subroutine of every entity has been executed, the mapscript will end its execution and wait at server memory for beeing demanded, that means when another event is happening to one of the entities.

Trigger Multiple:

Then lets assume we spawn, run around in the map and run into the trigger_multiple. If you dont know what a trigger_multiple is try use google first, then ask in a forum. But basically its an inivisble entity, which defines an area that the moment it gets touched by a player, will activate itself. Therefore that entity has an event called activate. So when you run into the trigger_multiple it will go inside its routine and inside there search for the subroutine, beeing responsbile for the event. As this event has been a simple activation you might already guess that the subroutine beeing searched for also has the name "activate". Once reached the subroutine, the procedure on "how it goes on" will be the same as with every other entity. We will cover that (how to organize ourself once we are inside the script) in the next paragraph. So whenever we run into the trigger_multiple it will call the script and inside there reach a subroutine called activate.

Func Explosive:

Then, while having enough of permanently running into our trigger_multiple and think its cool, we go to our func_explosive entity, which hopefully is dynamiteable (you set this in the spawn event of that entity, its a special command, more on this later), and blow it into pieces. You might already guess that "blowing an entity" will also cause an event beeing triggered inside our script. This time its the death subroutine of our my_func_explosive entity. Once reached the subroutine, we could perform an endgame or something else we wanna do. We could also perform an endgame at the trigger_multiple of course.

So:
  1. while the map is beeing played, the script remains silently at server memory most of the time
  2. events will trigger some subroutines inside the script
  3. entities define the routine beeing searched for the subroutine
The whole scripting language is working like that. The subroutines are the places which get entered, and these places get defined by the event type, entity type and the scriptname key-pair it has.

Game Manager:

I almost forgot this one. This is the entity which by the inventors of this game mostly got used to set game settings like the spawntime, maptime etc.. We will follow this convention as well in our next examples.

Take a look at the Examples Section: Example 1, if you want to build up a fully working example. It covers everything you must know until here, so its also a good way to control yourself.


Part 3:

Content of Part 3:
  1. organizing the execution of scripts



Comments

Before we start this last theoretical part a short explanation on how to make your script more readable: comments. I never use them to be honest, but if you want to make notes inside your script or make it understandable for another fellow mapper you are given the opportunity to insert a text at a random location in your script, and write in some explanations there.

A comment gets marked in two ways. One tells the interpreter (this is the thingy which transfers your scripting commands into binary during runtime) to ignore this text until the end of the line is reached.

CODE:
//this is a comment across 1 line
game_manager
{
    //here another comment
    spawn
    {
    }
}


The other one tells our fellowed interpreter to ignore even multiple lines

CODE:
/*
this is
a comment across
multiple lines
*/

//you can also write across
//multiple lines by just linewise
//inserting a comment like this

game_manager
{
    spawn
    {
    }
}


The key thing is to either write these double slashes in front of something so that the whole line gets ignored, or write a comment across multiple lines by writing something in between /* [TEXT] */.

The Trigger Instruction

Subroutines normally get executed from top to down: warning: that script wont work because the commands are no real commands. They are for demonstration.

CODE:
my_trigger_multiple
{
    spawn
    {
         command1
         command2
         .
         .
         .
         commandN
    }
    activate
    {
         commandN+1
         commandN+2
         .
         .
         .
         commandZ
    }
}


When the spawn block of the routine above gets executed then the commands get executed one after the other, starting from the very first command after the opening bracket, like this:


command1
command2
.
.
.
commandN


The same goes with the activate subroutine:


commandN+1
commandN+2
.
.
.
commandZ


So without the possibility to navigate into other subroutines, a script cycle would look like this:
  1. entering the script (if available)
  2. entering the routine (if available)
  3. entering the subroutine (if available)
  4. executing the subroutine from top to down and leaving the mapscript
Of course the inventors of this scripting language gave their mappers the possibility to execute subroutines of their choice, and therefore brake this top-down-method. This is what the trigger instruction is about.

Syntactically it is looking like this:

CODE:
 trigger <routine> <subroutine>


So lets say we want the script to execute the spawn subroutine of the my_trigger_mulitple routine, whenever its activate subroutine is executed. This should look like this:

CODE:
my_trigger_multiple
{
    spawn
    {
         command1
         command2
         .
         .
         .
         commandN
    }
    activate
    {
         commandN+1
         commandN+2
         .
         .
         .
         commandZ
         trigger my_trigger_multiple spawn
    }
}


Assuming the activate subroutine gets executed as the very first subroutine of the script cycle, the whole cycle should look like this now:


commandN+1
commandN+2
.
.
.
commandZ
command1 
command2
.
.
.
commandN


So whenever you feel there is need to execute a bunch of commands in another subroutine, then simply place a trigger instruction where you want it to be (it could be everywhere inside a subroutine) and whenever the script cycle reaches that command it will execute the other subroutine at first, and come back.

There are of course some limitations: when you point the trigger instruction to a subroutine, whose routine is not belonging to an entity, that means to a routine which has no entity inside the map, which has a scriptname assignment, then it wont work. It wont neccessarily give you an error, but it simply wont work.

Nice trick and what can be seen sometimes:

CODE:
my_trigger_multiple
{
    spawn
    {
         command1
         command2
         .
         .
         .
         commandN
    }
    activate
    {
         commandN+1
         commandN+2
         .
         .
         .
         commandZ
         trigger self spawn
    }
}


That self replaces the name of the routine we currently execute. So its always to be read and maybe implemented in context.

Accums

Accums are buffers that store integer values. Its about the same as in programming languages, where you have variables storing an integer. Ill try to explain this as simple as possible as newbies mostly get problems here.

So an accum is a buffer storing an integer value, like 3, 8, 10, 11188, -5, -7 ... Stuff like 5.123 is not allowed. Depending on the hardware which runs the server code an accum comprises every integer value which can be displayed by 32 bits in the binary system.

To give you a few examples where they are used you could for example open up the oasis.script of the stockmap oasis and in there try to find out how the map ends with the "wm_endround" command. Going into map happening this means when the second Anti-Tank-Gun is destroyed. You will find accums beeing responsible for that.

Going a bit more into detail: the accum there (it is only one) gets initialized by 0 at mapstart and carries that value as long as both guns are undestroyed. As soon as one of the two Anti-Tank-Guns gets destroyed the value of that accum will get increased by +1 and then another subroutine gets asked to finish the map, when the value is 2. As the value is only 1 at that time the request will get rejected, that means the subroutine wont get even entered, as there is a command in there blocking. When Tank-Gun2 is destroyed however, the map ends.

Another example is the map radar, where an accum gets used the same way. But also in CTF maps they are crucial and mostly everywhere where you want to count something, create a dependency or simply want to execute a subroutine, which should only be executed at a certain moment during the map. The methods to reach that are always the same: an accum gets initialized with a starting value and afterwards gets used for the task.

Initializing:

this tutorial is still beeing written.

Examples Section:

To be done..

Appendix A:

To insert a script_multiplayer just open Radiant, open your map and right click in top-view and choose the entity as shown:




Once inserted it should look like this:



Appendix B:

To assign a key-pair to an entity select it and press 'N' to open up the entity window. Do..

key: scriptname
value: game_manager

..as shown in the pic. In this example the key is scriptname, value is game_manager.



Hit 'enter' on your keyboard to confirm your settings.

Appendix C:

To be done..

Appendix D:

To be done..

Events

Complete list

activate
Something has activated this entity.

activate <axis|allies>
Some player has activated this entity.

buildstart [final|stage1|stage2|stage3]
Called when the construction of a stage of a multistage constructible has started.

built [final|stage1|stage2|stage3]
Called when the construction of a stage of a multistage constructible has finished.

death
Entity was killed.

decayed [final|stage1|stage2|stage3]
Called when the construction of a stage of a multistage constructible isn't completed after a while.

defused
Called when a stick of dynamite is defused inside a trigger_objective_info entity.
Note: The TOI must target a func_constructible, for this event to be called.

destroyed [final|stage2|stage3]
Called when a stage of a multistage constructible (this entity) is destroyed.

dynamited
Called when a stick of dynamite is armed inside a trigger_objective_info entity.
Note: The TOI must target a func_constructible, for this event to be called.

failed
Called when the construction isn't completed after a while.

mg42 mount
Called when a player mounts the gun of a script_mover.

mg42 unmount
Called when a player unmounts the gun of a script_mover.

message
Contains a sequence of VO in a message

pain
Something hurt the entity. Two integer values are also used as parameters, however there is no way to use them in the script. The first integer is how much health it had before the attack, and the second is the amount of health after the attack.

playerstart
Called on client start.

rebirth
Called when the alertentity command is called with this entity as the target. Its health is restored.

spawn
Called as each entity is spawned into the game.

stopcam
Its commented out in the source code.

trigger <identifier>
Functions can trigger userdefined events via the trigger command. The game also uses this form for event calls for certain entities.

Entities and the events they can trigger

This table contains a list entities and the events they can trigger. NOTE: This table is still not complete.

Entity Event Comment
func_constructible spawn  
builtstart  
built  
decayed  
destroyed ?
death Doesn't get called unless its targeted by a TOI and have construtctible_weaponclass set to 3
func_destructible spawn  
death  
func_explosive spawn  
pain  
brushes w/health
NOTE: Need a proper entity name
spawn  
pain  
death  
func_invisible_user
trigger_multiple
trigger_once
spawn  
activate  
activate allies  
activate axis  
script_mover spawn  
pain Only if it has a health key.
death Only if it has a health key.
rebirth Only when its alerted through a script using alertentity.
mg42 mount Only if it has a mounted gun.
mg42 unmount Only if it has a mounted gun.
script_multiplayer spawn  
trigger allied_object_dropped Called when an allied objective is dropped.
trigger allied_object_returned Called when an allied objective is returned.
trigger allied_object_stolen Called when an axis objective is stolen.
trigger axis_object_dropped Called when an axis objective is dropped.
trigger axis_object_returned Called when an axis objective is returned.
trigger axis_object_stolen Called when an axis objective is stolen.
trigger timelimit_hit Called when the round timelimit is hit.
NOTE: The timelimit is only used if a team has been declared the winner with wm_setwinner.
trigger_objective_info dynamited  
defused The TOI must target a func_constructible.
team_CTF_checkpoint trigger allied_capture  
trigger axis_capture  
team_CTF_redflag
team_CTF_blueflag
trigger stolen  
trigger dropped  
trigger returned  
trigger captured  
(Source: http://games.chruker.dk/enemy_territory/scripting_reference.php#complete_list)


=> Do you also want a homepage for free? Then click here! <=