Chapter 7

Predefined Object Procs

That which is the dark night of all beings, for the disciplined man is the time of waking; when worldly people are working the enlightened sage is unaware of the material waking.
--Bhagavat Gita

Just as there are a number of pre-defined variables defining the properties of objects, there are also pre-defined procs that control how the objects behave in certain situations. A programmer would call many of these event handlers, because they define how the object responds to various events.

Some of the pre-defined procedures don't do anything at all but exist solely for the purpose of allowing you to override them with your own definition. Such empty procedures are often called hooks because they provide a place for you to attach your own handler for an event. However, in DM, not just the hooks, but all other object procedures as well are available to be redefined and customized to suit your own needs.

1. Movement Procs

Both mobs and objs are capable of movement (though mobs are usually the only ones to do it of their own volition). You have already seen how the density variable restricts movement: no two dense objects may occupy the same position. This rule, among others, is enforced by the various movement procs.

Note that movement is not restricted to positions on the map. Objects can move inside of each other as well. For example, when a mob picks up a sword, the sword moves from the map into the mob. The sword could then be moved into a bag object for safe keeping. Any change of location, whether it be on the map or inside other objects, is considered a movement.

1.1 Enter

The Enter proc is defined for all four object types. It returns 1 if the specified object may enter the source and 0 if not. If the object entering is dense and there is already a dense object at the location, entrance will be refused.

Enter (O)
O is the object entering.
src is the location being entered.
usr is the entering mob if any.

Suppose you introduced a magic spell that made people intangible (i.e. non-dense) and able to walk through walls. You might still want some walls to be impenetrable. This could be easily accomplished by overriding the Enter proc.

turf/lead_wall
   name = "lead wall"
   Enter(O)
      return 0 //none may enter here

1.2 Exit

The Exit proc is the counterpart to Enter. It returns 1 if the specified object may exit the source and 0 if not. By default, exits are always allowed.

Exit (O)
O is the object exiting.
src is the location being exited.
usr is the exiting mob if any.

By redefining this proc, one could make a nasty trap.

turf/pit/Exit(O)
   O << "You are stuck in [src]!"

1.3 Bump

The Bump proc is called when movement fails because of a dense blockage. If the mob trying to move happens to be in the group list of the mob who was in the way, the two will swap positions.

Bump (Blockage)
Blockage is the object in the way.
src is the object who was blocked.
usr is the mob who was blocked if any.

This proc could be overridden to also include the rule that all players, regardless of group membership, will swap positions.

mob/Bump(mob/M)
   if(istype(M) && M.key && src.key)
      var/pos = M.loc
      M.loc = usr.loc
      usr.loc = pos
   else ..()

The istype() instruction is used to determine if the value contained by a variable is indeed of the same type as the variable. Here we use it to see if the blockage is really a mob. It could also have been spelled out like this: istype(M,/mob).

If the blockage is a mob and if both mobs in question have keys, their positions are swapped. Instead of directly assigning the mobs to their new positions, you might want to make one of them non-dense and do a proper movement. How and why you would do that is described next.

1.4 Move

The Move proc is the one which ties all the other movement procs together. It calls the Enter proc of the destination. If this fails, it calls Bump. Otherwise, the Exit proc of the original location is called next and finally, if that succeeds, the source is assigned to the new location. The return value is 1 on success and 0 on failure.

Move (Dest,Dir)
Dest is the destination location.
Dir is the direction of movement.
src is the object moving.
usr is the mob who is moving (if any).

The direction parameter need not be specified. It is relevant to one more thing that the Move proc handles, which is changing the direction the object that moved is facing. If the direction argument is specified, it will be used as the new direction to face; otherwise it will be computed from the relative positions of the original and final locations.

The other movement procs are not usually called directly. The Move proc, on the other hand, can be useful if you want an object to move in accordance with all the movement rules. Directly assigning the object to a location bypasses all such density checking, bumping, and direction changing.

You could, for example, make a magic scroll that would teleport the spell caster to any open position in view.

obj/scroll/teleport
   verb/teleport(T as turf)
      set src = usr.contents
      if(!usr.Move(T)) usr << "You cannot move there!"
      else view() << "[usr] dances through the ethers!"

There is much more to say on the subject of movement. For example, one might want to make NPC mobs walk around without mindlessly bumping into obstacles. Many useful movement instructions and techniques will be discussed in section 14.2.

2. Object Creation and Destruction

You have already seen examples using the instructions new and del. These are used to create or destroy objects. Another way of creating objects is to place them on the initial map using the map editor. When the world is finally shut down, any objects that remain are destroyed at that time. Since you might want to do some special action when an object is created or destroyed, DM provides procs to handle these events.

2.1 New proc

The New proc is called when an object has been created. It may be used to do any special initializations required by the object. By default, it doesn't do anything.

New (Loc)
Loc is the initial position of the object.

When an object is created with new, the location and any additional arguments you define are passed in at that time. The syntax for doing so is:

new Type(Loc,...)
Type is the type of object to create.
Loc is the initial position of the object.
... contains any additional arguments.

Note that the object is created at the initial position. It doesn't move there--it just gets directly assigned to the location. If no initial position is specified, the object's location is null, which means it won't show up on the map.

As an example, one could make an object that plays a sound when created.

obj/portal
   New()
      view() << "A shimmering portal appears!"
      view() << 'portal.wav'
mob/DM
   verb
      make_portal(NewName as text)
         var/obj/portal/P = new/obj/portal(usr.loc)
         if(NewName) P.name = NewName

The use of new here is very common. We declared a variable, assigned it to a new object, and made additional modifications through the variable. In this case a useful abbreviation can be applied. The variable being assigned has the same type as the new object being created. Instead of specifying the redundant type information, it can simply be left out. In that case, the call would be new(usr.loc) instead. Also note that in the example above, space is optional between new and /obj/portal.

The same example can be redesigned to pass the new name in as a parameter.

obj/portal
   New(Loc,Name)
      if(Name) name = Name
      view() << "A shimmering portal appears!"
      view() << 'portal.wav'
mob/DM
   make_portal(Name as text)
      new/obj/portal(usr.loc,Name)

The choice of whether to pass such information as a parameter to New or whether to perform the assignments elsewhere depends on how often you will be doing that same configuration task. One time when you would want to use parameters to New is if you wish to handle the information differently in derived objects. Then you could simply override the New proc in those cases.

2.2 Del proc

The Del proc is called to destroy an object. By default, this causes the contents of an obj or mob to be dumped to its location. A player using the mob will be disconnected from the world. Any additional cleanup can be handled in your own redefinition of the Del proc.

Del()

This proc can be called directly or it can be called by using the del command. The advantage of the del instruction is you don't need to have the type of the object declared as you do to call the Del sub-procedure.

del Object
Object is the object to destroy.

When an object is deleted, any existing references to it are set to null. Therefore, if you have a variable referring to an object that could have been deleted, you should check to make sure it isn't null before trying to access any of the object's variables or procedures. If you don't, your proc will crash. Debugging techniques related to this issue will be discussed in section 19.3.2.

A very simply example of Del would make an object play a little death tune.

mob/Del()
   view() << 'death.wav'
   ..()

It is important to remember to call the parent proc, since it performs the actual deletion.

In practice, it is usually best to define a second proc (say Die) that handles a real death and reserve Del() for the abstract business of deleting the object. That way (for example) you can remove player mobs when they are not in use by saving them to a file and then deleting them. You probably wouldn't want them to appear to die each time they log off!

obj/corpse
   icon = 'corpse.dmi'
mob
   var/corpse = /obj/corpse
   proc/Die()
      if(corpse) new corpse(loc)
      src << "See you around!"
      del src

With this definition, we would call mob.Die() when we want a mob to die. By default, that just creates a corpse and deletes the mob. That behavior could easily be overridden for different types of mobs. For example, you might want players to remain connected but be penalized in some way.

2.3 Areas and Rooms

The creation and deletion of areas are handled somewhat specially. When the same area is placed in several positions, the actual area object is only created once. New() is only called the first time the area is created. From then on, new positions are simply marked as part of the existing area.

It is also possible to create areas that are not on the map. These are called rooms and may be used as locations for any number of other objects. (Note that density is ignored in rooms; it only applies to objects on the map.)

Rooms can be created with new by not specifying a location (or by passing null). Another even easier way is to define the area but never place it on the map. When you access the area at run-time, the room will be automatically created.

You could, for example, create a special room for players to enter when they die where they can do penance and meditate on the sins of their previous life.

area/Purgatory
   Enter()
      usr << "Welcome to [src]."
      return ..()

mob/proc
   Die()
      if(key) //players
         loc = locate(/area/Purgatory)
      else    //NPCs
         del src

You would probably also want to provide a way out, since the patience of players who have just suffered a gruesome death is often rather thin. A verb that teleports them back to the land of the living would do the trick. In both cases, the locate instruction is useful for getting a reference to the destination.

3. Stat proc

The Stat proc is used to display information about an object in some panels on the player's screen. These are called the stats. By default, the player only sees the stats of her own mob, but the designer could provide facilities for the player to see the stats of other objects.

Stat ()
src is the object being viewed.
usr is the mob of the player viewing.

Each stat panel consists of a number of lines. The lines each have an optional name and a corresponding value which is displayed next to it. To generate a line of output in the stat panel, one uses the stat instruction.

stat (Name,Value)
Name is the optional name of the line.
Value is the value to display.

The following example generates a typical panel of player stats.

mob/Stat()
   stat("description",desc)
   stat("")  //blank line
   stat("strength",strength)
   stat("health",health)
   stat("odor",odor)

To provide structure to the stats, a second instruction, statpanel, provides the ability to create multiple panels. It takes the name of a panel and an optional stat line to display. If no stat line is specified, the given panel becomes the default panel for subsequent output. If no call to statpanel is made, the default panel simply has the name "" (an empty text string).

statpanel (Panel,Name,Value)
Panel is the name of the panel.
Name and Value are the same as for stat().
Returns true if panel is currently visible to player.

One final nuance is that you can pass an entire list of objects as the value for a stat, in which case the objects are displayed in a list to the player. This is equivalent to looping through the list and generating an individual (unnamed) stat line for each item. This feature is most often used with statpanel to create a separate panel for the list.

The following example displays the player's description as well as an inventory and group list.

mob/Stat()
   stat(desc)
   statpanel("Inventory",usr.contents)
   statpanel("Group",usr.group)

One additional nicety would be to avoid generating the inventory and group panels if their contents are blank. You could accomplish that by checking usr.contents.len, a variable indicating the length of the list. This and other list details will be discussed in chapter 10.

The following example shows the framework for generating a typical multi-panel stat display.

mob/Stat()
   if(statpanel("Stats"))
      stat("health",health)
      stat("strength",strength)
   if(statpanel("Description"))
      stat("appearance",desc)
      stat("race",race)
   statpanel("Inventory",contents)

Notice the use of statpanel() in a conditional statement, making use of the fact that this procedure returns true only when the specified panel is visible to the player. This is not strictly necessary but it is more efficient, because it doesn't bother to fill panels that aren't visible to the player. Since the Stat proc may be called quite frequently to update the stats display, it is good to avoid extra overhead when possible.

4. Click and DblClick

The Click proc is called when the player clicks an object, and the DblClick proc is called by double-clicking. By default, nothing happens, so these procedures exist merely as hooks for your own use. More will be said about them in section 9.2.2.

Click (Panel)
DblClick (Panel)
Panel is the stat panel name.

The object may be clicked on the map or in the stat panels. If it was on the map, the panel argument is null.

The following example uses clicking to play sounds.

obj/instrument
  var/melody
  piano
    melody = 'entertainer.wav'
  trumpet
    melody = 'jazzy.wav'
  Click()
    usr << melody // play them tunes!

5. Login and Logout

When a player connects to a mob, the mob's Login() proc is called. When the player disconnects from the mob, the Logout() proc is called. Often, by the time Logout() is activated, the player is already disconnected. However, you can call Logout yourself if you want to force a player to disconnect.

Login()
Logout()

One common use of the Login() proc is to welcome players and place them at a starting location. By default, Login() places the player at the first available opening on the map.

turf/landing_pad
   name = "landing pad"
mob/Login()
   if(loc)  //reconnecting
      usr << "Welcome back, [name]."
   else
      usr << "Welcome, [name]!"
      loc = locate(/turf/landing_pad)

If there is a possibility of players reconnecting to existing mobs, it is best to check if the player is already at some location before making the initial placement as we have done here. If, on the other hand, you don't want player mobs to be retained when players disconnect, you could remove them in the Logout proc.

mob/Logout()
   del src

Alternately, you could just make the mob disappear by setting the location to null. That is a quick and easy way to "save" players when they log out. It won't, however, survive a shutdown of the world, which could happen if you need to reboot it after making code changes or (if there is a serious bug) the server crashes. To handle cases like that, you need to use a save file, which will be discussed in chapter 12.

6. Topic

An object's Topic proc is executed when the player clicks on a hyperlink that references that object. A hyperlink is a clickable region in the output text (usually underlined) that causes some action to take place when it is selected. In this case, the hyperlink is called a topic link because it contains information (called the topic) which the object may use to form a response.

First, you need to know how to embed a link to an object in some output text. Since hyperlinks were popularized by the web, DM uses the same syntax as an HTML web document. It is a little strange, but my mother always says an ounce of strangeness is better than a pound of competing syntaxes. So following that wisdom, we use the HTML anchor tag <A> to form a hyperlink.

"... <A HREF=#\ref[Obj]Topic> click here </A> ..."
Obj is the linked object.
Topic is the link topic.

The code \ref[Obj] is used to specify the object associated with the hyperlink. This is followed by whatever text you need in order to identify this particular link from others to the same object.

The player, of course, doesn't see the scary looking stuff inside the < >'s. All he has to do is click on the link to call the object's Topic proc with the hidden topic data.

Topic (Topic)
Topic is the link topic.

The following example uses topic links to form the skeleton for a conversational NPC.

mob/Noah/Topic(Topic)
   if(Topic == "weather")
      usr << "Looks a little stormy."
   if(Topic == "storm")
      usr << "I'd say about 40 days worth!"

mob/Noah/verb/hello()
   set src in view()
   usr << "Nice weather, eh?"

Object topics are just one type of hyperlink. More will be said on the subject in section 9.2.4 and chapter 11.