This text file is designed to give you a short decription of the mapC language, along with a walkthrough of the mc2 map, and its mapC. As a mapmaker, if you don't understand how mapC works, or don't want to understand, then don't... the only reason we provide you with this is so you can gain a better understanding of what the TF2 map engine is capable of. We will be providing large chunks of example mapC code with TF2, handling all the standard map types... 2 flag ctf, 1 flag ctf, command point system, president, etc.
This does _not_ tell you how to program mapC. If you have programming experience, then you'll grasp it easily... if not, then look elsewhere for programming help.
Please note this is a badly cut version of a text file supplied to our mapmakers, and may make references to files that we haven't made available to you. Please don't mail us asking for these files.
![]()
MapC
![]()
The mapC language is basically C. You write a header for, which is essentially a makefile, for your project. In this case, it's mc2.mapH, which looks like this:
        #mapinfo
        	"name"          "mapC test 2"
        	"outputname"	"mc2"
        	"description"   "simple script stuff"
        #interface
        	"int6.mapI"
        #libraries
        // none
        #scripts
        	"mc2.mapC"
For your map, just copy the mapH and change the #mapinfo section.
The #script section tells the compiler which files to include, so
you'll probably want to include your map's .mapC file. Apart
from that, ignore the rest.
The only other bit of interest is the int6.mapI file. This file defines the Scripting Interface... basically, all the variables and functions the TF2 engine provides the mapC. Have a browse through it... skip past all the start and take a look at the #interface_vars section. This section contains all the interface variables and functions... for instance:
        void dprint(string cstring);
This interface function is Debug Print, which takes a string variable,
returns nothing. It simply prints that string to the server's console.
A more interesting one is:
        void team_set_name(int team_number, string team_name);
This sets the name of a Team to whatever string you specify.
Browse through the interface funcs... so far, all you really have are
the various functions we needed for the ctf mapC.
This is the main area we want feedback from you mapmakers... if you have interface functions you require for a particular map, then by all means tell us, and we'll look into including them.
When TF2 starts a map, it looks for a mapC script file to match that map... first it looks inside the map's Worldspawn entity. If there's a "scriptfile" key, such as "ctf", it'll look for that .mcB file (ctf.mcB). If that "scriptfile" key isn't specified, it looks for a .mcB file matching the name of the map file (mc2.mcB).
![]()
MC2
![]()
The mc2 map is a simple map which we're using to test mapC on. At the moment, it contains mapC for CTF, Command Point, and a cute scoreboard that Midori wanted.
It'd take ages to describe it all, and you'll probably figure out more just by reading it... so I'll begin by just describing the behaviour of one entity, a CTF Flag... the most complex entity in the mc2 mapC, and indeed, probably the most complex use of mapC there is.
![]()
Custom Entity Definitions
Start by looking at this code:
        
    //////////////////////////////////////////////////////
    // Custom Ent Definitions for Flags
    entity_def 
    {
        "classname"         "item_flag_team1"
        "spawnclass"        "item_script"
        "script_spawn"      "spawn_flag"
        "script_postspawn"  "postspawn_flag"
        "script_activate"   "touched_flag"
        "script_getitem"    "get_flag"
        "script_dropitem"   "drop_flag"
        "owned_by_team"     "1"
        "name"              "Blue Flag"
    }
This defines a new type of entity... a "Custom Entity" that can only
be used on a map which includes this mapC. The first line in the definition
sets the classname of the Custom Entity to "item_flag_team1".
This means you can open up your map, put an entity somewhere, call
it "item_flag_team1", and include this mapC, and bang, that entity
will be a ctf flag for team 1.Anyway, back to the flag. The "spawnclass" tells TF what type of entity this entity really is. Basically, Custom Entities are derived from an existing entity... in this case, an item_script. You've probably already seen John's initial TF2 mapmaking ent specs... in it, the "item_script" is defined as:
    "An item who's properties are mainly described by scripts.
     It can be picked up, carried and used by players."
So, we're deriving our flag entity from that basic entity type, and we're
going to override its basic behaviour with some of our own mapC code.
Then we define a bunch of scripts for this entity... you'll be able to find all the scripts listed here throughout the mc2.mapC file. Currently, every entity can use any or all of the following scripts:
    ALL ENTITIES
	script_use		    // called when targeted
	script_activate	    // called when activated (touched, etc.)
	script_usetargets	// called after activated, after delayed, when using target/killtarget
	script_kill 		// called when killtargeted
	script_damaged	    // called when damaged
	script_think		// requires a nextthink
	script_prespawn     // yes/no to whether it should spawn
	script_spawn		// called just after spawn
	script_die		    // called when player/button/etc dies
    ITEM SPECIFIC
	script_postspawn	// called after items have spawned and dropped
	script_dropitem	    // called when player tries to drop this item
	script_useitem	    // called when player tries to use this item
	script_getitem	    // called when player tries to pick up the item
    PLAYER SPECIFIC
	script_attack		// called when player attempts to attack
	script_changeweapon // called when player changes weapons
Here, we're defining 5 scripts for this flag.
The next line ("owned_by_team"     "1") sets a variable in this entity
called "owned_by_team" to 1. Then we set the "name" of the entity to
"Blue Flag".
Note that all of these settings (scripts, owned_by_team, name) are set exactly as if they were key/value pairs inside the .map file. Note that Custom Entity definitions are overridden by .map file key/value pairs, so if you put an "item_flag_team1" entity in your .map file, with two keys, as follows:
    "owned_by_team"     "16"
    "name"              "Team 16's Flag"
The .map's keys would override the mapC's entity definition, and you'd
effectively have a flag for team 16. We could provide an entity definition
along these lines:
    entity_def 
    {
        "classname"         "item_flag"
        "spawnclass"        "item_script"
        "script_spawn"      "spawn_flag"
        "script_postspawn"  "postspawn_flag"
        "script_activate"   "touched_flag"
        "script_getitem"    "get_flag"
        "script_dropitem"   "drop_flag"
        // Mapmaker must provide a "owned_by_team" and a "name"
    }
And there, we've created a Custom Entity definition for a generic flag.
Provided the mapmakers sets the "owned_by_team" and "name" keys in the .map
file, the map could use this Custom Entity to makes flags for any team.
![]()
Spawn Scripts
Now, lets look at the Flag's first script, the spawn script. As the above description says, the spawn script is called when the entity is spawned, at the start of the map... before players have joined even. Here's the flag's spawn script:
    int entity::spawn_flag(int flags)
    {
        //self.model = "models/items/tf_flag/tf_flag.md2";
        self.model = "models/items/healing/medium/tris.md2";
        self.flag_state = FLAG_HOME;
        return TRUE;
    }
Ignore the comment... you don't have the tf_flag model, so it's just
using the health box model for now, which the second line sets. Note
that the "self" variable points to the entity the script is being run
for... in this case, it's the Flag itself.
To understand the third line (self.flag_state = FLAG_HOME), we need
to take a look up near the top of the file, 
at these lines:
    // new vars
    vector entity::init_origin;             // For tracking flag movement
    int entity::flag_state;                 // Allow us to keep track of all flags on the map
    entity entity::carrier;                 // Allow us to keep track of all flags on the map
    // Flag states
    const int FLAG_HOME             = 1;
    const int FLAG_AWAY             = 2;
    const int FLAG_BEING_CARRIED    = 3;
The // new vars section defines a bunch of new variables.
The second one (int entity::flag_state) defines a new integer for 
every entity in the game. Its a mapC var only, so you can do anything you want 
with it, and be assured that the dll code won't ever change it on you.Now, back down to the spawn function again... now we can see that the (self.flag_state = FLAG_HOME) line is setting the Flag's flag_state variable to FLAG_HOME (1). As you've probably figured out by now, we're going to use the Flag's flag_state variable to keep track of what the status of the Flag is... whether it's at it's Home, Lying around somewhere, or Being Carried by someone.
The last line of the spawn function is important. Almost all built in scripts return an int, which is usually TRUE or FALSE, and the TF engine uses that return value to do something. In the case of Spawn Scripts, if the spawn function returns FALSE, the TF Engine understands that you don't want this entity to exist in the game, so it removes it. This allows the mapC to check server settings, like deathmatch, teamplay, etc, and remove entities that shouldn't exist under those settings.
![]()
Postspawn Scripts
Onto the next script... the "postspawn" script. This is mainly used just for
items... and to understand why it's needed, we need to understand just a little
about how Quake2 works. When Quake2 starts a new level, it moves through the
entities in the map and spawns them all, calling their spawn functions. The
TF engine calls all the mapC spawn functions at this time. All spawn functions
are called at the same point in the game time... so that none of the entities
quite exists until the next game time "tick".
Now, the tricky part is that even the map itself, the world, is considered an
entity. So, until the next game time "tick", not even the world exists. This
means that if you wanted to do something with your entity in the spawn that 
involved interaction with the world/map itself, it would fail.
And that's where the "postspawn" script comes in... it is called shortly after
the initial spawning of entities, and you can safely perform functions which 
rely upon other entities, like the world.
Here's the flag's postspawn script:
    int entity::postspawn_flag(int flags)
    {
        droptofloor(self);
        self.init_origin = self.origin;
        return TRUE;
    }
The first line (droptofloor(self)) calls an interface function called 
"droptofloor". As it's name implies, this function simply makes the
entity passed to it (in this case, the flag) drop to the floor. This
had to be done in the "postspawn" script, not in the "spawn" script,
because as we described above, the world does not exist in the "spawn" script.
Why do we drop this item to the floor? It's standard practice... in Quake2,
all items are dropped to the floor upon spawning, so that mapmakers don't
have to worry about putting their in exactly the right place on the floor...
they can just suspend them up in the air a bit, and let the engine handle 
the rest.
Why do we have to do it in the mapC then? Because the "item_script" entity,
from which the Flag's Custom Entity is derived, does almost nothing on it's
own... it designed to let the mapC define it's behaviour.
Now, the second line (self.init_origin = self.origin) sets another mapC variable we defined at the top of the mapC code... init_origin. An entity's .origin variable is a vector containing the current position of the entity in the world. As we all know, in ctf style maps we need to return the flag to it's original position on occasion. So, we need to store this initial position somewhere... and that's why we defined a new vector for every entity: init_origin.
![]()
Activate Scripts
Now, onto the really interesting Flag functions... the "activate" script.
Activate scripts are called when something has attempted to activate this
entity. These scripts provide you with the capability TF1 provided with 
its extensively ugly Criteria.
TF2 provides entities with only 2 basic criteria... team checking and 
playerclass checking. If the activating entity passes both those Criteria,
the activate script is called (if it exists), and if that script returns
TRUE, the entity is activated.
So, lets look at the Flag's activate script, 
which will be called whenever a player has touched the Flag. It's a long 
function, so I'll just describe snippets of it:
    int entity::touched_flag(entity other, int flags)
    {
        entity msgent, flag;
        string fstr, estr, astr;
Look at the parameters this script takes... an entity and some flags. The
flags simply provide you with some more information about this activation...
ignore them for now. The entity parameter is important though: it's a pointer
to the entity that is attempting to activate this entity... in this case, it's
the player who has touched the Flag.
The first two lines define local variables we need in this script. They work just like any local variable in C. The next few lines:
    if (other.team_no == self.owned_by_team)
    {
        if (self.init_origin != self.origin)
        {
            // Return Flag
            self.origin = self.init_origin;
            self.flag_state = FLAG_HOME;
            // Setup and Print Messages
            fstr = other.name + " Returned Your flag!\n";
            estr = "Your Flag was Captured!\n";
            astr = other.name + " Returned the " + other.name + "!\n";
            print_team_updates(other, other.team_no, other.team_no, fstr, NULL, astr);
            cprint(other, "You returned your flag\n");
            // Play Wav files
            playwavs_to_teams(self, other.team_no, other.team_no, "tf/maps/ctf/t_fl_return.wav", "", "tf/maps/ctf/t_en_fl_return.wav");
        }
The first line (if (other.team_no == self.owned_by_team) is fairly self
explanatory... it checks to see if the other's (the player) team number is
the same as the self's (the Flag) "owned_by_team". Remember that the Custom
Entity definition of the Flag set the "owned_by_team" variable to "1" for
Team 1's Flag. So, we're checking to see if the a member of the team who
own this Flag has touched it.After that is a large chunk of code, so I've snipped out some of the simple stuff that we've seen above.
        else
        {
            // If they've got any enemy flags, capture them
            while ((flag = find_ent(flag, entvar, "item_script")) != NULL)
            {
                if (flag.flag_state == FLAG_BEING_CARRIED AND flag.carrier == other)
                {
                    // Setup and Print Messages
                    [snip]
                    // Play Wav files
                    [snip]
                    remove_item(other, flag.name, 0);
                    flag.origin = flag.init_origin;
                    flag.svflags = flag.svflags - (flag.svflags & SVF_NOCLIENT);
                    flag.solid = SOLID_TRIGGER;
                    flag.flag_state = FLAG_HOME;
                    flag.carrier = NULL;
    
                    iscore += 1;
                    set_scoreboard("my_board", iscore);
                }
            }
        }
 
So, if the Flag is touched by someone on the same Team, and it _is_ at home,
we do some slightly more complex stuff.
    while ((flag = find_ent(flag, entvar, "item_script")) != NULL)
 
"find_ent" is an interface function that takes 3 parameters... an entity
to start from, a variable to check, and a value to check the variable against.
In this case, we start from flag, which was a local variable, and hence is 
initialised as NULL... and if the starting entity passed to "find_ent" is NULL, 
it starts from the start of the entity list. The second parameter 
(entvarSo, basically, this function simply returns a pointer to the first entity it find with a classname of "item_script". The line starts looking in the entity list from the "flag" variable's position, and since we set the "flag" variable to it's return variable, the while loop will cycle nicely through all the "item_script" entities in the map.
The next line is as follows:
    if (flag.flag_state == FLAG_BEING_CARRIED AND flag.carrier == other)
It checks the state of the current flag returned by the "find_ent" function.
If the flag's state is being carried, AND the flag's carrier variable is
equal to the other (the player who touched this flag), then we want to capture
this flag.The next important bit is the (return FALSE) line. We return FALSE because we don't want to allow the player to pick up the Flag... it's his flag after all, and in ctf style modes, we don't allow player's to carry their flags.
Now, onto the last bit of the script, which handles what happens if a player _not_ on the Flag's team touches it... obviously, we want the player to pick up the flag, so we return TRUE, and let the TF2 code give the player the Flag.
![]()
GetItem Scripts
Now, we're starting to get into the trickiest part of mapC... item handling. Lets look at the Flag's "getitem" script:
    int entity::get_flag(entity other, int no_of_flags)
    {
        self.flag_state = FLAG_BEING_CARRIED;
        self.carrier = other;
        // Since we're not going to let the Code remove this item,
        // we need to hide it ourselves
        self.svflags |= SVF_NOCLIENT;
        self.solid = SOLID_NOT;
        // Return False to prevent the Code from removing the item 
        // now its been picked up
        return FALSE;
    }
If you're following things nicely, you'll probably wonder why we need a 
"getitem" script at all... after all, couldn't we just put all this code at 
the end of the "activate" script, before we return TRUE?
To understand exactly what's going on here, we need to look at how TF2 
handles items, and here's where things get ugly.
![]()
Item Handling
For every player in the game, TF2 records a single number for every Item 
Type in the game, which represents the number of those Item Types the 
player has. For instance, if a player gets a weapon, the player's count for 
that weapon Type is incremented, and the actual weapon Entity itself is 
_removed_.
It'd be a terrible waste of memory to keep all the entities that player's 
are carrying around, when we can simply keep track of a number of them
a player has.
So, way back when we declared a new Custom Entity definition, TF2 took note
of the fact that we were deriving it from an "item_script". Whenever TF2
spots an "item_script" in a map, it creates a new Item Type based upon
that item_script. If there are 20 of those item's in the map, it still only
creates 1 Item Type.
Then, whenever a player gets one of those Item Entities, it simply increments 
the player's count of that Item Type, and nukes the Item Entity.
And whenever a player drops one of those item's, it simply decrements the 
player's count of that Item Type, and then, and this is important, creates
a new Item Entity Based Upon The Original Item Type.
So, after the Item Type is created, which occurs during the spawning of the "item_script", any changes to any Item Entities derived from this Item Type do not affect the Item Type. This is the fundamental thing to understand when it comes to handling items in TF2 mapC.
Now, this creates a problem for us in our ctf mapC... because we don't want the item (in this case, the Flag) destroyed and the player's count simply incremented... because we store information in that item. In the Flag's "postspawn" script, we store the Flag's initial origin. The Item Type is created during spawning, which means that initial origin, and the flag state for that matter, is _not_ stored inside the Item Type. Which means that if we allowed the Flag entity to be destroyed when the player picked it up, and then recreated when the player drops it, we'd lose that initial origin and flag state.
![]()
Item Pickup Summary
So, to make things simple for mapC coders, there's two scripts related to item
pickups, and each has a distinct purpose.
The "activate" script is called before the pickup, and is designed to return
TRUE if you want the TF2 code to handle the pickup and increment the player's 
count of this Item Type. 
The "getitem" script is called after the pickup, and is designed to return 
TRUE if you want the TF2 code to destroy the Item Entity.
In this case, we're happy to let TF2 handle the pickup and the item count incrementing and all that crap... we just don't want it to remove the Flag entity afterwards.
So, we return TRUE in the Flag's "activate" script, and we return FALSE in the Flag's "getitem" script. But we don't want to just leave the flag there, so we need to do a bit more work... here's the code again:
    int entity::get_flag(entity other, int no_of_flags)
    {
        self.flag_state = FLAG_BEING_CARRIED;
        self.carrier = other;
        // Since we're not going to let the Code remove this item,
        // we need to hide it ourselves
        self.svflags |= SVF_NOCLIENT;
        self.solid = SOLID_NOT;
        // Return False to prevent the Code from removing the item 
        // now its been picked up
        return FALSE;
    }
The first line (self.flag_state = FLAG_BEING_CARRIED) updates the status
of the flag, and the following line (self.carrier = other) sets the 
Flag's carrier variable, which we defined up at the top of the mapC,
to point to the player who's now "carrying" the Flag.
The next two lines are more involved in the Quake2 dll code than we'd like
to get into at this time, so I'll describe them only in what they do, not
in how they do it.
The (self.svflags |= SVF_NOCLIENT) line sets a bit in the Flag's serverflags
which prevents the game server from telling any clients that it exists...
effectively making it invisible to everyone. 
The (self.solid = SOLID_NOT) line makes the Flag non-solid, making it 
impossible for any entities to touch it.
So, these two lines effectively "remove" the Flag, but still leave the actual
Flag's entity intact.
And then finally, we return FALSE, preventing the TF2 code from removing the Flag entity. And we're done with Flag pickups.
![]()
Flag Capturing
Now, lets backtrack just a bit, to the bit in the Flag's "activate" script where we capture flags being carried by the player... the code again:
        if (flag.flag_state == FLAG_BEING_CARRIED AND flag.carrier == other)
        {
            // Setup and Print Messages
            [snip]
            // Play Wav files
            [snip]
            remove_item(other, flag.name, 0);
            flag.origin = flag.init_origin;
            flag.svflags = flag.svflags - (flag.svflags & SVF_NOCLIENT);
            flag.solid = SOLID_TRIGGER;
            flag.flag_state = FLAG_HOME;
            flag.carrier = NULL;
            iscore += 1;
            set_scoreboard("my_board", iscore);
        }
The first line (remove_item(other, flag.name, 0)) calls an interface script
called "remove_item". This function takes a player (in this case other, the
player who touched the flag), and removes items from him/her. The Item Type
to be removed is specified by the second parameter, and the third parameter is 
the number of that type to remove... or 0 if we want to remove however many
the player's got.
The (flag.origin = flag.init_origin) sets the Flag's origin back to the
initial origin that we saved in the "postspawn" script.
The (flag.svflags = flag.svflags - (flag.svflags & SVF_NOCLIENT)) line removes
the bit we set in the Flag's serverflags, effectively making it visible again.
The (flag.solid = SOLID_TRIGGER) line makes the Flag solid again, ready to
be touched by players. Then we update the Flag's status, and clear the Flag's
carrier variable, since it's not being carried by anyone.
The following lines just update the scoreboard... they're not part of the CTF code. I just needed a way of incrementing the scoreboard while I was testing it. Capture the flag a few times and you'll see the scoreboard keep count of how many captures you've made.
![]()
DropItem Scripts
Now lets look at the last piece of the Flag puzzle... the "dropitem" script.
An item's "dropitem" script can be called for a few reasons, so the second
parameter passed to the "dropitems" script specifies the cause for the drop.
The last parameter specifies the number of these Item Types the player has...
more on this shortly.
Let's look at the first part:
    int entity::drop_flag(entity other, int flags, int no_of_flags)
    {
        entity flag;
        string fstr, estr, astr;
        if (flags & SF_DI_PLAYER_DROP OR flags & SF_DI_PLAYER_TRADE)
            return 0;               // Prevent Drop
First we define a couple of local vars, and then we take a look at the
flags passed to us by the TF2 code. The flags will be set to one of the
following (which are defined in the mapC interface file, .mapI):
    const int SF_DI_PLAYER_DEATH	=   1;
    const int SF_DI_PLAYER_DROP	    =   2;
    const int SF_DI_PLAYER_TRADE    =   4;
The "dropitems" script is called for one of three reasons... the Item Type
is being 'dropped' because either the player died, the player voluntarily
tried to drop the item, or the player tried to trade it to a teammate.
The "dropitems" script simply returns the number of this Item Type to 
'drop' (or trade).Now, unfortunately things are going to get tricky again here, because we're about to discover the other part of Item Handling... Item dropping. When the "getitems" script is called, the "self" variable is set to point to the Item Entity... the one the player has just picked up. When the "dropitems" script is called, the "self" variable is NOT set to point to an Item Entity... why? Because there may not be one. Remember... a player does not have a list of Item Entities he/she is carrying. All a player has is a count of the number of Item TYPES he/she is carrying.
So, when the "dropitems" script is called for an Item, it is called for an 
Item TYPE. Remember... when a map spawns, TF2 creates an Item Type for each
new Custom Item Definition it finds in a map. This Item Type is a template
that is never altered after it's initial creation.
So in the "dropitems" script the "self" variable is set to point at that Item 
Type... at that template. You can read information out of it as much
as you like, but you cannot alter it.
Now, lets continue:
        // Play Wav files
        [snip]
        // We want to handle it ourselves
        remove_item(other, self.name, no_of_flags);
After playing some wav files, we remove all of this Item Type that the 
player has.
After that we use a loop as follows:
        // Cycle through all the flags, and find the correct flags
        while ((flag = find_ent(flag, entvar, "item_script")) != NULL)
        {
            if (flag.flag_state == FLAG_BEING_CARRIED AND flag.carrier == other)
            {
                throw_object(flag, other.origin);
                flag.solid = SOLID_TRIGGER;
                flag.flag_state = FLAG_AWAY;
                flag.carrier = NULL;
                // Setup and Print Messages
                [snip]
            }
        }
 
This simply cycles through all the Flag's in the map, and finds out if they're 
being carried by this player. If they are, we call this function:
    throw_object(flag, other.origin);
This is an interface function that takes an entity and a point. It moves the
entity to that point, and throws it upwards in a random direction. It also
removes the entity's serverflag NOCLIENT bit... making the entity visible
again.
        return 0;
    }
This tells the TF2 code that we don't want to drop any of this Item Type...
because we've already handled it ourselves. If we were to return 1, the TF2
code would create a new Item Entity, based upon this Item Type, and drop it.
If we were to return 10, the TF2 code would create 10 new Item Entities, all
based on the Item Type template, and drop them all.
Now you might be wondering at this moment... why are we using the "dropitems" 
script of one Type of Flag to drop all the Flag's the player is carrying? 
Isn't that kind of illogical, not to mention stupid if the player has more
than one flag, because this script will be called for each Flag Type?
Well... yes. 
The correct way of handling this would be to define all the Flag's "dropitem" scripts as nothing but a simple function that returned 0 and do the actual dropping of the Items in the player's "script_die" script, which would be called whenever a player dies.
But, that'd be easy... and I wanted to try and push the difference between an Item Entity and an Item Type.
![]()
Item Handling Summary
While the whole Item Handling concept isn't simple, the implications for mapC coders are. Its important to keep in your head the difference between an Item Entity and an Item Type... do that, and you shouldn't have any trouble.
    - Player's carry numerical counts of Item Types.
    - Players pickup Item Entities, which increments their counts of the 
      corresponding Item Types, and destroys the Item Entities.
    - Players drop Item Types, which decrements their counts of those Item 
      Types, and creates new Item Entities based upon those Item Types.
![]()
Simpler Items
![]()
Now, you're probably thinking mapC looks fairly daunting. Not so... as I said, the ctf style flags are among the most complex uses of mapC there is, because they're items that you want to keep track of even when they're being carried. That's fairly rare... you'll usually be happy to let the TF2 code do the item handling.
Lets look at a far simpler item, and one that's more like the standard item's
most mapmakers will want to add.
Here's the entity definition:
    entity_def 
    {
        "classname"         "item_comm_marker_team1"
        "spawnclass"        "item_script"
        "script_postspawn"  "comm_marker_postspawn"
        "script_activate"   "comm_marker_touched"
        "script_getitem"    "comm_marker_getitem"
        "script_dropitem"   "comm_marker_dropitem"
        "owned_by_team"     "1"
        "name"              "Blue Command Marker"
        "model"             "models/items/healing/medium/tris.md2"
        // Criteria
        "team_no"           "1"
    }
This defines a new Custom Entity... the "item_comm_marker_team1".
It's a Command Marker, used in the Canalzone Command Point scoring system.
It's derived from the "item_script" class, like the ctf Flags. It's owned
by team 1, has a health box as a model, and unlike the ctf Flags, it's got
some Criteria: a "team_no".The Command Marker's scripts are very simple. No spawn script, and a postspawn script as follows:
    int entity::comm_marker_postspawn(int flags)
    {
        droptofloor(self);
        return TRUE;
    }
Like the ctf Flags, we want Command Markers to drop to the floor after spawning.
The "activate" script called when a player tries to pick up a Command Marker is as follows:
    int entity::comm_marker_touched(entity other, int flags)
    {
        // Only allow them to carry 1 at a time
        if (get_num_items(other, self.name) == 0)
            return TRUE;    
        return FALSE;
    }
The "get_num_items" function is an interface func that returns the number
of an Item Type that a player is carrying. In this case, we want to see if the
player has any of this type of Command Marker... if not, we allow them to
pick it up.
The "getitem" script for the Command Marker's is also simple:
    int entity::comm_marker_getitem(entity other, int no_of_flags)
    {
        return FALSE;
    }
Quite simply, we don't want the TF2 code to remove this item... it simply
acts as a kind of dispenser. It'll just give out 1 Command Marker item to any 
of its team that touch it.
The "dropitem" script is just as simple:
    int entity::comm_marker_dropitem(entity other, int flags, int no_of_flags)
    {
        return 0;   // Lost when player dies
    }
Player's cannot trade or drop Command Markers... when player's die, Command 
Marker's are just lost.
And that's it. In their "activate" scripts, the Command Points check to see 
if player's have any Command Markers, and if so, allow the player to capture
that Command Point. Command Markers themselves provide player's with no other
capabilities.
So, lets look at an item that does.
![]()
Command Items
![]()
Command Items are a special type of item that adds a command to any player carrying it. The ctf mapC uses a Command Item for the "flaginfo" command, so we'll look at that. Here's it's entity definition:
    entity_def
    {
        "classname"         "item_flaginfo"  
        "spawnclass"        "item_script"
        "script_spawn"      "spawn_flaginfo"
        "command_func"      "flaginfo_command"
        "command_str"       "flaginfo"
        "name"              "flaginfo"
        "item_flags"        "96"         // IT_NOT_VISIBLE | IT_RESPAWN_KEEP
    }
Like all previous items, it's derived from the "item_script" class. It only
has one script, and that's it's spawn script, which looks like this:
    int entity::spawn_flaginfo(int flags)
    {
        return FALSE;
    }
Returning FALSE from a spawn function causes the entity to be removed...
we don't want "flaginfo" entities in the map, so we just remove them. 
They don't actually need to be in the map... the spawn function's just in
case a mapmaker puts one in the map.
Now, lets look at the interesting parts of the Flaginfo Entity Definition:
        "command_func"      "flaginfo_command"
        "command_str"       "flaginfo"
The first line specifies a Command Function. The second specifies a Command
String. That's all Command Items need. 
    void flaginfo_command(entity ent)
    {
        entity flag;
        // Cycle through all the flags, and dump the status of each
        while ((flag = find_ent(flag, entvar, "item_script")) != NULL)
        {
            if (flag.flag_state == FLAG_HOME)
            {
                cprint(ent, flag.name);
                cprint(ent, " is in its base.\n");
            }
            else if (flag.flag_state == FLAG_AWAY)
            {
                cprint(ent, flag.name);
                cprint(ent, " is lying around somewhere.\n");
            }
            else if (flag.flag_state == FLAG_BEING_CARRIED)
            {
                cprint(ent, flag.name);
                cprint(ent, " is being carried by ");
                cprint(ent, flag.carrier.name);
                cprint(ent, "\n");
            }
        }
    }
 
Command Functions take a single parameter... a pointer to the player who used
it. The "self" variable points to the Item Type of the Command Item.
In the case of the Flaginfo item, the Command function just cycles through
all the Flags in the map, and displays the status of each of them to "ent",
which is the player who typed the "flaginfo" command.
There's only one catch... the player must have one or more of the Command 
Items. So how does a player get the Flaginfo item, since the spawn function
automatically removes it from the map?
Easy... through the use of a Global Script.
![]()
Global Scripts
![]()
Global Scripts are scripts that aren't attached to a specific entity... they're generally event based. An event occurs which has an attached Global Script, and if the Global Script exists in the mapC file, it's called.
The first Global script we'll look at is PlayerConnect. As it's name suggests, it's called whenever a Player connects to the server. Here's mc2's PlayerConnect script:
    int entity::script_PlayerConnect(int flags)
    {
        add_item(self, "flaginfo", 1);
    	return TRUE; // yes, the player is allowed to join the game
    }
The "self" variable is set to point to the connecting Player. If the 
PlayerConnect script returns FALSE, the player is prevented from joining the 
server. In mc2, we use the PlayerConnect to give the player the Flaginfo
Command Item, using the "add_item" interface function.There are currently only two other Global Scripts: Worldspawn, called at the initial spawning of the map, and Precaches, called when the server begins to precache sounds and models. We will be adding more... for when Player's disconnect, join teams, leave teams, etc. Like Interface Functions, we'd like to hear your requests for Global Scripts.
![]()
Think Functions
![]()
And last but not least, lets look at Think functions. The Command Point
System uses a Think function to check the status of every Command Point
in the map.
Every entity can have a think function and a nextthink time. The TF2 
engine will automatically call that think function when the time is reached.
Lets look at the WorldSpawn Global Script:
    int entity::script_Worldspawn(int flags)
    {
        entity ent;
        number_of_teams = 2;
        team_set_name(1, "Blue");
        team_set_name(2, "Red");
        // Command Point Scoring Entity
        ent = create_entity();
        ent.think = comm_scorer_think;
        ent.nextthink = time + 300;    // 30 Seconds
    	return TRUE;
    }
The Worldspawn Global Script is called when the map first loads.
First, we initialise the number of teams the map is designed for, and set
the team's names. These values could be overriden by setting values inside
the "worldspawn" entity in the .map file, but don't worry about that for now.
The (ent = create_entity()) line creates a new entity and sets the "ent" 
variable to point at it. The next two lines set that entity's think
and nextthink.
We set the think to "comm_scorer_think" and the nextthink to time + 300.
What this means is that the TF2 code will automatically call the 
"comm_scorer_think" script 30 seconds from now.
You might want to take a look at the 
"comm_scorer_think" script.
![]()
Other MC2 Code of Interest
![]()
We'll let you look over the mc2 mapC and mess with what's there. Take a look at the scoreboard... it's an example of a more complex interaction between mapC and brushes inside a map.
You might notice the use of the "entlist" variable in some places, such as
the "print_team_updates" script. An "entlist" 
is an Entity List. Entities can be added and removed from lists simply and 
effectively, and any operation applied to a list is applied individually to 
all entities in the list. Handy for messaging everyone on a team, or providing 
bonuses to a subset of players.
More complex uses of them will come later.
Also, check out the entity definitions for the spawnpoints. Players attempting to spawn on a spawnpoint have their criteria checked just like everything else... a spawnpoint's "activate" script can check any of the player's details, and return TRUE if the player is allowed to spawn on it.
![]()
Results
![]()
So what have we got here?
We've got a chunk of mapC code that, among other things, will handle any
CTF map, with any number of Flags per team, with upto 32 teams. And all the 
mapmaker has to do is set the worldspawn's "scriptfile" key to "ctf", and 
put the Flag and Spawnpoint entities around the map. 
Robin. [email protected]
![]()
[Front][News][Download][TF
Info][TF Clans][Links][Contact][Maps][Classes][Supporters]