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.
1. Movement Procs
|
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
|
By redefining this proc, one could make a nasty trap.
turf/pit/Exit(O) O << "You are stuck in [src]!"
|
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 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.
2. Object Creation and Destruction
|
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.
|
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.
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.
2.3 Areas and Rooms
area/Purgatory
Enter()
usr << "Welcome to [src]."
return ..()
mob/proc
Die()
if(key) //players
loc = locate(/area/Purgatory)
else //NPCs
del src
3. Stat proc
|
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.
4. Click and DblClick
|
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.
5. Login and 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.
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.
6. 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.
|
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.