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.
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.
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.
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.
|
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
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.
|
By redefining this proc, one could make a nasty trap.
turf/pit/Exit(O) O << "You are stuck in [src]!"
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.
|
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.
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.
|
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.
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.
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.
|
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:
|
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.
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.
|
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.
|
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.
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.
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.
|
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.
|
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).
|
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.
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.
|
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!
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.
|
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.
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.
|
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.
|
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.