View the live web reference here.
The DM (Dream Maker) language uses a syntax similar to C/C++ to build networked multi-user worlds. This reference and the accompanying guide discuss the structure and function of DM.
This reference is arranged in sections:
You can filter the reference content by subject, which helps narrow down anything you might be looking for.
In various articles you may see notes or sidebar content:
⚠️ | Important note |
🔒️ | Security concerns |
🏃️ | Performance tip |
👍️ | Rule of thumb |
🏛️ | Compatibility |
💡️ | Did you know? |
🔧️ | Under the hood |
🎳️ | Play area |
Files specified in single quotes are loaded (at compile time) into the
world cache file (ending in .rsc
). These are referred to as
resource files. At runtime these files are downloaded by players into their
byond.rsc
file for future use. With the appropriate verbs or
through savefiles, players may also upload files into the world cache.
If a resource file is not used for a long time, it will be automatically removed from the cache file to save space. If a cache file gets too bulky, however, you may manually delete it and start from scratch.
To make compilation faster and to make it easier to distribute code, the
compiler will use an existing cache file if possible. That means you could
compile up a world, and send people the .dm
and
.rsc
files without any need to package all the individual
resource files. It is also possible to include additional supplementary
.rsc
files by using the #include
statement.
Comments may be used to explain code. They can also be used to turn off a line or block of code. All text inside a comment is ignored by the compiler.
The single line comment begins with //
and runs to the end of
the line.
The multi-line comment begins with /*
and runs until
*/
.
Multi-line comments may be nested.
At runtime, data objects are garbage collected. That means data which is no longer in use gets automatically deleted to free up system memory. This applies to text strings, lists, savefiles, datum objects, and so on.
The garbage collector works by using an efficient reference counting system. Once an item is no longer referenced by any variable, it gets deleted. For the most part, that frees you from having to think about memory allocation, which is wonderful, especially in the case of text strings, which tend to be allocated on the fly all over the place.
There are a couple provisos that you should note. One is that circular
references will never be deleted by the garbage collector. By circular
reference, I mean a pair of objects with variables that point to each
other, or even an object with a variable that points to itself. In rare
cases, you may even depend on this behavior. When you are done with such
objects, you should either null out the circular reference, or you should
forcibly destroy each object with the del
instruction.
An object with running or sleeping procs is referenced by the
src
variable of those procs and will therefore not be thrown out.
Another note is that the world.contents
list does not count as
a reference. Otherwise, /mob and /obj objects would never
be deleted, which is not the case. Note that objects which are contained by
another object or which contain objects themselves are referenced and
will not be deleted. That means an object must be at loc=null
with no contents and, of course, no other references anywhere in order to get
deleted by the garbage collector.
Mobs with a non-empty key and all objects with non-empty tag are also immortal.
Turfs and areas do not currently get garbage collected.
When the world shuts down, all objects are destroyed, whether they are referenced or not. You don't have to worry about system memory getting consumed by persistent objects. That doesn't happen.
In general, people who do not like reference counting garbage collection
should be happy that DM provides a del
instruction, allowing you
to take charge and delete things whether they are referenced or not. Another
nicety is that this automatically nulls out any existing references to the
object, so you don't end up with dangling references to a deleted object,
which can otherwise be a great source of instability and mysterious bugs.
An icon file may be referenced by putting single quotes around the
filename. The file extension determines the type of icon. Currently
supported icon types are .dmi
, .bmp
, .png
,
.jpg
, and .gif
. To create dmi icons, use the Dream
Maker icon editor. This allows you to make animations, 4 or 8 directional icons,
and icons with different states (such as "live" and "dead").
You can also load icons into memory at run-time and manipulate the graphical data to produce new icons dynamically. This is done by creating an /icon object.
Note: The following "arithmetical" methods of icon manipulation are being phased out in favor of the /icon object, which can be directly manipulated and which provides a wider variety of operations. Many of those in turn have been obviated by the color and transform vars.
There are several ways in which icons can be manipulated at runtime. They can be rotated, added together, and the colors components may be altered.
One purpose for such operations is to make players look different. Other interesting uses (and abuses) will undoubtedly follow.
The result of adding two icons is an arithmetic combination of the color components of each individual pixel. At positions where either icon is transparent, the result is also transparent. Subtraction, instead of increasing the intensity, decreases it by the amount in each pixel of the icon being subtracted.
Suppose you wanted to add together different bodies and heads. You could do that by making a few of each type with black backgrounds. When these add together, the black contributes nothing but prevents pixels in the other icon from getting clipped.
If you need to add the same color to every pixel, you can do so using a color value. Color values have the same format as in HTML: "#RRGGBB" with two hexadecimal digits for each color component. That gives you a range in color from 0 to FF (which is 255 in decimal).
You can also specify a color value as "#RGB". The single digit is automatically repeated, so "#F00" is the same as "#FF0000", which is bright red. For certain pre-defined color values, you can also specify a name, such as "red". See HTML colors for a list of color names.
If you prefer base 10, you can create color values with the rgb(R,G,B) instruction. Each parameter is in the range 0 to 255.
To increase (or decrease) the intensity of an icon multiplicatively, you
can use the '*
' operator.
Various mouse actions may be handled by defining procedures either on the client object or on the atomic object being manipulated. Any of the following procedures may be defined:
In general, define only the procedures you need, because extra communication overhead may be avoided when the compiler detects that you do not care about certain events.
The arguments used in mouse procs generally follow one of these forms:
The location argument varies with the type of control. For the map, it will be the turf where the mouse action happened. For info controls (statpanels), it will be the name of the statpanel where the action happened. For grid controls, it will be the cell where the action happened. For others controls it may vary, but most will leave this blank.
The control argument is the ID of the skin control where the action happened, such as "mappane.map" or "mainwindow.banner".
The params argument is text, and can be converted to a list using params2list(). It may contain any of the following properties, which will only be set if they are used:
The icon-x/y coordinates are integers, and try to point to the actual pixel in the icon before any atom transforms are done; i.e. if the icon were scaled up to 3 times its size using the transform var, then a 3×3 region of pixels would all have the same icon-x/y values. The lower left pixel of the icon is 1,1. The vis-x/y parameters are screen-based, and their origin (1,1) is wherever the lower left corner of the icon is rendered.
Note: vis-x/y will not be included in the parameters if they are the same as icon-x/y.
If the mouse is over an overlay, icon-x/y and vis-x/y are relative to the parent object, not the overlay icon itself, so it's possible to have value outside of the normal range of 1,1 to [width],[height].
The mouse pointer may be customized as well. The following variables all deal with the appearance of the pointer. They do not control what actions may be taken by the user, but they provide hints to the user about what actions may work.
When selecting a mouse pointer, you may provide your own custom icon or use one of the built-in pointers.
Note: Older games compiled prior to BYOND 4.0 had a different format for the MouseDown() and MouseUp() procs. These used icon_x and icon_y as arguments, but control and params have replaced them.
Note: Games compiled before version 514 did not have the button parameter, so they handled the left, middle, and right parameters differently. In old versions, only the button used in the action (left, middle, right) was included as a parameter; now all buttons being held or changed are included, and button is the mouse button that changed.
The following mouse pointers are built-in and may be assigned to any of the mouse pointer variables. Of course, you can also define your own custom mouse pointers using an icon file.
Variables that are not initialized have the value null. This value is
distinct from 0 and "". If you compare it to these, using the ==
operator, it is not equal. However, in a numeric context (like a mathematical
operation), null evaluates to 0 and in a text context (like insertion into a
text expression), null evaluates to "". In a logical expression, null, 0, and
"" evaluate to false and all other values are true.
In an embedded text expression, null behaves like "". That means, if you are expecting a variable to display a 0, you should explicitly initialize it to 0 rather than leaving it as null.
The preprocessor performs various transformations on source code as the DM compiler reads the file. It may be used to define macros—that is words which are replaced by other fragments of code. It is also possible to insert other source code files and to conditionally compile or not compile sections of code.
Preprocessor commands are called directives. They are placed on a line by
themselves and always begin with a hash symbol #
. The
preprocessor directives recognized by DM are the same as standard C compilers:
#define
#if
#elif
#ifdef
#ifndef
#else
#endif
#include
#pragma
#error
#warn
This macro indicates the minor version of the compiler, which is useful during BYOND beta testing. More often, you will want to use the DM_VERSION macro instead, which is the major version.
This macro indicates the version of the compiler. This could be useful when distributing code that uses new language features that would not compile in older compilers.
The __FILE__ macro expands to a string containing the name of the current source file. This may be useful when generating debugging error messages.
The __IMPLIED_TYPE__ macro is replaced by a reference to the type path implied at the current point in compilation. For instance, when using the new proc and assigning to a var, the type path for new() is implied by the var's type. Implied types are also automatically used in locate(), and are used by default for the second argument in istype().
__IMPLIED_TYPE__ is valid in the following situations:
This is actually a pseudo-macro; the preprocessor doesn't handle it directly.
The __LINE__ macro is replaced by the line number in the current source file. This may be useful when generating debugging error messages.
The __MAIN__ macro is defined in the main .dme
file being
compiled. In all other files included by this file, __MAIN__ is not defined.
The purpose of this is for library writers to package a small demo of their
library directly in the library source code. When users compile the library
directly, the library's own .dme
is the main file and can include
extra files that are not normally part of the library.
If the demo requires a lot of extra resources, it is probably better to
package the demo as a separate project. Small demos, however, are nice and
convenient using this "auto-demo" technique—especially since Dream
Seeker automatically launches Dream Maker after installing a library
containing a .dme
file.
The __PROC__ macro is replaced by a reference to the current proc being compiled. This may be useful when generating debugging error messages, especially when wrapped in nameof, e.g. nameof(__PROC__).
This is actually a pseudo-macro; the preprocessor doesn't handle it directly.
The __TYPE__ macro is replaced by a reference to the type path currently being compiled. This may be useful when generating debugging error messages. If used in a global proc, the value will be null.
This is actually a pseudo-macro; the preprocessor doesn't handle it directly.
The #define statement creates a macro that is substituted for Name.
Substitution only applies to whole words. Text inside of double or single
quotes is not processed for substitution, so "This is BIG."
would
not be modified even if a macro named BIG were defined. That is different
from "This is [BIG]."
, where BIG is an embedded expression, which
does get processed for macro substitution.
Note that it's usually important to use parentheses around any arguments you use in a macro. Otherwise strange results may occur if you use an expression such as 2+3. In the SQR(X) example, if there were no parentheses around each X then the expansion of the macro would be (2+3*2+3). Since the * operator has a higher precedence than + the result is 11, not 25 as expected. It's equally important to put parentheses around the entire macro for the same reason.
The last parameter of a macro can end in ... which means that it and all other arguments following it count as a single argument. This is called a variadic macro because it lets you use a variable number of arguments. The last parameter will also become optional.
In a macro's body, if you precede a parameter by #, the replacement value will be turned into a string. For instance, 2 would become "2".
A parameter preceded by ## in the macro body is substituted directly, without any spaces. If you use this with the last argument in a variadic macro, any preceding spaces and a comma (if found) will be removed if the replacement is empty.
Using ### in the macro body, preceded by a number, will repeat the replacement a certain number of times.
If DEBUG
is defined, source file and line number information
will be stored in the compiled .dmb
file. If a proc crashes
during execution and DEBUG
information is present, the current
source file name and line number will be indicated in the error output.
This option increases the size of the .dmb
, typically by about
10%. Execution of the code may also be a tiny bit slower.
If you are distributing the .dmb
to players and you do not
want them to have debug access at runtime, you should not compile in
debug mode.
If you want to use the run-time profiler (see the debugging options in Dream Seeker), you must compile in debug mode. Then you can get a report of CPU usage by your various procs.
This macro defines a search path to be used in evaluating resource files
(icons and sounds). First the current directory is searched, then the first
FILE_DIR
path, then the next, etc.
This searches for the file at the paths "./clown.dmi"
,
"./icons/clown.dmi"
, and "./icons/sounds/clown.dmi"
,
where "."
is the directory of the current source file.
The #error directive halts compilation and displays the specified message.
The #if
statement is used to conditionally compile code. If
Val is true (non-zero), the code following the #if
statement
will be compiled. Otherwise, compilation skips to the next
#elif
, #else
, or #endif
statement.
The function defined()
can be used in the conditional
expression. It is true if its argument is a defined macro (with
#define
) and false otherwise.
You can also use fexists() in a conditional to check if a file exists.
The #ifdef
statement is used to conditionally compile code. It
is equivalent to #if defined(Name)
.
The #ifndef
statement is used to conditionally compile code.
It is equivalent to #if !defined(Name)
.
The #include
statement causes the compiler to process another
file before continuing in the current source file.
If a file is included multiple times, only the first occurrence will be processed. That is a convenient addition to the standard C preprocessor, which DM otherwise emulates quite closely. If you want the file to be included more than once, put the line #pragma multiple somewhere in the file.
The file <stddef.dm>
is automatically included before
all other source code. You can view the contents of that file by creating a
file with that name in Dream Maker.
The BYOND lib directory is called "lib"
and is located in the
BYOND system directory (typically "\Program Files\Byond\lib"
).
If the file is not found there, it also looks in the user lib directory, which
would typically be "...\Byond\user\login-name\lib"
.
Note that the compiler interface allows you to include files graphically by
simply clicking the checkbox next to the file. This creates an include
statement for you in the .dme
project environment file. The only
time you would still want to manually include files is when you need to ensure
a certain order of processing. For example, if file "MyCode.dm"
overrides procedure definitions of an object defined in
"LibCode.dm"
, you should include "LibCode.dm"
at the
top of "MyCode.dm"
. Most other DM code is independent of order,
but overriding procedure definitions is not. The compiler will warn you in
such cases if you forget.
Another case in which you should manually include files is if you are
writing a library to be used by other programmers. Since the
.dme
file is not distributed with a library, all necessary
inclusions must be made in the .dm
files.
The #pragma directive alters the compiler's behavior in various ways.
Pragmas will not be inherited by libraries included in your project.
The #undef
statement removes a macro definition.
The #warn directive displays the specified message as a warning, but does not prevent the project from compiling.
A sound stored in a file may be referenced by putting single quotes around the filename. The file extension determines the type of sound. Currently supported music types include MIDI (.mid or .midi), and module formats .mod, .it, .s3m, .xm, and .oxm. Supported sound effect formats include .wav, .ogg, .mp3, .raw, .wma, and .aiff.
This example plays the specified midi file to all players.
Text consists of a string of characters enclosed in double quotes. To
place a quote inside a string, escape it with a backslash \
character. You will also need to escape a backslash if you want to use
one on purpose.
This example sends some text to the usr: He said, "Hi."
Backslashes are also used for special macros and to escape other characters that would normally be hard to include in a string. A backslash at the end of a line will ignore the line break, and continue the string on the next line after ignoring any leading spaces.
To insert a variable expression into a string, enclose it in brackets
[]
. These are referred to as embedded text expressions. An
object expression will display the object's name preceded by the text macro
\the
or \The
if no other article has been
specified. Capitalization of the article is inferred from context.
If this example is called by a mob named "Bill" with the text "hi everybody!", it will display "Bill: hi everybody!".
On the other hand, if it is called by a mob named "cat", it would display "The cat: hi everybody!".
Via operator overloading you can define an operator"" proc for an object to return different text when it's embedded in a string.
For lengthy text strings, DM provides a special text document
syntax. This begins with {"
and ends with "}
. It
may include multiple lines and even un-escaped double quotes, but it still
parses escape characters and embedded expressions.
DM also has a format for raw strings, which do not allow escape characters
or embedded expressions. This can be useful for many situations, especially
regular expressions which tend to use
characters that need escaping. There are three ways to specify a raw string.
All of them begin with the @
character.
Simple raw strings follow @
with a single-character
delimiter, usually "
but it can be almost anything, and end when
that delimiter is seen again. Line breaks are not allowed in simple raw
strings.
Complex raw strings use more complicated delimiters, but they let you
include line breaks. There are two ways to do this: One starts with
@{"
and ends with "}
, so it looks like the familiar
document string format. The other way starts with @(XYZ)
, where
XYZ
is any arbitrary text you want it to be, and ends when that
same text is encountered.
With a complex raw string, a single leading and/or trailing line break will be ignored.
Special characters may be inserted into text using HTML syntax. Such
characters are known as entities. They start with an ampersand and end with a
semicolon. The main reason for doing this is to insert characters that
otherwise have a special meaning. The most common entities have names. The
rest must be referred to by their Unicode character number (e.g. & is
the same as &). The common ones are listed in the following table.
Note that the same effect may be achieved by simply escaping the special
character (like \<
). The full entity syntax is included for
generality.
& | & |
< | < |
> | > |
" | " |
© | © |
Text macros start with '\' (a backslash) and end with a space or other punctuation.
The \icon macro is used to treat the following embedded expression (in []'s) as an icon rather than as text. An object, for example, would be replaced by its icon rather than by its name.
The \icon
macro expands internally to the <IMG> tag. The
above example, could be rewritten like this:
Note that the current icon state of the object is automatically used.
Also note that the image belongs to a class called icon
. That
allows you to configure the way icons are displayed by using a style sheet.
The following default style rule causes icons to be shrunk to 16 by 16
pixels so they fit in better with surrounding text:
You could override this setting globally in your own style sheet. You could even define rules to allow individual icons to be formatted differently from the rest.
With those rules in place, you could output a full sized icon by using the <BIG> tag:
The one time that one might want to use the <IMG> tag directly is to specify the ALT text to be displayed on clients which don't support graphical icons.
Specific states, directions, and frames of an icon can be displayed in lieu of the default through use of the following tags:
Note that the \icon macro does not work in the mini-browser; it is only for text output. To make icons appear in an HTML document, use browse_rsc() to send an icon to the client before using browse() to display it.
The \ref
text macro inserts a unique identification number
or text string for the following embedded object (inside []'s).
In older versions of BYOND, if an object had a tag, that was used instead. However this has often proved to be problematic, so anything compiled from version 512 onward should expect to output a reference number. If you want to use the tag, which stands a better chance of still being valid if the object is deleted and recreated (like in a world reboot), you can output the object's tag explicitly.
The primary use for object references embedded in text is in topic links. This allows you to encode a reference to an object in the href value of a hyperlink. (Just make sure the object does not get deleted before the user executes the link. See garbage collection.)
Topic links that contain a parameter "src" assigned to an object reference are treated somewhat specially. Unless you override client.Topic() to do otherwise, the default behavior is to call the referenced object's own Topic() procedure.
The above example uses an embedded reference to the player's own mob to
create a link to a topic handled by that mob's Topic() proc. The
href_list
parameter is simply the result of
params2list(href)
.
In that example, the embedded reference was automatically converted back into an object (dereferenced) for you. If you embed references to additional objects in the href data, you would have to dereference those yourself using the locate() instruction.
HTML tags, such as <font>
may be used to directly format
output text. Another approach, however, is to use HTML tags to specify
purely structural information and use a style sheet to define how various
elements within that structure should be treated. DM uses a
subset of the Cascading Style Sheet (CSS)
language, which was introduced for this purpose in HTML documents.
This section discusses the syntax of style sheets as an independent element. For information on how to include the style sheets in your DM code, see the section on client.script.
As an example of a style sheet, one might want combat and conversational
messages to appear differently—perhaps using different colors. Instead
of using the <font>
tag to color the text, you could use
<span>
to mark the beginning and ending of the text and
to specify what kind of message it is. The result might be text such as the
following:
The class
attribute may be used with any tag, but
span
and div
are often convenient because they
have no other side-effect but defining the style class. span
is for text within a single paragraph and div
is for whole
paragraphs. The way text belonging to a particular class is formatted may be
controlled in a style sheet such as the following:
This says that text in the combat class should be colored red and text in the chat class should be colored green. These classes are not pre-defined; you can create whatever new style classes you need. (The color names are predefined however. You can find a list of them in HTML colors.
The advantage of using style sheets instead of direct formatting tags is that you can cleanly separate structural information (such as combat and conversational messages) from formatting information (such as red and green text). By separating the two, you or the player can easily plug in different formatting schemes without changing any of the actual content.
A style sheet is composed of a list of rules, such as the two rules in the preceding example. Each rule contains one or more selectors followed by a body of attribute assignments (in braces). The selector specifies the context of the rule and the body specifies the format.
A selector may specify a container tag (such as span
,
body
, or p
) and a class. The above example could
have been written with a selector of span.chat
. However, by
leaving out the tag, it applies to any tag with class=chat
. It
is also possible to only specify the tag and not the class. In that case,
the selector applies to any matching tag, regardless of class.
To specify a nested context, several simple selectors may be listed one after the other. For example, emphasized text within a combat message could be enlarged with the following rule:
It is also possible to list several selectors separated by commas in order to make them all apply to the same body. For example, this next rule is equivalent to the two following ones:
The style rule body contains a list of attribute assignments, delimited by semicolons. Each assignment takes the form of an attribute name, followed by a colon, followed by the value of the attribute. The following table summarizes the recognized attributes and their possible values.
color | #F00, #FF0000, red, rgb(255,0,0), rgb(100%,0%,0%) |
background | |
font-size | 10pt, 1.5em, 150% |
font-style | normal or italic |
font-weight | normal, bold, lighter, darker, or 100 to 900 |
font-family | monospace, sans-serif, serif, cursive, ... |
font | style weight size family |
text-decoration | none, underline |
text-align | right, left, or center |
vertical-align | top, middle, bottom |
text-indent | 0.25in, 3em, 20pt |
margin-left | |
margin-right | |
width | 16px, 32px, auto |
height | |
line-height | 1.2 |
The font
attribute is a special short-hand for assigning
font-size
, font-style
, font-weight
,
and font-family
in one statement. Any properties that are not
specified in the font
statement are assigned to their default
values.
The font family may be a specific font name or a more general category such as monospace or sans-serif. Since not all users necessarily have the same fonts installed, it is a good idea to list alternate fonts. The desired font is placed first, followed by other possible fall-backs, each separated by a comma. Usually a general family such as monospace is listed last of all. Any font names containing a space should have quotes around them.
The following example sets the font for the <body>
tag. Even if you don't explicitly use <body>
in output
text, it is applied implicitly.
This sets the font to 12 point and selects Times New Roman
if
it is available and otherwise falls back on a system-determined sans-serif
font. This command also implicitly specifies not to use italics and to use a
normal font weight (not bold).
Font sizes may be specified in points (1pt = 1/72 of an inch), picas (1pc = 12pt), pixels (px), inches (in), centimeters (cm), and millimeters (mm). There are also various levels corresponding to the old 1 to 7 HTML scale. These are xx-small, x-small, small, medium, large, x-large, and xx-large. In addition to these absolute font sizes, it is possible to use a relative size, such as 150% or equivalently 1.5em (1em = 100% of the current font size). This scales the font relative to the currently active font setting.
In addition to regular classes, there are special pseudo-classes for
handling embedded hyperlinks. These are specified in the selector with the
class starting with a colon rather than a dot. They are :link
,
:visited
, and :active
. These only apply to the
<a>
tag. The :link
class applies to
hyperlinks in their normal state. Once a link has been clicked, it belongs
instead to the :visited
class. When the user holds the mouse
over a link, it temporarily belongs to the :active
class. The
only attribute that may change in an active or visited link is the text
color.
Paragraphs can be given different margins according to your preferences.
The margin-left
attribute controls the left margin, and
margin-right
is the right margin. You can use specific sizes
like inches or points, or a relative size unit like em or ex. (A percentage
is interpreted so that 100% is 1em, not the width of the window.) Using the
text-indent
attribute will indent the first line of a paragraph
from the left margin. It is possible to create a hanging indent by using a
negative value for text-indent
, like so:
The background attribute is only relevant to the body context. It causes the entire terminal background to change color. When doing this, it is usually necessary to change the foreground colors of text or it may become unreadable. The various standard classes of output generated by DreamSeeker are in the following table.
system notice | general notices from the client |
system command echo | command echoing |
system command expansion | command-line expansion list |
system pager | pager messages |
system irc | IRC command prefix |
The value of the CLASS attribute may contain a list of classes separated by spaces. This permits client output to be in the 'system' class as well as more specific ones. That allows you to change all of these colors in one shot if you are too lazy to change them each individually. For example, if you define a style sheet that changes the background color, you might need to redefine the various foreground colors like this:
In this example, the background color of the terminal will be aqua, normal text from the server will be black, and all output from the client will be bold and red, except echoed commands and expansion lists, which will be bold and green. The more specific .command rule is placed after the general .system rule so that its color takes precedence. This is how style sheets are composed—you write general rules first followed by any exceptions.
The order in which rules are specified is one of the factors that determines precedence of style sheet commands. The language is known as Cascading Style Sheets because of its ability to handle several layers of stylistic rules, intermingling the configurations of the user and the designer in an ordered fashion.
Rules are selected by first finding all matching candidates for a given
attribute in the current HTML tag being processed. If there is more than
one, rules from a higher level style sheet take precedence over lower level
ones. That means the basic user configurable settings in DreamSeeker are
the lowest priority, followed by a style sheet in the user's
.dms
script file, followed by a style sheet from the designer's
client.script
setting, because that is the order in which these
are read by the style sheet manager.
Rules from the same style sheet are ordered by specificity. The selector
span.chat
is more specific than .chat
and
.chat em
is more specific than em
. In general,
the more classes referenced by a selector, the more specific it is. When
that results in a tie, the selector with the greater number of tags takes
precedence.
If two rules about the same attribute come from the same sheet and have the same specificity, the final one to be defined takes precedence.
In the rare event that a rule needs to break out of the normal order of precedence, it can be flagged as important. In this case it will take precedence over all other "unimportant" rules. However, if more than one rule is important, the normal rules of precedence will be used to resolve the conflict.
The important flag is applied after the attribute assignment like this:
In the above example, only the background color is important, not the font specification.
Style commands may also be inserted directly in an html tag to control its appearance. This does not have the advantages of style sheets, which separate content from presentation, but it does allow you to use the style sheet syntax when formatting text.
The following example uses the style attribute to color some text:
As you can see, the style
attribute of any tag can be
assigned to a text string containing a list of attribute assignments. Just
the body of the style rule is given, since no selector is needed to match
the current context.
The atom.maptext var supports some additional CSS attributes.
vertical-align | top, middle, bottom |
text-shadow | x-offset y-offset blur color |
-dm-text-outline | width color style |
Additionally, you can use the :hover pseudo-class to change the color of a link. As with other link pseudo-classes, only the text color can currently be changed.
Text tags (also known as elements by snooty HTML purists) control
how the text is formatted. HTML syntax is used, so all tags start with
<
and end with >
. The tags which are
currently supported by Dream Seeker, are listed below:
<A></A> // anchor (hyperlink) <ACRONYM></ACRONYM> // acronym or abbreviation <B></B> // bold text <BIG></BIG> // one size bigger text <BODY></BODY> // body of html document <BR> // line break <CITE></CITE> // citation reference <CODE></CODE> // program source code <DFN></DFN> // definition <DIV></DIV> // used in conjunction with style sheets <EM></EM> // emphasized text <FONT></FONT> // font face, color, and size <H1></H1> // heading level <H2></H2> <H3></H3> <H4></H4> <H5></H5> <H6></H6> <HEAD></HEAD> // document head section <HTML></HTML> // html document <I></I> // italic text <IMG></IMG> // display icons <KBD></KBD> // keyboard input <P></P> // paragraph <PRE></PRE> // pre-formatted text <S></S> // overstrike text <SAMP></SAMP> // sample output <SMALL></SMALL> // one size smaller text <SPAN></SPAN> // used in conjunction with style sheets <STRONG></STRONG> // strongly emphasized text <STYLE></STYLE> // contains a style sheet <TITLE></TITLE> // document title <TT></TT> // typewriter style <U></U> // underline <VAR></VAR> // variable name <XMP></XMP> // preformatted (tags ignored)
In addition to these, the <BEEP>
tag, which is not
standard HTML, may be used to beep the terminal.
Some tags take additional parameters, known as attributes. The most common
ones are <FONT>
and <A>
.
The syntax for these is illustrated by the following two examples:
As many attributes may be specified as desired. The attribute value may have quotes around it, but this is only necessary if the value contains spaces. It is usually more convenient to use single quotes so you don't have to escape the double quotes, but you can also embed the HTML in a text document to avoid the need for escaping quotes.
When applying color to text, you can use hexadecimal RGB or you can use one of the named HTML colors.
Text sizes range from 1 to 7, 1 being the smallest and 7 being the largest. In addition to absolute sizes, relative sizes may be specified (like +1 for one size bigger or -1 for one size smaller).
This is a list of all global variables. The items in the list are the variable names. If the variable name is used as an index into the list, the value of that variable is accessed.
This example displays all global variables. The global
keyword is used here to distinguish it from src.vars
, which in
this example would be the mob's vars.
Areas are derived from /area, which derives from /atom. Regions on the map may be assigned to an area by painting it onto the map. Areas off the map serve as rooms that objects may enter and exit.
For each area type defined, one area object is created at runtime. So for areas on the map, all squares with the same area type belong to the same instance of the area.
Additional instances of rooms may be created from the same type by explicitly creating them with null as the initial location. That is, the first argument to new() should either be null or left unspecified.
The following example defines the area prototype /area/outside. It also defines an action to be taken when somebody enters an area, namely to display its description.
Built-in area procs:
Areas that are not located on the map are referred to as rooms. When a player enters one, the map goes away and you have something like a text MUD. By default, there would be no way for players to move from one room to another, so you have to handle movement yourself.
You can check the variable area.x to see if a given area is on the map or not.
The following example puts players in a room when they log in and provides a single exit.
Built-in area vars:
The default parent_type of /area is /atom.
The /atom object type is the ancestor of all mappable objects in the game. The types /area, /turf, /obj, and /mob are all derived from /atom. You should not create instances of /atom directly but should use /area, /turf, /obj, and /mob for actual objects. The /atom object type exists for the purpose of defining variables or procedures that are shared by all of the other "physical" objects. These are also the only objects for which verbs may be accessible to the user.
/atom is derived from /datum, so it inherits the basic properties that are shared by all DM objects.
The /atom/movable object type is the ancestor of all mappable objects that are capable of motion. The types /obj and /mob are derived from /atom/movable. You should not create instances of /atom/movable but should use /obj and /mob for actual objects. This object definition exists solely to define variables and procedures related to motion.
Built-in movement procs:
The following behavior only applies to LEGACY_MOVEMENT_MODE. In other movement modes, src.Cross(O) returns 0 by default if src and O are both mobs in the same group.
If src completely covers the turf it is standing on, Cross() is called as part of turf.Enter(). This is to preserve the behavior of older games, which expect turf.Enter() to care about its contents.
If src and O are both mobs, and O is in src's group, overlap is allowed unless neither of them use pixel movement. Older games that do not use pixel movement expect that Bump() will be called, and by default Bump() will swap the mobs' positions. Swapping obviously only works in situations where a mob takes up a whole tile and only moves by tiles; for all other situations, allowing an overlap makes more sense.
Any Move() is either a slide or a jump. Normal walking around is a slide; it can be stopped partway. A jump is pass/fail. See more information below.
This is what happens by default:
oldloc.Exit(src) is called for any turfs or areas being vacated, or the container if moving out of an obj or mob. neighbor.Uncross(src) is called for any atoms that will no longer be overlapping this object. If any of these return 0 (failure), movement fails.
newloc.Enter(src) is called for any turfs or areas that may be entered for the first time, or the container if moving into an obj or mob. neighbor.Cross(src) is called for any atoms that may be in collision with this object if the move fully succeeds. If any of these return 0 (failure), then a slide can be cut short but a jump will fail completely.
If any obstacles were encountered via Enter() or Cross() failing, then src.Bump(obstacle) will be called for each of them.
If movement did not fail completely, then loc and step_x/y, will be changed, and the following calls will be made: oldloc.Exited() for any turfs, areas, or other containers vacated; neighbor.Uncrossed() for any movable atoms no longer overlapping; newloc.Entered() for any turfs, areas, or other containers being entered for the first time; and neighbor.Crossed() for any movable atoms now overlapping the object.
A movement is considered a slide if src is moving from one turf to another on the same z level, and the total pixel distance is less than either src.step_size or a full tile size (whichever is largest). Any other movement is a jump. Movement to the same turf with no step_x/y change is also considered a jump.
Built-in movement vars:
Setting this to 0 causes movement between two adjacent positions to be displayed as a single discrete jump. Otherwise, objects will be made to glide from one position to another, using the movement animation defined in the icon file if one is defined.
By default, movement animation avoids cutting corners, since this can look very bad in some games. If you want objects to take the shortest (and smoothest) visual path when moving around, use SLIDE_STEPS instead of the default FORWARD_STEPS. This also allows the object to be facing in a different direction than it is moving, so make sure this is what you want.
SYNC_STEPS is intended for objects that move in unison as part of a larger "conglomerate" object. You should set the movement animation to SYNC_STEPS on all but a single "head" object, which will serve as the leader when choosing pixel step sizes. If you do not use SYNC_STEPS, there are cases where the pixel offsets of objects may get out of sync during motion, causing the object to visually break up. Because of the advent of features like big icons, this value is no longer of much use.
Note: This setting has no impact when used with pixel movement, except in special cases. See Gliding for more details.
This var defines the left side of the physical atom's bounding box, in pixels. By default all atoms are assumed to be one tile in physical size.
The left edge of the bounding box starts bound_x pixels inward from the left edge of the atom's icon (as affected by step_x). A bound_x value of 4 means the atom has 4 pixels of empty space to its left.
Example: A 16×16 smiley face centered in a 32×32 icon should have a bound_x value of 8, since there are 8 pixels of empty space to the left.
This var defines the bottom side of the physical atom's bounding box, in pixels. By default all atoms are assumed to be one tile in physical size.
The bottom edge of the bounding box starts bound_y pixels inward from the bottom edge of the atom's icon (as affected by step_y). A bound_y value of 4 means the atom has 4 pixels of empty space below it.
Example: A 16×16 smiley face centered in a 32×32 icon should have a bound_y value of 8, since there are 8 pixels of empty space below.
This var defines the width of the physical atom's bounding box, in pixels. By default all atoms are assumed to be one tile in physical size.
Example: A 16×16 smiley face centered in a 32×32 icon should have a bound_width value of 16.
This var defines the height of the physical atom's bounding box, in pixels. By default all atoms are assumed to be one tile in physical size.
Example: A 16×16 smiley face centered in a 32×32 icon should have a bound_height value of 16.
The default value depends on world.icon_size and world.map_format. In a topdown or tiled map_format, the icon height specified in world.icon_size is used. In other modes, height is irrelevant and tile "footprints" are square, so the icon width is used.
Note: The way this setting is used depends on world.movement_mode. See Gliding for more details.
This controls the number of pixels an object is moved in each footstep during animated movement. The default value of 0 chooses automated control over this value, which generally results in a minimum footstep of 4 pixels that is increased when necessary to keep up with motion on the turf grid.
Decimal values are allowed.
Be careful about using small glide sizes. Icons with high contrast pixel-level detail can look pretty ugly when displaced by short distances.
The glide size is measured in server ticks. If you use a different client
tick rate by altering client.fps
or client.tick_lag
,
the actual glide used will be scaled appropriately. E.g., if your
client.fps
is 4 times greater than world.fps
, the
actual glide amount each client tick will be glide_size/4
.
This was renamed from pixel_step_size.
This read-only var tells which turfs are being physically covered by the atom's bounding box. The purpose of this is for cases where you set the atom's bounds to change its physical size so that it ends up covering more than one turf.
This is different from the loc var in that every atom still has only one "true" location. A movable atom may cover multiple turfs, but only one turf is its loc. The loc var can be thought of as an anchor point, while the actual physical footprint is in locs.
For every turf in locs, this atom will also be in that turf's contents list.
If loc is not a turf, it will be the only item in the locs list. If loc is null, locs will be empty.
A datum (not derived from /atom) containing information about a particle set attached to this atom. See Particle effects for more information.
Resetting this var to null after it has contained a particle set will cause any existing particles on this object to immiediately disappear.
Renamed to glide_size.
This is a text string that controls where an object that is listed in client.screen will appear on the user's screen. The format is:
The bottom left corner of the map viewport (southwest) is "1,1". If the view is 11x11, then the top-right corner (northeast) is "11,11". (Changing world.map_format may change the range for screen_loc.)
A range of coordinates (the second format above) causes a square region to be filled with the object at each position. The southwest and northeast corners of the box are indicated in the screen_loc value.
The edges of the map may also be referenced by using directions, such as
"3,NORTH"
. For convenience, the order of coordinates is arbitrary
when using directions, so one may specify y before x as
in "NORTH,WEST"
. In expressions such as the latter, you may also
leave out the comma. Icon size is not taken into account, so a big icon with
a screen_loc of "SOUTHEAST"
will extend further to the right and
may create a border (see "Outside the map" below).
The CENTER keyword can also be used. This can be used alone to completely center the object, or as either the x or y component. If the map covers an even number of tiles in either direction, pixel offsets will be applied automatically. Centering is relative to the regular map edges, and it does not take the icon's size into account.
The LEFT, RIGHT, TOP, and BOTTOM
keywords (also TOPLEFT, TOPRIGHT, BOTTOMLEFT,
BOTTOMRIGHT) can be used to anchor a screen object to the edge of
the map control if the map is zoomed in so that some pixels are cut off. When
you use these edge-alignment keywords, the icon size is taken into
account, and the alignment of the icon changes to fit whichever edge you use.
Because these keywords do not conform to the normal tile-based structure of
the HUD, they can't be used for a range of tiles with the "to"
format.
Note: Letterboxing, the blank space to either side of the map if it doesn't take up the whole map control, is not considered usable space. HUD objects aligned to the control edge appear inside any letterboxing, not on top of it.
In addition to objects inside of the map view, one may create border objects. Borders are automatically created when screen objects are placed at coordinates outside of the inner map view. For example, objects placed at y=0 fall on a border directly below the map and y=-1 is one tile below that. (The CENTER keyword is based on normal viewport bounds and not any extra borders.)
A big icon placed towards the northeast end of the map, if it spills over the edge, will create a border big enough for the whole icon to be shown. You can avoid this by using the TILE_BOUND appearance flag. Transforms on this atom are not taken into account when determining whether to add a border.
Offsets may be applied to screen_loc coordinates. For example,
"NORTH+1,WEST"
is in a border above the map.
"CENTER+2,CENTER-1"
will appear 2 units right, 1 unit down from
the center of the map.
Non-integer offsets like 1.5 are allowed; the fractional part will be counted towards a pixel offset.
Offsets may be specified in percentages as well. These effectively always
count as pixel offsets and will never be used to determine if a border should
be added. "WEST+100%"
and "100%"
are basically
identical to "EAST"
in most respects, and "WEST+50%"
is basically the same as "CENTER"
. If you're using the edge
keywords (LEFT, TOP, etc.), this percentage is relative to
the control edge and also factors in the icon size, so "LEFT+100%"
is equivalent to "RIGHT"
.
It is also possible to specify a pixel offset. Screen objects do not use pixel_x and pixel_y for this purpose, because it is intended that an object could exist on the map and in the screen object list simultaneously, so positioning must be independent. Pixel offsets are specified after a colon like this: "1:16,1:16". In this case the object is shifted to the northeast by 16 pixels.
Screen objects on a plane will appear above non-screen objects on the same plane regardless of layer, except that BACKGROUND_LAYER or EFFECTS_LAYER may be used to move the objects forward or back.
You can use HUD objects in any additional map controls that might appear in game's skin file. If you have a second map named map2 for instance, then you can use "map2:1,1" or something similar as a screen_loc. If the map control is set to automatically scale to fit its contents, it will try to show every object you put there.
Note: For secondary-map HUD items, you should not use the full window.control ID, just the id of the control itself. Map controls should always have a unique id.
This var defines how fast, in pixels, an atom will move by default. If you use lower values of step_size for most items in your world, you may want to consider raising world.fps (at higher performance cost).
When Move() is called by a step or walk, or by the built-in client movement verbs, a change of step_size is applied to step_x and/or step_y. Any movement within the speed of step_size, or up to one tile in distance (whichever is greater), is considered a slide and may partially succeed if an obstacle is bumped before reaching the final position.
This var defines the position of the atom (in pixels) relative to its tile, on the x axis. A step_x of 5 means the atom actually is shown 5 pixels east of the tile's western edge.
The atom's actual bounding box may not begin at step_x, but can be set even further in via bound_x.
Example: A 16×16 smiley face centered in a 32×32 icon should have the following bounds:
For the smiley to appear at the left edge of the tile it is standing on, it would need a step_x value of -8. A step_x value of 8 takes it all the way to the rightmost edge of the tile. Anything outside of the range -8 to 8 will have the atom straddling multiple turfs.
This var defines the position of the atom (in pixels) relative to its tile, on the y axis. A step_y of 5 means the atom actually is shown 5 pixels north of the tile's southern edge.
The atom's actual bounding box may not begin at step_y, but can be set even further in via bound_y.
Example: A 16×16 smiley face centered in a 32×32 icon should have the following bounds:
For the smiley to appear at the bottom edge of the tile it is standing on, it would need a step_y value of -8. A step_y value of 8 takes it all the way to the top edge of the tile. Anything outside of the range -8 to 8 will have the atom straddling multiple turfs.
Built-in atom procs:
This proc is called by the default client.Click() procedure.
The following example allows the player to walk to a position by clicking it.
This proc is called by the default client.DblClick() procedure.
This example allows the player to teleport to a position by double clicking it.
Areas, objs, and mobs will always permit anything to enter by default.
The following behavior only applies to LEGACY_MOVEMENT_MODE. In all other movement modes, the turf's contents are not taken into account. Only the result of turf.Cross() matters.
Turfs will return 1 (permit) or 0 (deny) based on density. In simple terms, if the atom that is entering is dense, then the turf will deny entry if the turf itself or its contents (any that take up the full tile) are dense.
What actually happens in turf.Enter() is more detailed: Cross() is called for the turf, and if it succeeds (movement is permitted), then Cross() is called for any atoms in turf.contents that cover the entire tile. If any Cross() call fails, Enter() fails too and will return 0.
If a mob is standing on a turf but its bounding box does not cover the whole tile, it is ignored by Enter(). Instead, its Cross() proc is called if there is a danger of the object overlapping it.
The mob's Entered() and Exited() procs can be used to control what happens when objects are added or removed from the mob's inventory. Of course that could all be done within get() and drop() verbs, but the following code separates user interface from lower-level functions.
To see the advantages of this arrangement, imagine that there are certain situations in which an object may be created directly within the mob's inventory without the mob picking it up. You can still run it through your normal movement rules without calling get().
By default, every atom returns 1 to allow exit, except for turfs which call Uncross() to handle it for them.
The following behavior only applies to LEGACY_MOVEMENT_MODE. In all other movement modes, the turf's contents are not taken into account. Only the result of turf.Uncross() matters.
In the default Exit handler for turfs, Uncross() is called for the turf itself and then Uncross() will also be called for any atoms in turf.contents that cover the entire tile. If any Uncross() call fails, Exit() fails too and will return 0. In games using pixel movement, Uncross() is usually called separately, but this allows projects using tile-based movement instead to benefit from Cross() and Uncross().
This is called when a mouse button is pressed while pointing to this object.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Most operations can be done through Click(), DblClick(), and MouseDrop(). The other procedures are simply available for completeness.
Note: In BYOND 3.5 this procedure took three different arguments: location, icon_x, and icon_y. Since icon_x and icon_y have been replaced, old code will need to be modified. Games compiled before this change will still work normally.
This is called while dragging this object by pressing and holding the left mouse button over the object and moving the mouse. The over_object may be null if dragging over a stat panel or over other empty space.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Most operations can be done through Click(), DblClick(), and MouseDrop(). The other procedures are simply available for completeness.
This is called when the a mouse button is released after dragging this object. The over_object may be null if dropping over a stat panel or over other empty space.
This is called when the mouse moves onto the object with no buttons pressed.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Defining it on only the objects that require it reduces overhead.
This is called when the mouse moves off of an object with no buttons pressed.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Defining it on only the objects that require it reduces overhead.
This is called when the mouse moves over the object with no buttons pressed. When the mouse moves over for the first time, MouseEntered() is called instead.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Defining it on only the objects that require it reduces overhead.
This is called when a mouse button is released while pointing to this object.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Most operations can be done through Click(), DblClick(), and MouseDrop(). The other procedures are simply available for completeness.
Note: In BYOND 3.5 this procedure took three different arguments: location, icon_x, and icon_y. Since icon_x and icon_y have been replaced, old code will need to be modified. Games compiled before this change will still work normally.
This is called when the mouse wheel is moved while over an object.
Positive values of delta_x and delta_y refer to scrolling right or up, respectively. Negative values are left and down, respectively.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Defining it on only the objects that require it reduces overhead.
By the time New() is called, the object has already been created at the specified location and all of its variables have been initialized. You can perform additional initialization by overriding this procedure.
Since the initial location parameter passed to new()
is
applied before New() is even called, there is some special handling of the
loc variable when using named arguments in a call. Normally, if a
procedure is overridden, named arguments in a call are matched against those
in the the overridden definition. In this case, however, the loc
parameter name is hard-coded. Regardless of what you call the first argument
in your definition of New(), the initial location will be taken from the first
positional argument, or from the argument named loc if there are no
positional arguments.
The following example does some extra initialization that is not possible in the variable definition section, because it requires a runtime evaluation. This is a common reason to use New().
The following code could be used to display a player's current status.
Built-in atom vars:
Controls the opacity of the icon displayed on players' screens. A value of 128 means the atom is half-transparent, so it will have a ghostly appearance. This can be used to fade an atom in and out, especially when combined with animation. Alpha is also applied to maptext.
Overlays and images will also be affected by alpha, unless they use the RESET_ALPHA value in appearance_flags.
Every atom or image has an appearance, which controls all of the values relating to how it appears on the map. (The overlays and underlays lists are lists of appearances.) When read, this var provides a copy of the current appearance.
This value can also be used to change an atom's appearance, altering multiple values at once. Setting atom.appearance to another appearance will change all of the following values to match:
Other vars that are technically part of the appearance, but don't make any sense to change when cloning, are not changed. These include density, dir, screen_loc, and verbs. However, those vars ARE copied when you assign a /mutable_appearance.
If you set atom.appearance to another atom, the other atom's appearance will be copied.
The appearance_flags value controls miscellaneous behavior of an atom or appearance that doesn't make sense to handle in any other var.
These values are bitflags, and can be combined with the + or | operator.
Gliding is normally done by Euclidean (straight-line) distance, so diagonal gliding across a square tile takes about 41% longer, since the distance is multiplied by sqrt(2). With LONG_GLIDE, the dominant X or Y direction of the glide is used to adjust the glide size so it takes just as long as if the object were gliding in a cardinal direction.
These flags cause this overlay not to inherit aspects of its parent object. Ordinarily, transforms on a parent get applied to overlays too, as does color.
All of these flags are ignored if KEEP_TOGETHER is used on the parent (and this object does not override with KEEP_APART), since then the parent icon along with all of its overlays get drawn to a single surface and color, transform, etc. are applied afterward.
The value of client.color is normally applied to all icons. This flag says that the icon is an exception. Generally client.color has been superseded by the use of plane masters anyway.
The NO_CLIENT_COLOR flag is inherited by overlays and images automatically unless they have the RESET_COLOR flag. It is also basically meaningless when used on an overlay that's inside of a KEEP_TOGETHER group, since the client's color is applied to the entire group.
This flag is used to force the overlays and underlays of this icon (its "children") to be drawn with it all at once, not each icon individually. One reason you might want to do this is if your player's icon uses overlays for hair and equipment, and you want to change the alpha value to make them fade out. With regular drawing, changing the parent icon's alpha means that each individual icon becomes translucent; with KEEP_TOGETHER, the whole combination fades as one unit. Because this incurs some small overhead, it should be avoided for atoms that do not need it.
Any child appearances underneath KEEP_TOGETHER use NO_CLIENT_COLOR automatically, and RESET_COLOR, RESET_ALPHA, and RESET_TRANSFORM become meaningless. Use KEEP_APART with them if you want to use those flags.
Icons that are in a different plane from the parent icon will automatically have KEEP_APART set and therefore won't be included.
If this appearance is a child of something that uses KEEP_TOGETHER, it will be separated out from the main icon and drawn separately. This may be useful for things such as health meters, for instance.
This flag is automatically applied to icons that are on a different plane from their parents.
Use this flag to group all icons in the same plane and draw them on a temporary surface the size of the whole screen, and then that image is drawn over the existing scene. This is useful for post-processing effects, like lighting. The plane master's icon is not drawn, but its color, transform, and blend_mode are all taken into account when drawing.
In the example, all objects in plane 2 are lights. They're added together, and then the whole image is put through the color matrix, then multiplied over the rest of the scene below. This will darken everything that doesn't have a spotlight overlay, but anywhere a spotlight exists will have a circle of light.
The example also makes a point of adding full alpha to the plane, because a plane is fully transparent by default. However, it's usually a better idea not to mess with the alpha color, and instead use another icon, scaled to the appropriate size, as a backdrop.
The mouse_opacity set by the plane master will determine how the mouse interacts with objects on the plane. A value of 0 will mean everything in the plane is invisible to the mouse; 1 means the plane is mouse-invisible but the objects in it use their own mouse_opacity. 2 is the same except the plane itself is mouse-visible.
There are many ways an object may be shifted out of the normal bounds of the tile it's on: a large icon, pixel offsets, step offsets, and transform. Ordinarily it's desirable to be able to see the object if it touches any visible turf. However, in some cases it's more desirable to only show the object if its actual loc is in view. The TILE_BOUND flag will accomplish that. This flag is inherited by images and overlays.
Normally if an icon is transformed via atom.transform, it uses bilinear texture sampling which produces a nice smooth effect. If you want a granular pixel-art effect instead, PIXEL_SCALE will do that for you by using nearest-neighbor sampling.
If this object has a render_source it will take on the rendered appearance of another object (source). This is just a visual copy, so the mouse still interacts with this object, not the source. The PASS_MOUSE flag causes any mouse interaction to happen with the source instead of this object.
This flag indicates this atom is locked to the tile grid as it would be in TILE_MOVEMENT_MODE, regardless of the setting of world.movement_mode. In this way, pixel movers and tile movers can coexist.
[* This blend type appears only when using graphics
hardware mode. It is also not visible in the map editor.]
[† Since the alpha of the icon underneath is used for
alpha masking, mouse hits take it into account.]
Controls the way the atom's icon is blended onto the icons behind it. The blend mode used by an atom is inherited by any attached overlays, unless they override it. BLEND_DEFAULT will use the main atom's blend mode; for the atom itself, it's the same as BLEND_OVERLAY.
BLEND_OVERLAY will draw an icon the normal way.
BLEND_ADD will do additive blending, so that the colors in the icon are added to whatever is behind it. Light effects like explosions will tend to look better in this mode.
BLEND_SUBTRACT is for subtractive blending. This may be useful for special effects.
BLEND_MULTIPLY will multiply the icon's colors by whatever is behind it. This is typically only useful for applying a colored light effect; for simply darkening, using a translucent black icon with normal overlay blending is a better option.
BLEND_INSET_OVERLAY overlays the icon, but masks it by the image being drawn on. This is pretty much not at all useful directly on the map, but can be very useful for an overlay for an atom that uses KEEP_TOGETHER (see appearance_flags), or for the layering filter.
Controls the color of the icon displayed on players' screens. This color is multiplied by the icon, so that a white icon will become this color. The color multiplier is also applied to maptext.
If you include an alpha component in the color, the atom's alpha var will be set at the same time.
Overlays and images will also be multiplied by this color, unless they use the RESET_COLOR value in appearance_flags.
The color value can be set to a color matrix, which is a list of values. This allows for more complex transformations such as adding or subtracting color, inverting the color, turning to grayscale, shifting hue, etc. Using an RGB-only color matrix will include the existing alpha value in the matrix.
When reading the color var, if it is a matrix it will be read out as a list with 20 items (full RGBA format). That list is a copy, so if you save it and make changes to that list later it will not impact the actual color of the atom. This also means you can't use color[9] = 0.5; you would have to grab the value of color first, make sure it's a list, change it, and then assign the changed list back to the color var.
Except in the case of areas, this list is always restricted to objs and mobs (ie movable objects). Only direct contents are listed. Items inside of a bag object, for example, would not show up in the mob's contents list.
The contents of areas are a little different. The turfs contained in the area are in the list along with any objs or mobs directly contained by those turfs.
If a movable atom uses the bound vars to change its physical size, or step_x or step_y to change its position, it may cover more than one turf. In that case, those turfs' contents won't just contain anything directly in them, but also any atoms overhanging them. I.e., if a turf is in a mob's locs list, then the mob is in that turf's contents list. (See locs for more information.)
Note: Looping through all of the atoms, or even just turfs, in a particular area actually loops through every turf in the world. E.g., for(var/turf/T in area). The engine will check each turf to see if it belongs to that area, and then includes the turf and/or its contents in the results depending on what the loop is looking for. This also applies to any situation where you might grab area.contents as a list, e.g. area.contents.Copy(). Therefore in a large world, it's advisable not to loop through area contents at all.
This turns the object's density on or off (1 or 0). Two dense objects may not occupy the same space in the standard movement system.
This is the description of the object.
This is the direction that the object is facing. This has little effect unless the object's icon is directional. In the case of a directional icon, this selects the corresponding orientation from the icon file.
An icon file with only four (cardinal) directions makes the choice of orientation ambiguous when the true direction is a diagonal. In that case, of the two possibilities, the one closest to the previous orientation is displayed. Sounds complicated, but it's what one would naturally expect.
This var is a list of graphical filters to use for post-processing effects, applied in order. You can assign this value a list, an individual filter, or null to empty it.
Atoms with the KEEP_TOGETHER
flag will apply their filters
after the composite image has been drawn. Filters will also apply to any
maptext the atom has.
See the filters section for more information on individual filters.
This sets the object's gender. This influences text macros like
\he
, which may expand to "it", "he", "she", or "they". Valid
values are:
This is the icon file that will be used to represent the object on graphical clients.
You can also assign this to an external file at run-time with an expression such as file("wall.dmi"), but you would only want to do that when the other method is not possible, because it requires addition of the file to the resource cache, which can take a little time.
When this variable is set to any dynamically created icon object, that
object gets dumped into the world's resource cache (the .rsc file),
and a reference to that cached file is assigned to atom.icon. In other words,
don't expect comparisons such as usr.icon ==
MyDynamicallyCreatedIcon
to work unless you have used fcopy_rsc() to
get a cache reference to your dynamically created icon first. This is almost
never an issue, so don't worry about it if none of that made any sense to you!
Icons may appear differently depending on the icon state. For example, turf door icons could have "open" and "closed" states. If a state is specified that does not exist in the icon file, the default null state will be displayed if it exists.
This causes the object to be visible in the dark to mobs that can see infrared light. Nothing but the object itself is lit up by the infrared emission. The scale is identical to luminosity: 1 makes it visible only from the same location; 2 makes it visible from a neighboring position; and so on.
This determines the object's level of invisibility. The corresponding mob
variable see_invisible
controls the maximum level of invisibility
that the mob may see.
A value of 101 is absolutely invisible, no matter what, and it is filtered from all lists of possible values for verb arguments. This is intended for objects that exist purely for some internal purpose, such as "verb containers".
This numerical value determines the layer in which the object is drawn on the map. By default, the order is area, turf, obj, mob, followed by missiles and images (in FLY_LAYER, which is 5).
When making objects to be used as graphical overlays, you should also be aware of the special FLOAT_LAYER value. This causes the overlay (or underlay) to be in the same drawing layer as the base object, no matter how that layer changes after the addition of the overlay. Otherwise, the overlay object's own drawing layer is used.
The actual drawing order of icons may change depending on world.map_format. An isometric map for instance has to display tiles that are "closer" to the viewer in front of tiles that are in the back, so the layer var takes a backseat to the needs of the map. If you use the TOPDOWN_MAP or TILED_ICON_MAP map formats, the layer is more important.
If you are using a world.map_format that does not display topdown, such as ISOMETRIC_MAP or SIDE_MAP, then you can use a special layer for showing certain portions of the map in topdown mode. For those parts of the map, you can add TOPDOWN_LAYER to every atom's layer to make the atom appear in topdown mode. This is for special cases, like for instance a battle map in an RPG, where a regular topdown view is preferable to the special mapping used by the rest of the game. It is recommended that you use TOPDOWN_LAYER with every atom in that portion of the map, since topdown and isometric maps for instance don't mix. If you use TOPDOWN_LAYER, it is best to use a square size in world.icon_size if any of these atoms will be moving around.
Another special layer, EFFECTS_LAYER, is also available. Icons that use this layer will display above icons that don't. TOPDOWN_LAYER will then display above that. This layer is useful for situations such as using a floating name or health meter overlay on a mob in isometric mode. When using EFFECTS_LAYER, other icons on the regular map won't cover the overlay. (It is preferable to use atom.plane for this, when possible.)
Finally there is BACKGROUND_LAYER. Adding this to an atom's layer will make it appear below any atoms that do not use BACKGROUND_LAYER. (It is preferable to use atom.plane when possible.)
The atom.plane var takes priority over layer. This is the preferred method of handling background and effects.
The container is always an atom (or null). For areas, this value is always null, since an area may not be contained by any other object.
This sets the object's luminosity (how far it casts light). It must be an integer in the range 0 to 6.
Areas are a little different. Any non-zero value in an area results in all objects within the area being bathed in light.
This is optional text that will be displayed in the same position as the atom. If an atom has both an icon and maptext, the text will be displayed in front of the icon. Usually however, this is something that would be added to an overlay or image object, which can then be positioned with pixel offsets.
Map text is constrained to the bounds set by maptext_width and maptext_height, which default to a single icon in size. It can be offset by maptext_x and maptext_y.
Text can use HTML and CSS, mostly the same limited subset supported by regular text output, and different styles can be used in the same block of text. In addition, alpha colors can also be used, by specifying a color as #rrggbbaa instead of just #rrggbb. (Alpha transparency will be ignored when the map is drawn without hardware rendering, so anything below 50% opacity is not displayed in those cases.)
Maptext supports links with the <a> tag. Left-clicking on a link will follow the link, but also generate other events such as MouseDown or Click.
This is the width of the text shown in the maptext var. By default, it uses the icon width provided in world.icon_size.
This is the height of the text shown in the maptext var. The default value depends on world.icon_size and world.map_format. In a topdown (default) or tiled map_format, the icon height is used. In other map views, tile "footprints" are square and height is irrelevant, so the default will be the icon width instead.)
Maptext, if used, is offset by this many pixels to the right.
Maptext, if used, is offset by this many pixels upward.
This defines how the mouse looks when dragging this object. Assigning this to MOUSE_ACTIVE_POINTER (1) enables the default dragging indicator.
This variable may also be set to any of the other built-in mouse pointers, or a custom icon or icon state. If an icon state is specified, this is applied against the object's main icon to find a custom pointer.
Note that all mouse pointers are purely visual indicators. They do not effect what objects may actually be manipulated with the mouse. You control all of the real behavior in the associated procedures.
This defines how the mouse looks when dragging this object over another object that has mouse_drop_zone set. The default value enables the addition of a standard "droppable" indicator to whatever mouse_drag_pointer is (unless mouse_drag_pointer is turned off).
This variable may also be set to any of the other built-in mouse pointers, or a custom icon or icon state. If an icon state is specified, this is applied against the object's main icon to find a custom pointer.
Note that all mouse pointers are purely visual indicators. They do not effect what objects may actually be manipulated with the mouse. You control all of the real behavior in the associated procedures.
Setting this to 1 indicates that this object is a valid site on which to drop other objects. While dragging, mouse_drop_cursor of the object being dragged will become active in this case. Note that this is a purely visual effect. It does not control what the user may do with the mouse. You control the real behavior with the associated procedures.
This may be used to control how mouse operations on an object are interpreted. A click or mouse movement over an object's icon normally applies to that object only if it is the topmost object that is not transparent at the position of the mouse. Setting mouse_opacity to 0 would cause the object to be ignored completely, and setting it to 2 causes it to always be chosen over any lower-level objects, regardless of the transparency of its icon.
Note that overlays and underlays are not distinct objects, so their mouse_opacity is completely ignored in favor of the object they're attached to. The same applies to image objects, which act like special overlays as well. Visual contents, on the other hand, are separate objects that can act like overlays in some ways, but because they're separate their mouse_opacity is taken into account.
When this is applied to a PLANE_MASTER object (see appearance_flags), a value of 0 means everything on the plane is mouse-transparent. 1 means everything on the plane is mouse-visible (using the objects' normal mouse_opacity), but the plane master itself is not. 2 means everything on the plane is mouse-visible, and so is the plane master.
This defines how the mouse looks when no buttons are pressed and it is held over this object. Assigning this to MOUSE_ACTIVE_POINTER (1) enables the default indicator that there is something special under the mouse (crosshairs).
This variable may also be set to any of the other built-in mouse pointers, or a custom icon or icon state. If an icon state is specified, this is applied against the object's main icon to find a custom pointer.
Note that all mouse pointers are purely visual indicators. They do not effect what objects may actually be manipulated with the mouse. You control all of the real behavior in the associated procedures.
This turns the object's opacity on or off (1 or 0). Opaque objects block light.
This is a list of simple icons which are displayed on top of the object's main icon.
The individual items in the list may not be directly accessed, since they
are stored in a special internal format.
However, the list operators +=
, -=
, and the
procedures Add
, Remove
, and Cut
work
normally.
The data types that may be used as overlays are icons, icon states (text strings), objects, and object types. When an icon state is used, the corresponding image in the object's icon is displayed. When another object is used as an overlay, a static "snapshot" of the object is taken at the time when the overlay is created. Future changes to the object will not change the appearance of the overlay, except that in some cases the overlay may inherit things from the base object like its icon (if left empty), direction, and moving state.
Overlays have their own independent drawing layer. It is normally the special value FLOAT_LAYER, which makes them float above the base object. If the overlay is a snapshot of another object, the drawing layer of that object is used (and in that case, the overlay can appear beneath the object if its layer is lower). The important advantage of using FLOAT_LAYER is that if the layer of the base object changes, the overlays will move with it into the new layer.
Any negative number may be used in place of FLOAT_LAYER (which happens to be -1). They all cause the same "floating" behavior. However, the overlays are ordered amongst themselves according to their own relative layer values (-2 below -1 and so on). This may be useful if you have several classes of overlays that should always appear in a certain order, because you would not have to worry about the order in which you add them to the list.
That example used object types, but you can use instances of objects as well. Rather than using different "float" layers, you can also just make your own list of overlays with the order you want and assign that to the actual overlays list.
Currently this only applies to images.
If you attach an image to an atom, normally it is seen only in addition to the atom's regular icon. If the image's override var is 1, it will be seen in place of the original atom (and its overlays). It will inherit the atom's color, alpha, transform, and appearance_flags, unless its own appearance_flags say otherwise.
If the image has a specific name and/or suffix value, those will override the parent atom too. Leaving them blank will let the original atom take precedence.
This displaces the object's icon on the x-axis by the specified number of pixels. In a project with a standard 32x32 tile size, this can range from -32 to +32. (You can get away with larger displacements, but they are not guaranteed to work for objects off the edge of the map.)
Since overlays and images can each have their own additional displacements, this makes it possible to create visual effects that extend beyond the object's own cell in the turf grid, but which automatically move around with the object.
This effect is purely visual and does not influence such things as movement bumping or view() range calculations.
This displaces the object's icon on the y-axis by the specified number of pixels. In a project with a standard 32x32 tile size, this can range from -32 to +32. (You can get away with larger displacements, but they are not guaranteed to work for objects off the edge of the map.)
Since overlays and images can each have their own additional displacements, this makes it possible to create visual effects that extend beyond the object's own cell in the turf grid, but which automatically move around with the object.
This effect is purely visual and does not influence such things as movement bumping or view() range calculations.
This displaces the object's icon horizontally by the specified number of pixels. This is meant to be used in situations where world.map_format is used to display something other than a top-down form, for instance in an isometric or side-view display. In a top-down mode pixel_w behaves the same as pixel_x, except that it does not rotate with changes to client.dir.
This effect is purely visual and does not influence such things as movement bumping or view() range calculations.
This displaces the object's icon vertically by the specified number of pixels. This is meant to be used in situations where world.map_format is used to display something other than a top-down form, for instance in an isometric or side-view display. In a top-down mode pixel_z behaves the same as pixel_y, except that it does not rotate with changes to client.dir.
This effect is purely visual and does not influence such things as movement bumping or view() range calculations.
The value of plane overrides layer, and is mainly used for non-topdown map formats like isometric. Positive values are drawn on top, and negative values are drawn below. This mostly deprecates EFFECTS_LAYER and BACKGROUND_LAYER, but they can still be useful when using PLANE_MASTER for effects (see appearance_flags).
The special value FLOAT_PLANE can be used for images and overlays, to take on the plane of the parent atom. Whenever an icon or icon_state is used directly as an overlay, this is its plane. If there is no parent atom, the plane is 0.
You can also use FLOAT_PLANE as a relative value, so FLOAT_PLANE+1 adds 1 to the parent atom's plane. This may be useful for some applications where an object is included in visual contents. The added value should still be kept in the range of -10000 to 10000, or preferably much smaller.
If any icon uses render_source, the renderer will look for another icon with a matching render_target value on the visible map, and draw that in place of the icon/icon_state/dir that it normally would. The same render target might be used as a source multiple times, and for a complex image this can save some rendering time. This is also usable for special effects such as filters that might require copies of an image.
Note: The corresponding render_target must be visible to the player for a render source to be used.
Other vars that would normally affect the appearance of this icon, such as color, transform, filters, appearance_flags, etc. are all applied. Additionally, if this object has overlays, underlays, or visual contents, those will still be drawn as well.
If an entire PLANE_MASTER was the render source, some vars like pixel_x/y/w/z will not apply, but you can use transform.
Note: Any mouse hits on this object belong to the object itself, not to the object used in the render_source or its children. If you want mouse clicks and other behavior to go to the source, use the PASS_MOUSE appearance flag.
If any icon uses render_target, and another icon in the scene has a matching render_source, this icon will be drawn to a temporary image called a "slate", which can be reused mutiple times for faster drawing and for special effects.
If the render_target value starts with an asterisk ("*"), it will not be drawn on the map, but will still be drawn to a slate for reuse.
Other vars that would normally affect the appearance of this icon, such as color, transform, filters, appearance_flags, etc. are all applied when drawing to the slate. Additionally, if this object has overlays, underlays, or visual contents and uses the KEEP_TOGETHER flag to group all of the icons together, those will also be included in the slate.
If the render target is a PLANE_MASTER, vars like pixel_x/y/w/z will not apply when it is re-drawn as a render_source.
To use a render_target, the object it belongs to must be within the visible part of the map, or in the HUD, etc., so that it would be visible to the user under normal circumstances. If not, it will not be included in the render process and therefore it can't be reused as a render_source.
Additionally, if you use multiple map controls, a given render target can only be used as a source on the same map control where it appears. This is because each map is rendered separately.
This is an optional text string that follows the object's name in the stat panels. For example, items in an inventory list are displayed as an icon, a name, and a suffix.
This is the character used to represent the object on text clients.
Entering several characters produces a text movie (the state of the art!). In that case, each character is displayed for a 10th of a second.
HTML tags in the text can be used to modify the colors of the text
characters. As a convenience, the <font> tag may include a
bgcolor
attribute, so you don't have to do a CSS style setting to
accomplish the same thing.
The example above produces a map with a blue background (from the area) and turfs (depicted by ".") that flash from bright red to a shorter span of light red.
Note that in order to see text icons, the user must switch to the text map in Dream Seeker. If your DM code never does anything with the icon variable, then this is the default configuration. Such applications are known as advanced iconic text games:)
An atom can be made to appear rotated, scaled, and/or translated (moved) by using affine transforms. This takes a matrix and multiplies the x and y positions of the icon's corners like so:
a d 0 x y 1 * b e 0 = x' y' 1 c f 1
This is equivalent to:
x' = a*x + b*y + c y' = d*x + e*y + f
You do not need to understand matrix math to use transforms, because you can use the matrix datum to do this for you.
Transformations are relative to the center of the icon. They do not apply to maptext.
Whenever you read the atom.transform var, you will get a copy of the atom's current transformation. Whenever you assign a value to the var, you will update the transformation.
Assigning null to atom.transform will revert the atom to using no transformation at all. It is also legal to assign a list with six values, which is equivalent to using a matrix.
This is a list of icons which are displayed underneath the object's main icon. Since these are basically the same as overlays, see overlays for more detailed information.
The only real differences between items in the underlays list vs. the overlays list is that they're processed first, and if they use FLOAT_LAYER (or any other negative layer value) they'll appear below the object instead of above it. For this reason, the underlays list isn't used nearly as much as overlays since it's easier to throw most things into the overlays list.
When multiple turfs are stacked on top of one another in the map editor, there is actually only one turf (the topmost) and the rest are all underlays.
This is a list of the object's verbs. Initially, it contains all of the verbs defined in the prototype. It may be used to add and remove verbs at runtime.
Note that this variable is not automatically saved when the object is written to a savefile. That behavior may change in the future. In the mean time, you must save any necessary changes yourself or they will not be preserved when the object is loaded.
Turfs, movable atoms, and images can be given a list of atoms (limited to turfs and movable atoms) that are attached to them visually, like an overlay or image object, but act independently and have their own identity. These are visual contents. The purpose of visual contents is to provide an alternative system to overlays and images, with a little more flexibility for various special effects.
For example, a mob with an obj in its visual contents will show the obj as if it's following the mob around like an overlay, but clicking on the obj will not—unlike an overlay or an image object—count as a click on the mob. Likewise, the obj will retain its own separate mouse_opacity setting, which is not true of regular overlays.
An atom that appears on the map normally can still be in the visual contents of another atom. However it will not apply gliding and step offsets here. Also, considerations like visibility will not apply.
If a turf is in an atom's visual contents, the turf and all of that turf's contents will be displayed. (In the case of big atoms, or movable atoms with step offsets, only atoms that actually have that turf as their loc will appear. "Overhangers" will not.) Gliding and step offsets will be applied to that turf's contents normally; but again visibility, opacity, etc. will not be considered. (If you have one or more turfs in visual contents, an object gliding to a new turf outside of that list will not be shown because it's already technically on the new turf.)
If multiple turfs are present in visual contents, they will be offset as needed (relative to the southwest-most turf) to appear in the correct positions. That is, pixel_x and pixel_y offsets will be applied automatically, so if you add an entire block to visual contents (be aware this will impact performance), you don't have to do anything else to make the block appear normal.
You can alter some aspects of how an object behaves when in visual contents by changing its vis_flags var. In particular this is useful if you want an object to behave more like an overlay, inheriting aspects of its parent object or even acting like a part of that object instead of an independent one. Also this can make an object act like an underlay instead of an overlay when using a floating layer.
Visual contents do not impact the results of view() or range(), or verb availability, in any way. This is strictly a visual effect.
Being in a visual contents list counts as a reference for anything in the list, the same way that being on the map or inside of a movable counts as a reference.
This is a set of flags that determine how this object will behave when it is in another object's visual contents.
Because only turfs, objs, and mobs can be in visual contents, this var belongs only to those types.
The VIS_INHERIT_ID flag effectively makes this object act like an ordinary overlay when in visual contents. This means its mouse_opacity will be meaningless, for example.
Sometimes it's desirable for an object not to show up in visual contents, so VIS_HIDE will prevent that. The flag applies even if this object appears indirectly, like if it's in the contents of a turf that is in the visual contents of something else.
Note: Using any of the the flags VIS_INHERIT_ICON, VIS_INHERIT_ICON_STATE, VIS_INHERIT_DIR, or VIS_INHERIT_ID will cause movable atoms to inherit the "moving" flag of their container that appears during gliding. E.g., if your mob is walking, anything in its visual contents that uses these flags will use a moving icon state instead of a non-moving icon state, when available.
This list is the opposite of the vis_contents
list. If this
atom is in any other atoms' visual contents, those parent atoms will appear
in this list.
Because only turfs, objs, and mobs can be in visual contents, this var belongs only to those types.
Being in a visual locs list does not count as a reference, the same way that being a movable's loc does not count as a reference. If an object in this list otherwise runs out of references, it will be garbage collected and therefore removed from this list.
You may assign the coordinates of movable objects (mobs and objs), but this
is not advisable. It is better to compute the new location (with
locate()
) and move them to that. Then you can use the normal
Move()
procedure, which enables all the normal movement behavior.
For areas that are on the map, this is the coordinate of the turf with the lowest z, y, and x coordinate (in that order) that is contained by the area.
You may assign the coordinates of movable objects (mobs and objs), but this
is not advisable. It is better to compute the new location (with
locate()
) and move them to that. Then you can use the normal
Move()
procedure, which enables all the normal movement behavior.
For areas that are on the map, this is the coordinate of the turf with the lowest z, y, and x coordinate (in that order) that is contained by the area.
The z coordinate is how objects move between maps. When you include several maps in a project, they are placed on different z levels so that the full map is a single 3-dimensional space. It is also possible for a single map file to contain multiple z levels.
Do not confuse this with drawing layer. The z coordinate moves an object between different maps. The layer variable determines the order in which an object is drawn graphically relative to other objects at the same position on the map.
You may assign the coordinates of movable objects (mobs and objs), but this
is not advisable. It is better to compute the new location (with
locate()
) and move them to that. Then you can use the normal
Move()
procedure, which enables all the normal movement behavior.
For areas that are on the map, this is the coordinate of the turf with the lowest z, y, and x coordinate (in that order) that is contained by the area.
Each connected player has a corresponding client object. It has variables and procedures which control aspects of player input/output. This object is also responsible for linking the player up to a mob.
The client can be reassigned from its original mob M to a new mob N by setting N.client = M.client. This process disconnects the player from M (calling M.Logout()) and connects the player to N (calling N.Login()). Setting the mob's key has the same effect.
Additional vars, procs, and verbs may be added to the client in order to give the player properties that are independent of the mob.
Built-in client procs:
The client who owns this proc (src) is the one trying to upload the file. If this proc returns a true value, the upload will be allowed. Otherwise, it will be rejected.
This built-in procedure checks to see if the user is subscribed to a particular BYOND hub entry. If the user is subscribed, the result is the number of days left (rounded up) on their subscription, or -1 for lifetime subscribers.
Note that in general the value of world.hub has nothing to do with the passport you happen to check. This example assumes the passport number belongs to world.hub just for the purpose of forwarding the user to the appropriate subscription page.
You can use this with other APIs that are supported by BYOND, which currently only applies to Steam and only if the game is specially built for Steam support.
To check ownership of a Steam game or DLC (must be the current game's ID or one of its DLCs), use "steam:NNNNNN" for the passport string, where NNNNNN is a Steam app ID. Note that the user must be logged into Steam for this to work.
Note that due to network lag, it is possible when clicking on moving objects for the location of those objects to have changed by the time the Click() proc is executed. That is the reason for the location argument. It tells you where the click originally took place.
The argument format for this verb is:
If this proc is used, players will be able to connect to your world via telnet. All telnet users' commands are routed through this proc instead of being parsed into verbs. Players who join the world through Dream Seeker will have their commands parsed as verbs first, and those commands will end up here only if there is no applicable verb.
Note that text received by this proc is not interpreted beforehand, so quotes " and backslashes \ should come through unaltered.
This proc is primarily useful if you want to handle parsing yourself (like for a MUD), or if your world is a chat server and verbs are not used much.
Note that due to network lag, it is possible when clicking on moving objects for the location of those objects to have changed by the time the DblClick() proc is executed. That is the reason for the location argument. It tells you where the click originally took place.
Note that this does not automatically delete the player's mob. If you want to do that, you could do so in mob.Logout().
This stores the file on the user's computer in a special location unique to each registered world.hub setting. This is most useful for writing a client-side savefile, but any type of file may be stored. The purpose of this is to exchange information between different worlds running under the same hub path.
When a file is exported to the player's computer, it replaces any
previous file stored by a game with the same world.hub
value.
This should not be used for anything requiring high security, because any
other world could make use of the same hub path and access the file. It
is also possible for the user to tinker with the file, since it resides on
their computer.
To delete the client-side file completely, call
client.Export()
with no argument at all.
Interfaces with supported external APIs to read information. Currently this only has meaning for Steam, for specially built games that have a Steam app ID.
This proc returns null any time the call or its results are invalid: for instance, trying to query a Steam stat from a user who isn't logged into Steam.
Key | Return type | Description | |
---|---|---|---|
"steam" API Requires that the server and client are using a valid Steam app ID, such as when a game is built for standalone distribution. | |||
id | text | Returns the user's numeric Steam ID, if any. Because this number is too big to fit in BYOND's numbering system, it is returned as text. | |
name | text | Returns the user's persona name on Steam. | |
stat:Name | num | Returns the value of the stat called Name. | |
achievement:Name | num | Returns the date (for use with time2text) the achievement called Name, or 0 if it hasn't been earned. | |
achievement-data:Name | list | Returns information about the achievement called Name. The result is an associative list with named values name (display name), desc (description), and optionally hidden and icon. (This call is the same no matter which client you call it for.) | |
achievements | list | Returns a list of all possible achievements. Each item in the list is the internal name of the achievement, and it is associated with a list in the form described for achievement-data:Name. (This call is the same no matter which client you call it for.) |
When no query parameters are given, this returns the client-side file last
exported with client.Export()
. This comes as an entry in the
resource cache, which can be opened as a savefile among other things. If
there is no file, null is returned. For an example, see
client.Export.
When there are query parameters, these may be used to import a file from some alternate source. Currently this is not supported.
This built-in procedure checks to see if the user is a BYOND Member. Use it to give special in-game rewards to those who support BYOND.
If the user is a Member, the result is the number of days left (rounded up) on their Membership, or -1 for lifetime Members.
Because maptext rendering may vary by client, MeasureText lets you get a measurement of how text will be laid out, so you can adjust maptext_width and maptext_height accordingly.
This is called when the a mouse button is pressed while pointing to the object. Note that MouseUp() will always be called after MouseDown() is called, even if over empty space. That means object and location may be null.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Most operations can be done through Click(), DblClick(), and MouseDrop(). The other procedures are simply available for completeness.
The argument format for this verb is:
Note: In BYOND 3.5 this procedure took three different arguments: location, icon_x, and icon_y. Since icon_x and icon_y have been replaced, old code will need to be modified. Games compiled before this change will still work normally.
This is called while dragging an object by pressing and holding the left mouse button over the object and moving the mouse. The over_object may be null if dragging over a stat panel or over other empty space.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Most operations can be done through Click(), DblClick(), and MouseDrop(). The other procedures are simply available for completeness.
The argument format for this verb is:
This is called when a mouse button is released after dragging an object. The over_object may be null if dropping over a stat panel or over other empty space.
The argument format for this verb is:
This is called when no mouse buttons are pressed while pointing to the object.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Defining it on only the objects that require it reduces overhead.
The argument format for this verb is:
This is called when the mouse moves off of an object.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Defining it on only the objects that require it reduces overhead.
The argument format for this verb is:
This is called when no mouse buttons are pressed while pointing to the object, and the mouse has moved. The first time the mouse moves over the object, MouseEntered() is called instead.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Defining it on only the objects that require it reduces overhead.
The argument format for this verb is:
This is called when a mouse button is released while pointing to an object.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. Most operations can be done through Click(), DblClick(), and MouseDrop(). The other procedures are simply available for completeness.
The argument format for this verb is:
Note: In BYOND 3.5 this procedure took three different arguments: location, icon_x, and icon_y. Since icon_x and icon_y have been replaced, old code will need to be modified. Games compiled before this change will still work normally.
This is called when the mouse wheel is moved while over an object or control. It is NOT called if over a browser control, or any control that is currently scrollable.
Positive values of delta_x and delta_y refer to scrolling right or up, respectively. Negative values are left and down, respectively.
Don't define this unless you need it, because it generates extra communication that is otherwise avoided. If you only need wheel support on specific objects, use atom.MouseWheel() instead which is more selective.
The argument format for this verb is:
This is a fairly low-level procedure that you would only want to override
if you cannot accomplish the same thing in mob/Login()
or
mob/New()
. One reason to override client/New()
is
if player mobs are created from a savefile. In that case, you don't need a
temporary mob to be created first.
If you want to do any user-interaction before loading the saved mob, you
will have to create a temporary mob first in order to interact with the
player. In that case, you are better off doing things in
mob/Login()
, rather than client/New()
.
Note that for the above example to work, you must make proper use of the tmp flag when defining new object variables. Otherwise, this can end up sucking large portions of your world into each player savefile, which can have all sorts of unexpected consequences!
Use this proc to render an atom or an appearance as a single icon. This is a client proc because the server is not capable of rendering anything on its own.
Any overlays, image objects known to this client that are attached to the object, visual contents, maptext, and so on will be included in the render. The returned icon is sized to fit all of the above, and to include room for any expansion due to filter effects.
Important notes regarding this proc:
The user is prompted to authorize sending of the pager message. The recipient may easily respond or jump to the sender's location by clicking on the link in the pager message. The effect is identical to that of the sending a page through the Dream Seeker pager.
The options are encoded in the same format read by text2params(). The valid options are:
Interfaces with supported external APIs to write information. Currently this only has meaning for Steam, for specially built games that have a Steam app ID.
This proc returns null any time the call or its results are invalid.
Key | Return type | Description | |
---|---|---|---|
"steam" API Requires that the server and client are using a valid Steam app ID, such as when a game is built for standalone distribution. | |||
stat:Name | num | Changes the value of the stat called Name. Returns 1 on success. This may fail if the stat change is too much or is out of range. | |
achievement:Name | num | Earns the achievement called Name, or clears it. Value is expected to be a number, or number in text form, where 0 will clear the achievement and 1 will earn it. |
This proc is used to ask a client about sounds that are playing. The /sound datums in the returned list have the following vars set:
Not all info about the sounds is retrieved, such as volume, frequency, etc. If those are needed, it should be a simple matter to keep track of them in your code. The main purpose of SoundQuery() is to ascertain the current status of playing sounds.
If this procedure sleeps (or engages in some other waiting operation), it will not be called again until it finally returns. This allows you to effectively decrease the frequency of calls to the proc. You might want to do that if it is a fairly lengthy procedure, and frequent calls are slowing things down.
To increase the frequency of stat updates, you can lower
world.tick_lag
.
Note: Typically only the currently viewed statpanel is updated, which saves on some network activity and a little time. If however the proc sleeps, you need to be sure that any pending updates are displayed once the right panel is available. Therefore if you're resetting a var that indicates the proc should sleep next time, it should not be reset unless you know the player is looking at the right statpanel and has received the updates.
Because sleeping in Stat() requires more thinking through, it's best to do so only in cases where Stat() has to do a lot of intensive calculations.
The following example uses a very simple href value.
Be sure to call the default handler unless you want to prevent rerouting of topics to other objects.
Always validate the input in Topic() calls to make sure it's correct and the query you're recieving is legitimate. For security reasons, you will probably want to control which objects a player has access to, since a player could spoof a topic link containing any arbitrary object reference. (Never trust those sneaky players!)
The next example demonstrates an href that gets handled by another object. This is how you would normally want to do things. It is best not to override client/Topic() (as in the example above) unless you need to intervene in the low-level details of routing the request to the right object.
You specify the object that will handle the request by using a parameter called "src".
Although it is slightly more complex, the use of the parameter list allows you to easily include extra data and new functionality. Just remember that the data in the list is always stored as text, so if you are expecting a number or an object, you must convert it yourself (with text2num(), locate(), or whatever).
Built-in client vars:
In CGI mode, DreamDaemon is normally being executed by a web server and accessed by a web browser. The CGI object is an add-on to the basic client object for handling this case.
The CGI object is defined and documented in a separate code library
html/CGI.dm
. You can find the current version at
www.byond.com.
This is a read-only value which contains the player's network address.
This value may be set to 0 at compile-time to disable BYOND hub-based authentication of users. The default value is 1, which enables authentication. Hub authentication provides an additional level of assurance that the user is really the owner of the BYOND key in question.
When a world requests certification, Dream Seeker generates a random password and passes it through the hub (for certification) to the world. The certificate is saved for faster access in the future and for protection against possible hub outages.
Some applications do not depend on the validity of the user's identity. In that case, it would be more efficient to turn off the extra level of authentication. In other situations, the hub may not be available, such as from behind a firewall or on a LAN without internet access. In those cases, all hub access (including authentication) can be disabled by entering the command ".configuration hub-address none" in Dream Seeker.
Connections to worlds on the same machine are not hub-authenticated to allow for convenient offline testing.
The read-only bounds var returns the map coordinates, in pixels, covered by the client's viewport when accounting for pixel offsets, eye, step, etc. (The coordinates are only relevant to the default client.dir value of NORTH, and the TOPDOWN_MAP or SIDE_MAP map formats.)
If the viewport is not currently on the map (for instance, when the eye is at a null location), the var reads as null. Otherwise, it is a list with five values (x, y, width, height, z) in the same form used by the bounds proc.
The alias vars bound_x, bound_y, bound_width, and bound_height can also be used to retrieve the individual values from the list. They too will be null if the viewport is not on the map.
This is the build number (minor version) of BYOND being run by this client. Typically this is not useful information, but it can come in handy when diagnosing issues reported by players using a beta build.
Clients running versions of BYOND prior to 512 (major version) will not have this information. It is also not guaranteed to exist for non-Dream Seeker connections. When not available, byond_build is 0.
This is the version of BYOND being run by this client. A game designed to work around known bugs in older versions could use this to adapt its behavior accordingly. It is also possible to prevent players with much older versions from joining the game.
This is a read-only value that is the canonical form of the player's key (ie the value returned by ckey()). Among other things, this could be used as a unique directory name in a server-side save file for storing player information. See the ckey() proc for an example.
Casts a color multiplication or matrix effect over the entire main map. This behaves exactly the same as atom.color, and will be combined with atom.color (which comes first) where present. See atom.color for more information.
If a matrix is used, the alpha column and row will have no effect.
Icons that have the NO_CLIENT_COLOR value in appearance_flags will not be subject to client.color. This can be useful for HUD objects.
This value can be animated.
Note: In BYOND 4.0 this var is deprecated. The command parameter for an Input control can be set to !command (! in front of your default command) which does the same thing.
This text is placed onto the command line, to be followed by whatever the user may type. It is usually the name of a verb followed by a space, such as "say ". The user can clear this and enter a different command by hitting backspace, escape, delete, or /.
It is also possible to turn on macro mode, in which each keypress
executes a keyboard macro, by setting
command_text
to ".alt ". That stands for the Alt
key, which can be used to execute macros in normal mode.
This variable could also be used to create a specialized command prompt. For example, a traditional style MUD command-line could be implemented like this:
This example uses the command_text input type, which accepts raw text, with no quoting, escaping, or translating, so that you can invent whatever syntax you want.
This is a read-only text value which contains a unique numerical identifier for the player's computer. Its primary purpose is to detect if players are connecting to a server with multiple accounts on the same machine, while still allowing for multiple accounts on the same network (eg, through a router).
This is a read-only var describing the type of client that is connected.
Other values may be supported in the future.
An empty value means the connection type is unknown because a full handshake hasn't been completed yet.
This var lets you set flags to turn off options that are normally present for the end user. You can combine these flags with the | operator. The value 1 is equivalent to CONTROL_FREAK_ALL and will disable everything.
Using CONTROL_FREAK_ALL will default to disabling everything, and the other flags will reenable only the features you want. For example, CONTROL_FREAK_MACROS alone will disable the ability to use your own macros but nothing else. CONTROL_FREAK_ALL | CONTROL_FREAK_MACROS will disable everything except macros.
This value can be changed at runtime.
Note: If you define your own skin for the world, and disable the ability to use a custom skin or user-defined macros, you must be sure to define any macros your world may need. For instance, arrow keys may be needed for movement.
All verbs with category "" (the default value) are treated as though they had this category setting instead. In other words, this is the name of the panel that contains them. You could even turn that panel off by setting this value to null.
This defines the relationship between the world's coordinate system and the player's screen. The default means that the player sees the map exactly as it was created (in the map-editor or at runtime). Changing it to one of the other directions causes the player to see the map as if it were rotated to that direction. This means that a player with client.dir = SOUTH would see the map inverted relative to a person with client.dir = NORTH. That's handy in two-player board games where you want both players to see their side in the same place.
Note that this does not turn icons upside down! The map is rotated, but the icons on the map remain in their normal orientation.
Movement keys are remapped so that a player with client.dir = SOUTH hitting the up arrow will generate a call to client.South() rather than the usual client.North().
This value determines the limits that a client's eye will display.
If client.perspective
uses the EDGE_PERSPECTIVE
flag,
the view shouldn't scroll beyond the bounds set by edge_limit
. If
the bounds of edge_limit
are as big as or smaller than the
client's view, no scrolling will occur even if EDGE_PERSPECTIVE
is not used. Normally this value is null, which provides freedom for the eye
to move anywhere on the map. It may be changed to a text value describing the
limits in more detail.
The format is similar to atom.screen_loc
which uses
"[x1],[y1] to [x2],[y2]"
. It can also use directions
such as "SOUTHWEST to NORTHEAST"
, which refer to the
limits of the map.
This value determines the center of the player's map. The default value
simply means that the visible region is normally centered on the player's mob.
Effects such as setting perspective
to
EDGE_PERSPECTIVE
or using lazy_eye
can move the map
off-center temporarily. The eye is the ideal center, not
necessarily the actual center; to find the actual center, use
virtual_eye
.
The eye's step_x/y vars, if present, are also used to allow smooth scrolling of the map. These also obey lazy_eye and edge_limit.
Note that the visibility of objects is still computed from the point of
view of the mob rather than the eye. This allows the use of
lazy_eye
or similar effects that control the panning of the map
while still having the player see only what the mob can see. To determine
visibility from the eye, you can change the value of
client.perspective
.
If a player connects to a new mob M, client.eye automatically changes to M.
This fixes the center of the player's map at the turf coordinate (5,5,1). Since the eye is fixed, the map will not scroll even as the player's mob moves out of the visible range.
This is a client version of world.fps, so that the client can run at a faster speed for animations. For example, setting client.fps to 40 while world.fps is the default 10 will mean that all animations and glides are smoothed out and displayed at 40 FPS, even though the server still runs at 10 FPS. The result is a nicer-looking game with no additional impact on the server.
When this value is 0, the client and server tick at the same rate.
This is the client's gender, which is an attribute of the player's key.
By default, when a new mob is made for a player (in client.New()), the new
mob gets the same name and gender as the player's key. This influences text
macros like \he
, which may expand to "it", "he", "she", or
"they". Valid values are:
Note: The way this setting is used depends on world.movement_mode. See Gliding for more details.
This controls the number of pixels the map is moved in each step during scrolling of the map. The default value of 0 chooses automated control over this value, which generally results in a minimum step of 4 pixels that is increased when necessary to keep up with motion of the map.
Be careful about using small step sizes. Icons with high contrast pixel-level detail can look pretty ugly when displaced by short distances.
This was renamed from pixel_step_size.
This is a list of images that are displayed to the user. The output operator is one way to add entries to this list. Deleting an image object will automatically remove it from the display, but if you want to retain an image (so other people can still see it), it can be removed by directly modifying this list.
This is equal to the amount of time (in server ticks, which default to 1/10s) since the player's last action (such as executing a verb, moving, clicking an object, or selecting a topic link). This value is reset to 0 after each new action so you can use it to determine the time that has passed since the last one.
This is a read-only value that contains the player's key. Once the player is attached to a mob M, M.key == M.client.key.
This is the maximum "lag" between client.eye and client.mob. The mob can stray up to this many tiles before the eye will move to keep it in view. The default value of 0 means that the eye always moves as the mob moves, keeping the mob at the center of the player's map.
Setting this value to a non-zero value automatically initializes client.eye to client.mob.loc (or to the center of the full map if that is possible). Thereafter, client.eye will stray from the mob as it moves about the map, making one big jump to catch up whenever the mob gets out of range.
This setting allows client.mob to move onto the entire 11x11 visible region without changing the value of client.eye. The moment it steps out of this region, the entire region will shift 5 tiles in the direction of motion.
You can assign lazy_eye to any value valid as a view size, so, for example, if you have a non-square setting for client.view, say, "17x11", you could apply a similar setting to lazy_eye. You can even make one dimension lazy and the other one strictly centered: "0x5".
This is the mob to which the client is connected. The client and its connected mob have the following symmetry:
This is an icon file (.dmi) containing custom mouse cursors to use in place of the standard ones. The different possible mouse states are distinguished by special icon state names:
This controls the eye's apparent center and what can be seen by the client.
EYE_PERSPECTIVE determines how visibility calculations are performed when
client.eye
and client.mob
are different. Normally,
visibility is done from the position of the mob, rather than from the eye
(which is actually just the center of the map view). The alternative flag is
MOB_PERSPECTIVE, the default.
EDGE_PERSPECTIVE limits scrolling to the bounds of the map (1,1 to world.maxx,world.maxy), and does not keep the mob centered if it strays near the edge.
The above values can be used together via the | operator.
This displaces the player's viewpoint on the x-axis by the specified number of pixels. Can be animated with the animate() proc.
This displaces the player's viewpoint on the y-axis by the specified number of pixels. Can be animated with the animate() proc.
This displaces the player's viewpoint horizontally by the specified number of pixels. This value is meant to be used when world.map_format is not set to a default top-down view. Can be animated with the animate() proc.
This displaces the player's viewpoint vertically by the specified number of pixels. This value is meant to be used when world.map_format is not set to a default top-down view. Can be animated with the animate() proc.
Renamed to glide_size.
This variable controls whether resource files (icons and sounds) are automatically downloaded by Dream Seeker when first connecting, or whether they should be downloaded as needed. Resource files are cached (in byond.rsc) for future use, so this should only affect people who have not played the game before or who have not played it for some time.
The three possible settings are:
Preloading resource files will eliminate delays later on, but may cause a very long initial delay when logging in.
Resources may also be distributed from a website to save bandwidth on the machine hosting the game. Simply zip up the .rsc file, upload it to a web site, and put the URL here.
Instead of putting the .rsc file in the .zip, you can also put the individual resource files there. This would allow you to select specific files that you would like to be preloaded. For example, you could create a different resource package for different parts of the game world and assign client.preload_rsc dynamically as the player moves into each different area.
Once Dream Seeker has downloaded a resource package, it caches it and will not download it again, even if you upload a new version of the file. This allows you to make small changes without forcing a complete refresh. Any files which are not found in the preload package are simply downloaded from the game server directly.
If you want to force a complete refresh, simply change the name of the resource package. For example, you could put a version number in the name of the file: mygame_rsc_01.zip, mygame_rsc_02.zip, and so on.
This is a list of objects that are displayed on the user's screen. The object's screen_loc variable controls where it appears (if it appears at all). This allows one to create the elements of a graphical user interface, with such features as buttons, drag/drop areas, and stat monitors.
Screen objects (visible or otherwise) may also be used to make verbs available to users. To make them accessible, define verbs on the screen object like this:
Client scripts are mini-programs used to configure the client. The language they use is called DM Script, and will undoubtedly expand in the future. Currently, client scripts can be used to define style sheets, command aliases, and macros. When executed directly by a player, they can also be used to specify an initial URL to open and a password trigger (for some ancient telnet worlds that don't suppress password echo).
For the specific syntax of DM Script, see the relevant reference sections listed above.
The client.script
variable may be assigned to script code in
a text string (double quotes) or in a file (single quotes). You can also
simply include the file in your project or explicitly use the
#include
statement. Files containing DM Script should have the
extension .dms
.
This example selects a default monospace font for all output to the terminal.
In addition to scripts loaded via client.script
, the player
may have client-side scripts. These are either called
connection scripts or post-connection scripts depending on
whether they are used to automatically connect to a world or whether they
are executed automatically after connecting to a world. In either case, the
player's scripts are always executed before the designer's
client.script
script, so style sheets from the designer have
higher precedence by default.
There are three post-connection client-side scripts for the three types
of worlds the client can connect to: byond.dms
,
telnet.dms
, and irc.dms
. These are automatically
executed if the player connects directly to a world without using a
connection script to do so. The intention is to load any standard
configurations such as style sheets and command aliases.
This defines a special text trigger used to detect when the user is being prompted for a password in telnet mode. Most MUDs automatically suppress password echo, but if they do not, it is necessary to use this setting to hide it. Multiple triggers may be defined as necessary.
The example above is more robust than the more polite version because it works whether they capitalize the 'P' or not...
Defining a URL in a script, specifies the world to connect to when the
script is executed by the player. This is referred to as a connection
script, because the player uses it to connect to a world. Other
post-connection scripts such as byond.dms
or a script loaded
through client.script
are only used to configure the client
after it has connected to a world. In those cases, the URL setting has no
effect.
It is important to enclose the URL in double quotes. Otherwise, the
//
symbol would be mistaken for a comment.
DM Script can be used to effectively make a hyperlink in a web document to a BYOND world. This is done by making a DM Script file that defines the desired URL. It need do nothing more than that. When a user clicks on the link in a web browser, DreamSeeker will pop up, execute the script, and connect to the specified URL.
Some browsers may need to be configured to know what to do with a DM
Script file. For example, in Netscape, you can add an entry to the list of
helper applications. You should add a MIME type called
'application/x-dms
' with the description 'DM Script' and the
extension dms
. Have this execute DreamSeeker with the
.dms
file as an argument.
You can connect to my world here.
Command aliases have a syntax similar to verbs. They define a command and a series of arguments which can then be used to execute a new command. The most common use for this is in a telnet world like a MUD. By defining aliases corresponding to the MUD commands, the player can have primitive command expansion and help.
The syntax of an alias definition is best illustrated by the following example:
As you can see, it is just like a verb. Alias have all the same
properties as verbs, except the src
setting is always equal to
the player.
The value returned by an alias is executed as a command. In telnet mode, the command to execute is often simply the same as the command that was entered (since the alias was only defined to provide command expansion and help). Since that is such a common case, the return value defaults to the alias name followed by each of the arguments. The example above, for instance, would have the same effect without an explicit return statement.
Note that commands executed via an alias are never interpreted as aliases. Otherwise, examples such as the one above would result in an infinite loop.
Macros are just like aliases, except that they are triggered by a single key (or combination of keys) instead of a full command. When a macro is executed, it returns a text string which is then executed as a command. So a macro is just a short-cut for entering a command.
The following example illustrates the syntax for entering a typical set of macros.
Note: In old versions of BYOND, character keys required the Alt key to be pressed to trigger the macro, and did not include "ALT+" to do so. This behavior has changed, and the name of the macro is just like the format used in skin files. You can now use a key name, and modifiers like SHIFT+, CTRL+, ALT+, +REP, and +UP. Old .dms and client.script files (prior to version 507) should be updated accordingly when recompiling in a newer version.
Style sheets may be included in DM Script by putting the style sheet
inside the HTML tags <STYLE>
and
</STYLE>
. In general, any text enclosed in start
and end tags will be sent to the player's terminal, so you could use
client.script
to output a welcome message as well as loading a
style sheet.
This example style sheet makes the player's terminal have a black background and aqua colored text. When changing the background color, it is important to change the color of system and link text as well. See the section on style sheets for an example.
This variable may be used to turn off the view of the map in Dream Seeker. This could be useful for making text MUDs where the rooms are turfs (ie most rooms can be laid out on a grid but you don't want the user interface to show the map in any way).
The following example shows one useful combination of settings. Note that
setting world.view=-1
also disables the map, but it also sets
the default value of the view()
depth in such a way as to always
return an empty list.
This variable may be used to turn off the popup "context" menus that are displayed by default when an object on the map or stat panels is right-clicked. If client.show_popup_menus==0, then right-clicks will instead be passed to the various mouse functions.
Setting this to 0 turns off the verb panel in Dream Seeker. You might want to do that, for instance, if you've only got one verb (like "say") and the panel looks stupid with just one verb in it.
This value indicates which object the player currently has loaded in the stat panels.
This value indicates which stat panel is currently visible to the player. You can assign it to force a different panel to become the top panel. The special value "verbs" activates the panel of commands.
This is a client version of world.tick_lag, so that the client can run at a faster speed for animations. For example, setting client.tick_lag to 0.25 while world.tick_lag is the default 1 will mean that all animations and glides are smoothed out and displayed at 40 FPS, even though the server still runs at 10 FPS. The result is a nicer-looking game with no additional impact on the server.
When this value is 0, the client and server tick at the same rate.
This is the time offset from UTC, in hours, for the client's time zone. It can be used in the time2text() proc.
This is a list of the client's verbs. Initially, it contains all of the verbs defined for the client. It may be used to add and remove verbs at runtime.
This controls the size of the map window in Dream Seeker. Normally, you would simply compile with world/view assigned to whatever you want, but in some cases, you might want to customize the map size for different players, such as admins or subscribed users.
Like all other view sizes in DM, this may either be a view depth or an absolute size. A view depth is a single number that determines how far from a center point the edges of a square viewable region extend. A value of 5 creates edges which are 2*5+1 = 11 tiles long.
The newer, more flexible syntax is a text string of the form "WIDTHxHEIGHT". For example, a view depth of 5 corresponds to "11x11". Using this syntax, you can create non-square views as well.
The maximum view size is about 5000 tiles, or roughly 70x70.
This value determines the actual center of the player's map display. It is
based on client.eye
and whenever possible matches it; however it
may instead be a turf, or null, when the eye is off-center.
The value of virtual_eye
is read-only.
A /database datum gives you the ability to create or access a database using SQLite, which allows you to run complex database queries on any platform.
Creating a /database/query datum will let you put together a query, and once it's ready you can call its Execute() proc to run it.
SQLite databases in BYOND support numerical values (such as INTEGER or FLOAT), text (TEXT), and cache files such as icons (BLOB). Null values are also allowed.
Built-in database procs:
If a database is currently open, this will close the database and any queries currently running in it. Usually you don't need to call this directly, because deleting the datum will do it for you.
Returns the error code last received by the database.
Returns the error message last received by the database.
Opens a database file. If another database was already open, it is closed automatically. It is more common to simply open the database in New().
Creates a new database datum, and opens the file if a filename is provided.
This datum lets you create a query for a database, which can be run with the Execute() proc. The datum can be reused after a query is run by calling Clear() and adding new text with Add().
Built-in database query procs:
Adds text to a database query. If this datum was already used to run a query, Clear() will be called automatically.
If your text includes question marks, they will be replaced with the other items listed in the proc arguments. If that item is a string, quotes will be put around it for the query text. Files in the cache (such as icons) will be added as BLOB values.
After the query has been built, call Execute() to run it.
In the example above, the query text might look like this:
INSERT INTO quests (name, quest, complete) VALUES ('Tom','Save the Dog',1)
Clears the query text so you can begin creating a new query. This is called automatically if you already called Execute() for the last query used by this datum.
Ends a query that is in progress. This is usually done automatically and shouldn't be necessary to call in most cases.
Returns a list of column names for the current query, or the name of the Nth column.
You must call Execute() before calling Columns().
Returns the error code last received for this query.
Returns the error message last received for this query.
Runs a database query. Once the query is run, if the query is supposed to returns any rows you can call NextRow() until finished, and then GetColumn() or GetRowData() to get the information from each row. For queries that cause changes, RowsAffected() is also a useful call.
The database argument is optional after the first time you use it.
You can use a filename instead of a /database datum, as a shortcut; the datum will be created for you.
After a query is executed, calling Add() to create new query text will clear out the old query text automatically.
Gets the value from the Nth column in this row of results. If you haven't already called Execute() and NextRow(), you should do that first.
To get the name of the column, not the value for this row, call Columns(column) instead.
The value returned depends on what type the database table thinks it is. For instance if you defined a column as INTEGER or FLOAT, the value should be a number. TEXT is still text, and null values are returned as null. If an icon was saved into a BLOB field, the result is an icon file.
Returns a list with the current result row for this query. If you haven't already called Execute() and NextRow(), you should do that first.
The list returned is an associative list with name=value pairs. A typical result might look like this:
list("name" = "Tom", "quest" = "Save a Dog", complete = 1)
The values returned depend on what type the database table thinks they are. For instance if you defined a column as INTEGER or FLOAT, the value should be a number. TEXT is still text, and null values are returned as null. If an icon was saved into a BLOB field, the result is an icon file.
Creates a new query and adds text by automatically calling Add(). See the Add proc for more information.
Call Execute() to run the query.
If there are result rows in this query (Execute() must be called to run the query first), NextRow() will retrieve the next row and return 1 if it found a row, or 0 if the results are all finished. NextRow() is typically called in a while() loop.
After calling NextRow(), you can call GetColumn() or GetRowData() to get information about the results in this row.
Call Reset() if you want to rewind the query to the beginning.
If a query returns any rows of results, Reset() will go back to the beginning just after Execute() was called. This is useful if you have called NextRow() repeatedly to retrieve a number of rows, but need to go back to the start of the query for some other reason. This can also be used to count the total number of result rows if needed, but for best performance that isn't recommended.
After running Execute() on a query that changes rows in the database (for instance, an UPDATE query), this proc returns the number of rows that were changed. This can be useful if you need to know whether a query actually did anything.
The datum object is the ancestor of all other data types in DM. (The only exceptions are currently /world, /client, /list, and /savefile, but those will be brought into conformance soon.) That means that the variables and procedures of /datum are inherited by all other types of objects.
When you define a new "top level" object, if you do not specify a parent_type, it defaults to /datum.
Built-in datum procs:
del
instruction.
del
were called on each one of them.
When the world is destroyed, the Del() proc is not automatically called. The only object for which it is called is /world. If you need the Del() proc for a particular object to be called at that time, you should explicitly call it from world/Del().
Note: Always call ..() at the end of the proc if you override it.
new
,
when reading an object that was stored in a savefile,
or when the world is initially created.
You can use the New() procedure to do more complicated initializations than are possible in the object definition where you assign the initial value of variables to constants.
The following example makes use of the "Location" parameter that is passed
to objects of type /atom. You can pass any number of
additional arguments to New() by passing them to the new
instruction which creates the object.
Also note that the type of object being created in this case was automatically inferred from the variable type on the left-hand side of the assignment. That's a handy little DM short-cut.
This procedure is called by the default client.Topic() proc when the href contains a parameter called "src" containing an object reference.
The above example uses an embedded reference to the player's own mob to create a hyperlink to that mob's Topic() proc. You can easily add different actions, parameters, and so forth. Just remember that the parameter values are always stored as text, so you need to convert those to whatever data format you need using procedures such as text2num(), locate(), etc.
Always validate the input in Topic() calls to make sure it's correct and the query you're recieving is legitimate.
Built-in datum vars:
This variable is set at compile-time to specify the inheritance of an object type. Normally, a new type of object inherits its variables and procedures from the object type that contains it. For example:
Explicitly setting the parent type allows you to put the object definition any place you want. That often means putting it at the top "root" level. Example:
If you don't specify the parent_type for an object defined at the top level, it defaults to /datum, which (with just a couple exceptions) is the ultimate ancestor of all object types. You could use that fact to define procedures or variables that you need all of your objects to share.
This may be assigned to a unique text string identifying a particular object. A reference to the object can then be obtained by using locate().
One reason to use tags is when making references in the code to objects and locations that will be created on the map. You can simply edit the object in the map editor, set its tag, and then use that in the code relating to the object.
The following example demonstrates how to set a tag and use it to obtain a reference to an object.
Setting a tag to "" or null removes it. Any object with a non-empty tag is immune to garbage collection, since the tag is treated as an implicit reference to that object.
This variable is read-only.
This is a list of all the variables belonging to an object. The items in the list are the variable names. If the variable name is used as an index into the list, the value of that variable is accessed.
This example displays all the variables belonging to your mob.
This datum is created automatically when a runtime error is encountered, if it happens within a try/catch block or you have defined a global error handler with world.Error(). (The New() proc is not called when this happens.) This provides a convenient package for getting file and line number info associated with an error.
If you throw your own exceptions, you do not have to use this, but the EXCEPTION macro is provided to easily create one with the current file and line number.
The desc value is only filled in when you have a world.Error() handler and there is no try/catch handling this error. Just like when no handler is present, less detail will be provided after multiple runtime errors have occurred. This only exists as a convenience feature for logging errors if you want to use something other than world.log.
An /icon object is created by loading an icon file into memory for direct access and manipulation. In order to be displayed, an /icon object always gets converted back into an icon file; this happens automatically when you assign atom.icon to an /icon object, since that variable may only refer to a static icon file, rather than a dynamic memory object.
To create an /icon object, simply use new/icon(), or the short-cut icon() proc. The following example loads an icon file, reddens it, and then assigns it back to the player's icon, which implicitly creates a new icon file.
Note that merely displaying different icon states or directions can generally be achieved without any icon manipulation, which saves quite a bit of overhead. For example, the variables atom.icon_state and atom.dir can be used to control how atom.icon is displayed, without any need for generating a new icon file.
Many things that used to require icon manipulation may not need you to do so anymore, as DM has evolved new capabilities.
Operation | /icon proc | New method |
---|---|---|
Multiplying by color | Blend or SetIntensity procs | color var |
Adding color | Blend proc | color var (using color matrix) |
Applying color matrix | MapColors proc | |
Rotation | Turn proc | transform var |
Flipping horizontal/vertical | Flip proc | |
Scaling | Scale proc | |
Overlaying/underlaying another icon | Blend proc + ICON_OVERLAY | Overlay/underlay + KEEP_TOGETHER Layering filter |
Note: Anything you can do with an atom var instead of using icon manipulation procs will usually perform much better. Games that use the new methods use fewer resources, use less memory, and also usually look better too.
The valid blending operations are:
The result is a combination of each corresponding pixel in the two icons. In all but ICON_OVERLAY, ICON_UNDERLAY, and ICON_OR, the transparent regions of the two icons are ORed together. That means if either icon is transparent on a given pixel, the result will be transparent. With ICON_OVERLAY or ICON_UNDERLAY, on the other hand, the original icon shows through wherever the top icon is transparent, giving the same effect as an overlay object, but resulting in only a single icon. In ICON_OR, the transparent regions are ANDed together; solid pixels are added together where they exist in both icons, or just pass through if the other icon is transparent at that pixel.
In addition to blending with an icon, an rgb() value may also be specified. This is treated identically to an icon of that same solid color, except that the x and y arguments will be ignored. Blending with a color blends the whole icon.
By default, the icons will line up at their lower left corners. If you want to position the second icon in a different place for blending, use the x and y arguments to specify where its lower left corner will be. 1,1 is the default, which is the lower left. 11,1 for instance would be 10 pixels to the right, and 1,21 would be 20 pixels up.
A portion of the current icon is cropped (cut). If the crop region extends outside the icon, it will be padded with transparent pixels.
If using the TILED_ICON_MAP value for map_format, all icons must be even multiples of world.tile_size, so the icon will be padded with transparent pixels to the top and right as needed.
A rectangle (filled) of the given color is drawn over every frame in the icon. If x2 and/or y2 are omitted, a line or a single pixel is drawn. To draw a transparent box instead of an opaque color, use null as the color.
This flips the icon over in the specified direction. For example, Flip(NORTH) would be like turning the icon upside down by grabbing the bottom edge and flipping it up over the top edge. You would get the same result by doing Flip(SOUTH). In general, this is not the same as turning the icon by 180 degrees, because it produces a mirror image.
If an icon is square, it may be flipped diagonally.
This finds the icon_state and the right animation/direction frame of your choosing (it will pick the first one available if you don't specify) and returns the rgb() value of a pixel at that location, in "#RRGGBB" form. If the pixel is totally transparent, it returns null. If the pixel is partially transparent, an alpha component is also returned in "#RRGGBBAA" form.
This finds the width, in pixels, of the icon.
This returns a list of all icon state text strings that exist in the /icon object. This works in exactly the same way as icon_states(icon).
This adds additional states or images to an existing icon, allowing you to build directional, animated, and multi-state icons on the fly. If the state you wish to insert already exists in the file, it will be altered; otherwise it will be added. An animation may be built a piece at a time, for example by inserting an icon into the NORTH direction for animation frame 3.
The icon resulting from this example has two states: The original arrow, and a new state called "blink" that pulsates between full and ½ luminance. To use the blinking state after that, set the atom's icon_state to "blink".
(Note for animations: When building an animated icon_state from scratch, you can only add 16 new animation frames at a time; i.e., frame<=total_frames+16. Higher values for frame will be ignored. This is a safety precaution.)
If you insert an icon of a different size, the src icon will be resized to match the size of new_icon. (The only exception is if you are using the TILED_ICON_MAP map_format, and new_icon is a single tile being inserted as a chunk into a larger icon. If icon_state, such as "2,0" or "open 0,0", already exists in src as one of its smaller pieces, then new_icon will be inserted in its place.)
When inserting an individual animation frame, you can change the delay for just that frame only. If you don't specify a delay, the nearest frame's delay will be used. If this is the first frame being inserted into an icon, then the delay will default to 1 tick. Remember, if your delay is positive, it will turn off the rewind flag for that entire icon state; negative will turn it on.
This is used for complex color mapping that can be used for many special effects. For the number form, values usually range from 0 to 1, but you can use anything you like, including negative numbers. 1 means 100% of the original color will be used. If rg=1, for example, then the amount of red in the original icon becomes the same amount of green in the new icon's colors.
There is also an alternate form that can use rgb() values instead, though it is less flexible. r_rgb is the color that will be used in place of 100% red; any darker shades of red will become a darker shade of that color. g_rgb converts green to another color, and b_rgb converts blue to still another color, and all of them are added together.
Either of these calls change the icon to grayscale:
The calculations are as follows:
Or this will make a nice moonlight effect:
Or a negative icon (invert all colors):
The longer formats of MapColors() will allow you to also change alpha colors.
You generally don't call this directly but via new(). The specified icon file is loaded into memory for direct access and manipulation.
If the icon state is not specified, all icon states are loaded. Ditto for the direction, animation frame, or preference for movement states. Animation frames are numbered from 1 and up, so frame=4 is the 4th frame.
(Movement states are special versions of an existing icon_state with the same name, but appear in the Dream Maker editor with an "M" indicator. These states are used for animation when the atom using the icon_state moves from one tile to the next; otherwise only the normal non-moving state is displayed.)
The following contrived example, loads the EAST facing default icon state "" from the user's icon file, rotates that a bit, and then creates a new icon file for the user.
Note that merely displaying different icon states or directions can generally be achieved without any icon manipulation, which is good, because it saves quite a bit of overhead. For example, the variables atom.icon_state and atom.dir can be used to control how atom.icon is displayed, without any need for generating a new icon file.
The current icon is scaled to a new size.
If world.map_format is set to TILED_ICON_MAP and the new size is not in multiples of world.icon_size, the icon will be padded with transparent pixels to the top and right as needed. See map_format for more information.
Scale() automatically performs antialiasing to avoid unwanted artifacts.
This multiplies the pixel intensities by the specified amounts. A value greater than 1.0 increases the intensity and a value less than 1.0 decreases the intensity.
This moves all of the pixels by the specified amount in a direction. For example, Shift(NORTH,1) would move everything one pixel to the north.
By default, pixels that move off the edge are not wrapped around; transparent pixels are shifted onto the other side. Calling with wrap=1 causes it to shift the pixels around to the other side.
This causes a color value in the icon's palette to be changed. You can use null in place of an RGB or RGBA value.
If the old color is a full RGBA color with an alpha value, such as rgb(1,2,3,4) or "#01020304", then that exact color is the only one changed.
If the old color is an RGB value with no alpha specified, such as rgb(1,2,3) or "#010203", then that color will change to the new one regardless of its alpha value, and the original icon's alpha will be kept intact. (If the new color is totally transparent, however, then the old color will be replaced with full transparency.)
This rotates the icon clockwise by the specified amount.
If an icon is not square, it cannot be turned.
This finds the height, in pixels, of the icon.
The /image type contains data used to create a virtual image. Unlike other atomic objects, this object is a purely visual effect. It always appears attached to some other object and it behaves in every way as though it were part of that object (e.g. if the user clicks on it, this counts as a click on the atomic object, not the image).
One reason for creating images is player-by-player control over visibility. Images only become visible when they are explicitly output to players:
Images are also useful in the creation of overlays. Overlays are like images, since they are always attached to another object, but overlays obey the normal rules of visibility, so they are more convenient when you do not want to hide the effect from anybody. An overlay can be created directly from an icon file (or icon state), but when one wishes to override some additional parameter, the image() instruction is a convenient way to do it.
In the above example, the icon state of an overlay was set by creating the overlay from an image with the desired icon state. Note that after the creation of an overlay, no link remains between the overlay and the object that was used to create it. If you change the image after that time, it will not change the overlay, which is simply a "snapshot" of the original image.
The location of an image specifies the object to which the image is attached. Unless the image drawing layer is specified, the default will make it appear above this object, as though it were an overlay.
Note that the image is not inside the specified location. In other words, this loc variable does not behave like /atom/movable/var/loc which specifies the container object. It is more like the image is on top of the specified object. If the object moves, the image will automatically move around with it.
Lists are used to represent groups of objects. Like objects, they have vars and procs associated with them. In order to access these attributes, list vars must be declared of type /list. These may then be assigned to existing lists, or used to create new lists.
Lists created with 'new()' have a default length of 0; this can be overridden by specifying the size; that is, new/list(size) creates a list with size (null) elements.
The 'list()' proc may be used to more easily initialize list data.
Alternatively, lists may be declared by using brackets, '[]'. Empty brackets indicate a list reference, exactly as /list does, so list/L is equivalent to L[]. Setting an initial size within the brackets, for instance, L[10], creates a list of that initial size.
Once a list L is declared, a specific item can be accessed by putting its index in the brackets: L[index].
Indices range from 1 to len. If the length of the list is changed, existing elements in the list will be preserved if they are less than the new length. New elements in the list will be given the initial value of null.
Multi-dimensional lists may be created by making a list of lists.
Such a list may also be created by using new(). As in the previous example, the next one creates a list of 10 lists each having 5 elements.
Each unique text string or object in a list may be associated with another value. This is done by using the item as an index into the list.
The above example illustrates the typical way in which list associations
are managed. Note that an item in the list may be added by assigning its
associated value. The example could have started by doing
params.Add("player","score")
, but that would have been
redundant.
Both for
loops in the example have the same effect. The
first one loops through each item in the list, and displays it along with
its associated value. The second loop achieves the same thing by looping
through the numerical indices (referred to as array indices as
opposed to associative indices).
Since numerical indices are treated differently, you may not assign an associated value to a numerical list item. Associations must have a text string or object reference as the index item.
Associated values default to null if none is assigned. This is also the
value returned when the supplied index item does not exist in the list. The
list defined above, for example, would return null for
params["time"]
.
The list()
instruction may also be used to create associative
lists.
When the index values happen to be text strings that satisfy all the requirements for variable names, this may also be written in a convenient short-hand:
In other words, this is exactly the same syntax as for named arguments.
These operators are used for accessing list items.
These operators all have special meaning when applied to lists.
Built-in list procs:
Appends the specified items to the list. If an argument is itself a list, each item in the list will be added.
Copy list[Start] through list[End-1] into a new list. The default end position of 0 stands for the position immediately after the end of the list, so by default the entire list is copied.
Remove the elements between list[Start] and list[End-1], decreasing the size of the list appropriately. The default end position of 0 stands for the position immediately after the end of the list, so by default the entire list is deleted.
Find the first position of Elem in the list. Elements between Start and End are searched. The default end position of 0 stands for the position immediately after the end of the list, so by default the entire list is searched.
Insert values into a list at a specific point. Using Index=0 or Index=list.len+1 is the same as adding to the list.
If any of the items you insert is itself a list, its elements will be inserted instead of the list itself.
Note: This proc doesn't work with many special lists such as
contents
or overlays
.
This is exactly the same as calling jointext(List,Glue,Start,End), and is provided for convenience.
Removes the specified items from the list. If an argument is itself a list, each item contained in it will be removed. Removal starts at the end of the list (highest index) so that this operation is an exact reversal of Add().
Removes all copies of the specified items from the list. If an argument is itself a list, each item contained in it will be removed.
This is basically a faster version of the statement while(list.Remove(Item1,Item2,...)) with an empty code block. For large lists this might be a big improvement because the list doesn't have to be traversed every time.
Cuts out items from a list, and inserts new items in their place. This is basically equivalent to calling list.Cut(Start,End) and then calling list.Insert(Start,Item1,Item2...), but faster.
As with Insert(), any items that are lists will insert their contents instead of themselves.
The Start and End index values can be negative, which count backwards from the end of the list. If the index values are out of range, there will be no error; they will simply be clamped to the beginning or end of the list. If End comes before Start, the two values are swapped.
Note: This proc doesn't work with many special lists such as
contents
or overlays
.
Swap two items in a list. If the list has associated values, they will be preserved. This is most useful for user-defined sorting routines.
Note: This proc doesn't work with many special lists such as
contents
or overlays
.
Built-in list vars:
This is the length of the list. Increasing it expands the list, initializing all new elements with null. Decreasing it contracts the list, making old elements inaccessible.
One or more map files may be loaded into the world's map. These are loaded into successive z-levels. If no map files are specified, the default project map file will be used. This file has the same name as the project but has the extension .dmm.
If no map files are loaded, the world's map size is determined by the world variables maxx, maxy, and maxz. The default content of this map is determined by the world variables turf and area.
To display rotation, scaling, and other transformations on atoms, DM uses 2D matrices. The /matrix datum is a convenient way of handling the numbers involved, as it can be easily manipulated. There are six vars, a through f, laid out like so:
a d 0 x y 1 * b e 0 = x' y' 1 c f 1
When an x,y point is multiplied by the matrix, it becomes the new point x',y'. This is equivalent to:
x' = a*x + b*y + c y' = d*x + e*y + f
The default matrix is:
1 0 0 0 1 0 0 0 1
Matrices are created with the matrix() proc, or by calling new/matrix(). (See the matrix() proc for examples.) They are also created as needed whenever you read from atom.transform or use certain operators.
Manipulation of matrices can be done with operators, or with procs. You can do the following with them:
When you've built your matrix, you can assign it to atom.transform to change the way that atom is displayed.
The matrices supported by this datum are not the same kind used to transform colors, as in the atom.color var and icon.MapColors() proc. For color matrices, see color matrix.
The operators listed above have special meaning when applied to matrices.
The assignment operators will modify an existing matrix, and can also be used directly with atom.transform. Other operators will create a new matrix.
This adds Matrix2 to the current matrix.
Calculates and returns a new matrix between src and Matrix2. If t is 0.5, then the result will be exactly halfway between both matrices.
There are many ways to interpolate matrices. Whenever possible, DM will interpolate by doing scaling and/or shearing first, then rotation, then translation. This is done by trying to find the angle of rotation of each matrix first; a rotation of 180° is counted as a flip rather than a rotation.
It is not strictly necessary for t to be between 0 and 1. Using a value out of bounds will extrapolate a matrix, continuing the change as far as t.
This inverts the current matrix, if possible.
Not all matrices can be inverted. If it's not possible, the matrix is said to be degenerate. This happens if, for example, all of the values in the matrix are zero. A degenerate matrix will not be changed by this proc.
This multiplies the current matrix by Matrix2 or n. If the n format is used, this is just like scaling the whole matrix. If another matrix is multiplied, then it is like doing the two transformations in order: src, then Matrix2.
Multiplication of one matrix by another depends on the order. You may get a different result multiplying A * B vs. B * A.
The matrix is scaled by the appropriate amounts.
If y is omitted, x is used for both. E.g., Scale(2) is equivalent to Scale(2,2).
This subtracts Matrix2 from the current matrix.
The matrix is translated (moved) by the appropriate amounts. The x and y offsets applied by translation are in pixels.
If y is omitted, x is used for both. E.g., Translate(2) is equivalent to Translate(2,2).
The matrix is rotated clockwise, by the angle given.
All atoms and images have an appearance, which is an immutable object that can be shared by many atoms. Making changes to an object's appearance generates new appearances, many of which may be temporary. For high-performance games, this can be a drawback. The /mutable_appearance type exists so that you can make multiple changes to an appearance without creating all the temporary objects, then turn it into a regular immutable appearance when finished.
A new mutable appearance is created via new/mutable_appearance, and giving it an atom, image, or appearance as a source object. Assigning it to an object's appearance var will create a new immutable appearance.
Reading certain vars, such as overlays, will create a temporary list object that can be modified easily. With regular appearances, making many changes to the overlays list results in a lot of churn.
The /mutable_appearance datum is technically a descendant of /image, but this is only for convenience, and should not be relied on for any other purpose as it is subject to change in future versions.
Built-in mutable appearance vars:
Mobs are "mobile objects" derived from /mob, which derives from /atom/movable. Human players are associated with a mob when they log on. Mobs are typically used for other "creature" types as well such as NPCs. This type is slightly more complex than objs since it can be attached to a client.
This example defines the mob type /mob/guzzler.
Built-in mob procs:
One can typically tell if a player is connecting to a fresh mob versus reconnecting to an existing one by testing if the mob's location is null.
One may wish to distinguish between a player who has disconnected from
the game and one who is simply switching from one mob to another. In the
case of a player switching to another mob, by the time Logout()
is called, the original mob's key will be null, whereas the key will still
be non-null in the case of a player disconnecting from the game.
Built-in mob vars:
This is the value of mob.key converted to canonical form (ie the form returned by the ckey() proc). Among other things, this could be used as a unique directory name in a server-side save file for storing player information. See the ckey() proc for an example.
This is a reference to a set of properties specific to the player. Therefore non-player mobs (NPCs) do not have a client (client = null).
Setting a mob's client connects that player's client to the mob.
This is a list of mobs in the same group. By default, a mob will swap positions with another mob in its group if bumped. It is also possible to make verbs that are accessible only to members of the group.
The following example handles addition of somebody else to your group.
Note that group lists may be asymmetric. Mob A may have mob B in his group list, but mob B may or may not. It is up to you to define whether mobs are added into both lists or not.
Here is an example of a verb accessible to a group:
For player mobs (PCs) this is the value of the player's key. For non-player mobs (NPCs), this is the value of the "desired" key. This means that if a player with that key logs into the world, he will be connected to that mob (as opposed to a new one of type world.mob).
Setting the mob's key will cause a client with the same key to connect to the mob. Any other mob with the same key will lose it.
Key values are always compared in canonical form (ie the form returned by ckey()) so setting a mob's key to "Dan", "dan" are equivalent as far as controlling player linkage.
The default parent_type of /mob is /atom/movable.
This determines how far the mob can see in the dark. The scale is just like luminosity: a value of 1 illuminates the mob and its location; 2 illuminates the immediate surrounds; and so on.
Setting this to 1 enables infravision, allowing the mob to see infrared objects in the dark.
This is the maximum level of invisibility that the mob can see.
This controls which objects on the map the mob can see. The default value of 0 means that the mob can see all objects that are visible and lit. Different flags in this var can be set to extend or limit this range.
The following bit flags are encoded in mob.sight:
SEE_PIXELS draws everything and then covers hidden turfs with blackness. It is supported in topdown maps only, not in other map formats. It does not mix well with other flags. In practice, SEE_PIXELS acts as if SEE_BLACKNESS, SEE_TURFS, SEE_OBJS, and SEE_MOBS are all turned on. That is, all atoms are drawn even on hidden tiles, and black squares are also drawn to cover them.
The black tiles rendered by SEE_BLACKNESS and SEE_PIXELS are drawn on the default plane 0.
There are two types of movable atoms: objs and mobs. The difference between them is that a mob can be attached to a human player, and is also typically used for NPCs and creatures. The obj type is a little bit simpler and is typically used for objects in the environment, items in inventory, etc.
Objects are derived from /obj, which derives from /atom/movable.
The following example defines the obj type /obj/scooper.
Built-in obj procs:
Built-in obj vars:
The default parent_type of /obj is /atom/movable.
Operators are used extensively in DM to compute numerical values.
The DM operators are:
() . : / :: // here . : / are path operators [] . : ?[] ?. ?: ~ ! - ++ -- * & // unary operators (* and & here are pointer operators) ** * / % %% + - < <= > >= << >> == != <> ~= ~! & ^ | && || ? // ternary a ? b : c = += -= -= *= /= %= %%= &= |= ^= <<= >>= := &&= ||= in
Each line has higher order of operations than the next. Operators within a line have equal precedence and therefore are processed from left to right as they occur in an expression. (Assignment, or operate-and-assign, are processed from right to left.)
Expressions of the form A #= B are shorthand for A = A # B except for ~= and :=.
This is identical to the <> operator.
To check if A and B are not equivalent, use the ~! operator.
A % B is read "A modulo B", which stands for the remainder of A divided by B.
This operator only works with integer values, for legacy reasons. A and B are truncated to integers before the modulo operation. There are uses for the integer truncation, but if you don't want that and want the fractional modulo instead, you can now use the %% operator.
A %% B is read "A modulo B", which stands for the remainder of A divided by B.
This is a newer version of the % operator that supports all numbers, not just integers. It is equivalent to B * fract(A / B). The % operator does the same thing, but truncates A and B to integers first.
Set A equal to A %% B. It is shorthand for A = A %% B.
A %% B is read "A modulo B", which stands for the remainder of A divided by B. This version of the operator works with fractional values for A and B.
Set A equal to A % B. It is shorthand for A = A % B.
A % B is read "A modulo B", which stands for the remainder of A divided by B. A and B are truncated to integers before the modulo; use %%= instead to work with fractional values.
A and B must be between 0 and 2**24 - 1, giving an effective width of 24 bits.
If A and B are lists, the result is a list that contains only items that were in both lists, in the order of list A.
If A is an icon or /icon datum, it is blended with B which can be either a color or another icon. This is identical to the + operator. Transparent areas in either icon will be transparent in the result.
Sometimes it is desirable to have easy access to a var without knowing its name, or send multiple items back from a proc. To do this, you can create a pointer to that var. Then you can use the * operator to refer to the value inside the pointer, or even to assign a value to it.
This operator is also called the reference operator, since it creates a reference to a var that you can use elsewhere.)
If you want the compiler to recognize that the item in your pointer var should be a certain type, you can give the pointer var that same type. Hence in the example above, pl is defined as a /list.
You can also call procs this way. You can either wrap the pointer and the * operator in paretheses, like (*p).MyProc(), or you can skip the operator and just call p.MyProc() directly.
The same also applies to the list index operator []. If p = &list then you can use (*p)[index] or p[index] interchangeably.
Pointers can be made for any of these kinds of vars:
One advantage of pointers is that you can use them to alter a value in a suspended (sleeping) proc.
Note: When spawn() is used, the current proc is forked, where one keeps running and a copy is scheduled to run later. If any pointers to proc vars were created, they belong to the original proc (the one that keeps running), not to the fork.
The first false value from left to right completes the evaluation (a practice known as short-circuiting). The return value is equal to the last argument to be evaluated.
First A is evaluated. If its value is true, B will be evaluated and assigned to A. If A is false, B will not be evaluated and A will remain unchanged.
Note that this is slightly different from if(A) A = B if A is a complex expression such as list[index++], because the expression is only evaluated once.
This operator cannot be overloaded.
Set A equal to A & B. It is shorthand for A = A & B.
This is commonly used to turn off certain bitfields in a word.
If A and B are lists, items in A that are not in B are removed.
If A is an /icon or /matrix datum, the datum will be changed rather than creating a new one and re-assigning it to A.
If A and B are text strings, a case sensitive comparison is performed (like sorttextEx()).
Cause input to be read from a file into a variable. The file may be a savefile or a file object corresponding to a text file.
A and B must be between 0 and 2**24 - 1, giving an effective width of 24 bits.
Bits shifted below the 24 low bits are lost.
Set A equal to A >> B. It is shorthand for A = A >> B.
If A and B are text strings, a case sensitive comparison is performed (like sorttextEx()).
If A and B are text strings, a case sensitive comparison is performed (like sorttextEx()).
This is identical to the != operator.
Cause the value B to be output to any players connected to mobs specified in A.
B may be an image, sound, or text. A may be a mob, the whole world, or any list containing mobs.
A and B must be between 0 and 2**24 - 1, giving an effective width of 24 bits.
Bits shifted beyond the 24 low bits are lost.
Set A equal to A << B. It is shorthand for A = A << B.
If A and B are text strings, a case sensitive comparison is performed (like sorttextEx()).
Parentheses may be used in expressions to change the order of operations. Whatever is inside a pair of parentheses will be evaluated first.
They are also used for calling procs or verbs, by placing the parentheses after the name of the proc. Any arguments that will be sent to the proc go inside the parentheses; multiple arguments are separated by commas, or the parentheses can be left empty if no arguments are sent. For the same reason, parentheses are also used when defining a new proc or verb and the arguments (if any) that will be used.
If A is an icon, the result is a new icon with B (a number, color, or another icon) multiplied. This works with the /icon datum as well.
If A is a /matrix datum, the result is a new matrix. B can be a number (which scales the whole matrix) or another matrix. Multiplying two matrices together can have different results depending on the order.
When using the & operator to get a reference pointer, you can access the value it points to with this version of the * operator. This can also be used on the left-hand side of assignment operations, for instance *A = B or *X += Y, to store the result in the place the pointer indicates.
This operator is also called the dereference operator, since it takes a pointer reference and gives you the value within.)
Note: If you try to read to or write from a pointer reference that is no longer valid, such as to a var inside a proc that has ended, the read or write will fail silently; reading will return a null value. (An exception is pointers to list items. If the index is out of bounds, you will get the expected error.)
Set A equal to A * B. It is shorthand for A = A * B.
If A is an /icon or /matrix datum, the datum will be changed rather than creating a new one and re-assigning it to A.
The pre-increment has the value (A+1) and the effect of adding 1 to A.
The post-increment has the value (A) and has the effect of adding 1 to A.
Set A equal to A + B. It is shorthand for A = A + B.
If A is an /icon or /matrix datum, the datum will be changed rather than creating a new one and re-assigning it to A.
The pre-decrement has the value (A-1) and the effect of subtracting 1 from A.
The post-decrement has the value (A) and has the effect of subtracting 1 from A.
Set A equal to A - B. It is shorthand for A = A - B.
If A is an /icon or /matrix datum, the datum will be changed rather than creating a new one and re-assigning it to A.
If A is an icon, the result is a new icon whose color values (except alpha) are divided by B, which must be a number. This works with the /icon datum as well.
If A is a /matrix datum, the result is a new matrix. B can be a number (which scales the whole matrix) or another matrix. Dividing by matrix B is the same as multiplyng by its inverse. That is, A / B is identical to A * ~B.
Set A equal to A / B. It is shorthand for A = A / B.
If A is an /icon or /matrix datum, the datum will be changed rather than creating a new one and re-assigning it to A.
This is the runtime search operator. It is used to access a property (var or proc) of a var that is not explicitly prototyped. If the variable doesn't have the specified variable or proc, a runtime error occurs.
The . operator behaves the same way, but it checks at compile time whether the property is available. If the var is assigned a value that isn't the correct type and doesn't have this property, a runtime error will still occur.
Note: You should prefer the . operator in most situations, because it's better to catch a problem in the compiler instead of at runtime.
This is the scope operator. It has multiple uses.
::A is a shorthand for global.A, so if you have a local or object var with the same name this disambiguates to the global var. The same is true of :A() which will call global.A() with the arguments you give it.
If A is a constant type and B is a static var, A::B refers to the static var. If you have a local var with the same name, this disambiguates to the static var. This is also the only case where A::B can be used as an Lvalue (modifiable expression).
The most common use of the scope operator is to get the initial value for a var. If A::B isn't a static var, then it's equivalent to initial(A:B). If A is a constant type path, the compiler will go even further by compiling this expression as the actual initial value instead of doing a runtime lookup.
This can also be used when defining a var that overrides its parent, by using the parent_type keyword for A. Multiple parent_type levels can be chained together. Similarly, in a static var definition, type can be used for A the same way.
If B is a proc, then A::B() is a reference to the proc for type A, which can be used in call(). In this case the parentheses are just a cue for the compiler to know this is a proc reference; it doesn't actually call the proc. Currently, A must be a constant type for this usage.
This is the "assign into" operator. The value of B is evaluated, then A. If A is a datum that has an operator:= proc overloading this operator, then that proc will be called with A as its src and B as its only argument. The return value of the proc (which defaults to its src, the old value of A) is assigned into the var that holds A.
If A was not a datum, then B is assigned into the var as if this were an ordinary A = B assignment.
A common use of this operator might be to copy another datum. This is basically just "syntactic sugar" to make certain datums easier to work with, and is intended mainly for situations where you've overloaded the operator.
Set A equal to B.
Note that this is not the same as the equality test (==), which tests if A is equal to B.
All assignment operators, including calculate-and-assign (such as the += operator), can be chained together, and they are evaluated in right-to-left order. Therefore, a = b += c is a legal statement. It is equivalent to adding b and c, storing the result in b, then setting a to use the new value of b. (a = b) += c will, on the other hand, set a to equal b, then add c to a and store the result in a; b is never changed.
Note that this is not the same as the assignment operator (=), which sets A equal to B.
To check if A and B are equivalent, use the ~= operator.
This is used to access the procs and vars of a prototyped object. The variable need not actually contain a value with the specified type, but must at least be a type with the specified variable or a runtime error will occur, causing the proc to crash.
This is the same as the : operator, except that the compiler checks to see if the var type has this property, and throws a compiler error if not. It is good practice to use the . operator whenever possible, so more potential problems can be caught during compilation.
If . follows a proc call, a list lookup, or a complex expression where the type can't be known, it will act like : instead.
If Expr is true, this evaluates and returns TrueExpr. Otherwise, it evaluates and returns FalseExpr.
This is used to access the procs and vars of a prototyped object. It is just like the . operator, but if the object is null, the access does not happen and there will be no runtime error. (A runtime error can still happen if the object is valid but is a different type that doesn't have the property available.)
When used in an expression to read a value or call a proc from a null object, the result of the expression is null. When used for assignment, the assignment will not happen, and the expression being assigned will not be evaluated, if the object is null.
When reading A?.B
, it's roughly equivalent to A && A.B
except that A
is only evalulated once, even if it's a complex
expression like a proc call. Making an assignment to A?.B
is
the same: A is evalulated only once, and if it's not null then an assignment
is made to its B var.
For a version of this operator that doesn't check at compile time if the property is available, use the ?: operator instead.
If ?. is used after a proc call, a list lookup, or a complex expression where the type can't be known, it will act like ?: instead.
This is used to access the procs and vars of an object. It is just like the : operator, but if the object is null, the access does not happen and there will be no runtime error. (A runtime error can still happen if the object is valid but the property is not available.)
When used in an expression to read a value or call a proc from a null object, the result of the expression is null. When used for assignment, the assignment will not happen, and the expression being assigned will not be evaluated, if the object is null.
When reading A?:B
, it's roughly equivalent to A && A:B
except that A
is only evalulated once, even if it's a complex
expression like a proc call. Making an assignment to A?:B
is
the same: A is evalulated only once, and if it's not null then an assignment
is made to its B var.
This is identical to the ?. operator, except that ?. will check at compile time if the property is valid for the object type (if known). For this reason ?. is usually safer.
This is the null-conditional list index operator. It is used to access an element of a list, IF that list is not null. If the list is null, then the access will not happen and the index expression inside the brackets won't be evaluated. If this is the left-hand side of an assignment operator, such as list?[index] = rhs, then rhs is also not evaluated when the list is null.
This operator cannot be overloaded, but overloads to the [] operator will apply to this operator as well.
This is used to access an element of a list.
If you want to use a datum like a list, you can overload the operator by defining an operator[] proc for reading a value, and operator[]= for writing a value. Those overloads also apply if you use the null-conditional ?[] operator.
A and B must be between 0 and 2**24 - 1, giving an effective width of 24 bits.
If A and B are lists, the result is a list containing items that are in either list but not both. list(1,2) ^ list(2,3) is equivalent to list(1,3). The items found only in A come first in the result, followed by any items found only in B.
Set A equal to A ^ B. It is shorthand for A = A ^ B.
If A and B are lists, any items that are found in both lists are removed from A, and then any items found only in B are added to A.
This is a relatively safe way to check if an item is in a list, because the value of List is allowed to be a non-list value, such as null. Compare to List.Find(A) which will fail if List is not an actual list.
List can also be an atom, in which case A in List is equivalent to A in List.contents.
The in operator has a lower precedence than !, which can be a point of confusion. If you want to check if something is not in a list, it's a common mistake to try if(!A in List). Unfortunately the !A part is evaluated first and becomes 0 or 1, so you're really asking if 0 or 1 is in the list. The correct way to check if something is not in a list is to wrap the in operator and its operands with parentheses, as in if(!(A in List)).
Similarly, the assignment operators also have higher precedence than in, so has_thing = thing in src will not be interpreted as you might expect. Again the solution is to use parentheses, e.g. has_thing = (thing in src).
The in operator is also a modifier for some procs such as locate() and input().
Note: For associative lists there's a faster way to see if an item is in that list. The lookup of List[A] in an associative list is relatively fast, so if the associated value is always expected to be true (not null, 0, or an empty string), you can use List[A] instead of A in List in those situations.
A "path" in DM is a constant value that identifies a particular definition
in the code tree (i.e. an object, procedure, or variable definition). An
example of this is the default mob type for new players /mob
.
Paths are used in two contexts. One is to "get to" a particular point in the code tree in order to modify the definition. The other is to reference a particular definition made elsewhere in the code tree. The syntax of a path is similar in both cases.
When you are making a definition, you simply put the path at the beginning of a line like this:
That automatically creates that path in the code tree if it does not already exist. When starting at the beginning of the line (no indentation) there is no need to begin the path with '/', but that is perfectly acceptable.
When making definitions, DM equates the path separator '/' with indentation, so the above example is really just a more compact way of writing:
One generally uses indentation when you have several things to define with a common parent path:
An important element of DM is that you can get to the same path in the
code tree from multiple places in the source code. For example, given the
above definition of gloves
and sandals
, you could
modify a property of one of them from somewhere else using any path syntax you
like:
While that was not a useful thing to do in this case, it can be a very powerful tool when organizing source code in large projects. Also note that the use of "/" can save your source code from getting too deeply indented, which may sound mundane, but which is quite important!
The above examples used paths to make definitions. The other time when you use paths is when you need to refer to a particular definition. Creation of an object is one example:
Another common use of paths is to declare the data type of a variable. In DM, variable types do not affect what type of data the variable may contain—variables that you define may contain any type of value. Instead, the variable type affects what properties of the data you can attempt to access.
The following example defines variables for clothing that is occupying various positions on the body.
Since there were several variables of the same type, they were grouped
under var/clothing
. It can be done any number of ways, depending
on the situation. The same path syntax applies to variable definitions as
it does to anything else. This example produces the same effect:
Just do not make a mistake like the following:
Beginning a path with '/' effectively ignores whatever indentation may precede it. That is why it is called an absolute path. The above example would therefore be the same as the following, which is not what you want:
On a related note, parameter definitions in procedures should not begin with a "/".
Essentially, "var/" is prepended to each entry in the parameter list.
This is used to delimit paths in the DM code tree. A path beginning with '/' is an absolute path (which is independent of where in the code it is used). Otherwise, a path is relative, meaning it starts from the current position in the code.
The following example uses a path in the code tree to define the type of object to create when leaving a corpse behind.
The colon operator may be used as a short-cut when specifying a path in the DM code tree. Instead of specifying the full path, you can insert a colon and the compiler will search down in the tree with the node you specify. This is known as a "downward" search. You should only use it when the target node is unique.
The following example demonstrates the principle but it obviously doesn't save much typing!
The dot operator may be used as a short-cut when specifying a path in the DM code tree. Instead of specifying the full path, you can start a path with a dot and the compiler will search up in the code tree for the following node. This is known as a relative path with an "upward" search.
Here are the beginnings of a text MUD that allows you to walk around between rooms using the arrow keys. The links between rooms are created in this example by referencing the object type of the destination room. Since there could potentially be a lot of rooms, they are grouped into sub-classes, and to avoid lengthy type paths such as /area/Village/Square, they are referenced using a relative path from the point of reference.
A and B must be between 0 and 2**24 - 1, giving an effective width of 24 bits.
If A and B are lists, the result is a list containing items that are in either list. list(1,2) | list(2,3) is equivalent to list(1,2,3). The items from A come first in the result, followed by any extra items from B.
If A is an icon or /icon datum, it is blended with B which can be either a color or another icon. Unlike the + or & operation, the result is transparent only in places where both icons were transparent.
Set A equal to A | B. It is shorthand for A = A | B.
This is commonly used to turn on certain bitfields in a word.
If A and B are lists, any items in B that are not already in A are added to A.
If A is an /icon or /matrix datum, the datum will be changed rather than creating a new one and re-assigning it to A.
The first true value from left to right completes the evaluation (a practice known as short-circuiting). The entire expression takes the value of the last argument to be evaluated.
First A is evaluated. If its value is false, B will be evaluated and assigned to A. If A is true, B will not be evaluated and A will remain unchanged.
Note that this is slightly different from if(!A) A = B if A is a complex expression such as list[index++], because the expression is only evaluated once.
This operator cannot be overloaded.
A must be between 0 and 2**24 - 1, giving an effective width of 24 bits.
If A is a /matrix datum, the result is a new matrix which is the inverse of A.
Equivalence is a looser version of equality:
To check if A and B are equal, use the == operator.
Equivalence is a looser version of equality. See the ~= operator for more information.
To check if A and B are not equal, use the != operator.
DM allows you to overload most of the operators it uses when working with datums and other objects. This means that A + B can call a proc defined under A instead, with B as an argument, and the return value of that proc would be the result.
The proc name for an overloaded operator is "operator" followed
immediately by the operator itself, such as operator*
to override
the multiplication operator. A * B will call A.operator*(B) if the proc is
available.
The following operators may be overloaded:
Operators | Proc | Notes |
---|---|---|
Arithmetic and binary (return new value) | ||
A + B | A.operator+(B) | |
A - B | A.operator-(B) | |
-A | A.operator-() | Same proc as subtraction, but has no arguments |
A * B | A.operator*(B) | |
A / B | A.operator/(B) | |
A % B | A.operator%(B) | |
A %% B | A.operator%%(B) | |
A ** B | A.operator**(B) | |
A | B | A.operator|(B) | |
A & B | A.operator&(B) | |
A ^ B | A.operator^(B) | |
~A | A.operator~() | |
A << B (shift) | A.operator<<(B) | |
A >> B (shift) | A.operator>>(B) | |
A << B (output) | A.operator<<(B,A,window) world.operator<<(B,target,window) | Ignores return value ..() falls back on world proc, then default behavior |
A >> B (input) | A.operator>>(null,A) world.operator>>(null,source) | Return value is assigned to B ..() falls back on world proc, then default behavior |
Comparisons (return true or false) | ||
A ~= B | A.operator~=(B) | |
A ~! B | A.operator~!(B) | |
A < B | A.operator<(B) | |
A >= B | A.operator>=(B) | |
A > B | A.operator>(B) | |
A <= B | A.operator<=(B) | |
Assignments with side effects (return value defaults to src) | ||
A += B | A.operator+=(B) | |
A -= B | A.operator--(B) | |
A *= B | A.operator*=(B) | |
A /= B | A.operator/=(B) | |
A %= B | A.operator%=(B) | |
A %%= B | A.operator%%=(B) | |
A |= B | A.operator|=(B) | |
A &= B | A.operator&=(B) | |
A ^= B | A.operator^=(B) | |
A <<= B | A.operator<<=(B) | |
A >>= B | A.operator>>=(B) | |
A := B | A.operator:=(B) | |
++A | A.operator++() | |
--A | A.operator--() | |
A++ | A.operator++(1) | |
A-- | A.operator--(1) | |
List access | ||
A[idx] | A.operator[](idx) | Used for reading a list value |
A[idx] = B | A.operator[]=(idx, B) | Used for writing a list value; ignores return value |
Other | ||
turn(A, B) | A.operator_turn(B) | |
"[A]" | A.operator""() | Specifies a custom way for converting A to text (see notes below) |
Some operators cannot be overloaded. The = operator for direct assignment is one. The ! operator is another. The == and != operators measure equality and can't be overloaded, but ~= and ~! for equivalence can be. It would also be meaningless to override the ternary ? : operator pair, and the . and : family of operators for accessing vars and procs.
Comparison operators come in opposing pairs: ~= vs. ~!,
< vs. >=, > vs. <=. You only
need to override one operator from each pair; DM is smart enough to know that
!(A ~= B)
is the same as A ~! B
.
By the same logic, you don't have to define the assign-with-side-effect operators like += if you don't want to. For instance if you override + but not +=, then A += B will be handled internally as A = A + B, which means the value of A after the statement may be a different datum than A was before. The value of A can also change if you do overload += but the proc returns a value other than null; its return value will be the new A.
If an overloaded proc is not available for an operator you try to use on a datum, a runtime error may result.
The output and input operators are given special treatment. If no overload
is defined for the current left-hand-side value, the overload proc is looked
up under world
instead. The world overload proc is also a
fallback if ..() is called, and after that ..() does the
default behavior. These procs are always called with multiple arguments, to
distinguish them from bitwise shift operators. The output version gets a
third argument when the result output() is sent, since that can
include a window reference.
The list access operators are a special case as well, because reading to a list and writing to it are different things, so there are two procs for the purpose. The [] overload is for reading, and []= is for writing.
There is also now an overload for converting a datum to text. By having operator"" return a text string, that text will automatically appear anywhere you embed the datum in a string, use json_encode() on the datum, or many other situations. It won't work for atoms being sent directly to output (e.g., world << obj) or other skin controls because the client has special handling for these situations and the client isn't given any info about the overloaded text. Likewise, the overloaded text won't appear for objects in an input() prompt list, which is also handled mainly on the client. Despite these limitations, the text overload offers greater flexibility.
Procs may be derived from /proc. These procs are "global", in that they can be called anywhere in the code.
The proc poof() may now be called anywhere in the code.
Procs may also be attached to objects by defining them under the appropriate object/proc subnode. Currently DM allows procs to be defined or overridden for /mob, /obj, /turf, /area, world, and /client, as well as for datum objects derived from /. Predefined procs are discussed under the "procs" entry for the object type.
This can be called by a mob var M, using M.poof().
It is possible to define what type of value a proc is expected to return, by following its definition with an as clause. This can be a type path, such as as /mob/player, or a more intrinsic type like as num or as list.
Currently the only purpose for using the as clause is for situations where the compiler needs to infer the type of an expression. Mainly this applies to the . and ?. operators in an expression such as GetTarget()?.Attack(src). Giving GetTarget() a return type allows the compiler to check if Attack() is a valid proc for /mob/player. Otherwise, the . and ?. operators act like : and ?:, respectively; the compiler won't do any checking to see if Attack() is valid.
Call the current proc. A proc that calls itself is said to be recursive.
This computes the factorial N! by calling itself recursively.
If object O is derived from object P, P is called the parent of O. If a proc (or verb) is defined in both O and P, O can call P's version by using ..().
Here O is derived from P. When P calls "history", his name is displayed. When O calls "history", his name is displayed, followed by the name of his parent, P.
If O overrides the same proc more than once, ..() will search for the previous version and use that. For instance, you could have two O.history() procs; the second overrides the first, but the original could still be called via ..(). The original in turn could call ..() to reach P.history(). Overriding the same proc more than once in the same type should be avoided wherever possible, because it incurs extra overhead, it makes the code harder to read, and it isn't always clear which one gets called first. (Usually, the only time you'll want this to happen is when using libraries.)
..() can also be used for predefined procs.
This proc will print "moving..." whenever the mob moves.
This is used to make a sanity check. If the given expression is false, the current procedure crashes, generating diagnostic debugging output, which includes the expression, a stack dump, and so forth.
Crashes the current procedure, displaying the specified message and generating diagnostic debugging output, such as a stack dump.
This instruction returns text containing the first argument followed by the second, followed by the third, etc. The arguments may be constants or variables containing text.
This instruction exists primarily for backwards-compatibility. You can accomplish the same thing with the + operator or by using embedded expressions.
This sleeps the current proc until the user clicks one of the named buttons. As with input(), the first argument may be entirely left out.
A slightly more complicated example provides the user with a choice in the matter:
This proc creates an animation sequence that will be displayed to players. Starting with an atom or image, you can change one or more vars that affect its apprearance. This change will take place immediately, but will be displayed to users as a gradual change over a period of time. The actual interpolation between frames is all done on the client.
If the Object argument is left out, a new animation step will be created for the previously used animation seqeunce. If all other arguments are left out, this is tantamount to saying you want to start a new animation that does nothing, effectively ending the animation entirely.
The following vars will animate smoothly:
These vars can be changed, but will change immediately on each step rather than smoothly:
Other vars may apply:
For convenience, you can use an associative list, appearance, or mutable appearance in place of the appearance vars. You can use appearance itself as a name for this argument, or leave the argument unnamed.
Animation doesn't have to be strictly linear. Some changes look much better if they follow a curve. A cubic curve, for instance, will start slow, accelerate very quickly in the middle, and slow down again at the end. A sine curve could be used with a flip transformation to make a coin appear to spin. A text bubble can jump into place and bounce a little before it settles. The choice of curve you use is called easing, and you have several good choices to pick from.
These can be combined with EASE_IN or EASE_OUT using the | operator, to use just the first or last part of the curve.
Some easing functions may overshoot one line or the other, so it's fully possible to have a pixel_w value, for instance, animate from 0 to 100 but actually end up briefly outside of that range during the animation.
Any combination of these flags may be used for animation (use + or | to combine them):
Filters can be animated too. If you want to animate a filter, you need to specify the filter to be animated. If the last call to animate() used the same object as this filter, or a different filter for that object, then this will be treated as a new step in the same animation sequece. Likewise, if the last animate() call was to a filter, and this call is for the object that filter belonged to, again it will be treated as a continuation of the sequence.
When arctan is called with just one argument, the resulting angle can range from -90 to 90.
The two-argument form uses the polar angle. This angle starts at 0° for due east, and increases counter-clockwise from there. Therefore 1,0 has an arctangent of 0°, 0,1 is 90°, -1,0 is 180°, and so on. At point 0,0 the angle is undefined since it could be any angle, but arctan will return 0.
Here's another example, in which a rotating turret points to a target on another tile.
Normally, if you were to pass a list directly to a procedure, it would only
come through as a singe argument to that procedure. In some cases, you might
instead want the items in the list to become the arguments to the procedure.
That is what arglist()
achieves.
If the items in the list are associations, these are treated as named arguments. Each such list item is matched against the names of the procedure arguments and its associated value is assigned to that parameter.
Most built-in DM instructions do not support use of
arglist()
, but all user-defined procedures automatically
support it. The built-in instructions which support named arguments will also
support arglist()
.
The following example shows how to use arglist()
with both
positional parameters and named arguments. Both of these examples could be
replaced by a much simpler direct call without need for a list to hold the
arguments; this is just to illustrate the syntax.
The parameters to a proc are referred to as arguments. To define argument variables, place them inside the ()'s in the proc definition. A default value may be specified. Otherwise, arguments default to null.
Note how the variable type may be specified. It is just like any other
variable definition, except "var/
" is implicit and does not need
to be typed.
The parameters passed to a procedure are called arguments. These may either be passed in positional order, or they can be passed as named arguments. Not all procedures are defined with the intention of supporting named arguments, so consult the documentation for the procedure in question first. (This is mainly an issue of whether the argument names might change in the future.)
The following example shows several ways of producing the same call to a procedure.
To prevent silent errors, named arguments that do not match any of the arguments of the procedure being called will generate a runtime error. This is somewhat different from the behavior of positional arguments in DM where it is perfectly acceptable to pass more arguments than were explicitly defined in the procedure.
As always, arguments that are not assigned in the call will simply be given the value null (or whatever default value is specified in the definition).
When an object procedure is overridden, the variable names in the new definition are the ones that get matched against named arguments in a call to that procedure. A procedure which is intended to support named arguments should therefore be defined with care so as to conform to the interface expected by users of the procedure. That doesn't stop you from changing that interface when overriding a procedure, but the normal case would be to preserve the argument names of the base procedure when overriding it.
The following example is not useful, but it illustrates a situation where a procedure is overridden so as to preserve the same argument names and positions. As mentioned above, you are not required to preserve either the names or positions, but that is usually what you want.
This example merely used positional parameters in the call to
..()
, but one can use named arguments there too if it is
desirable.
The best time to use named arguments is when calling a procedure that takes a lot of optional parameters. You can just name the ones that you want to assign and leave the rest unspecified. Trying to do the same thing with positional parameters can be much more awkward–especially when the arguments you do want to assign are preceded by a number of ones that you don't care to assign. It's easy to lose your place in the list or to forget what it does.
Since named arguments involve a slight amount of extra overhead, one should avoid them in code that is highly cpu intensive due to being called many many times. Otherwise, code clarity may be a bigger priority.
ASCII codes are numerical values corresponding to keyboard and special characters. Among other things, they are used to represent many symbols in HTML. This proc converts an ASCII code to its corresponding text representation.
BYOND now supports Unicode via UTF-8 encoding, so you can use the character code for any valid Unicode character, not just ASCII.
The following example shows how to loop over a block of turfs.
In the version that uses coordinates directly instead of two turfs, you can leave any of the EndX, EndY, or EndZ values as null, and omit the last arguments entirely; they will default to using the corresponding StartX, StartY, or StartZ. Therefore this example is equivalent to the one above:
To leave Ref out of the results, use obounds() instead.
Calling bounds() will default to bounds(src,0), if src is a turf, obj, or mob. This returns all turfs, objs, and mobs (including src) within src's bounding box.
Changing the distance will return all objects within that distance from the bounding box. E.g., bounds(turf,12) will show you everything within 12 pixels of that turf.
An object's bounding box can also be offset. bounds(src,-6,0) shows what src would touching if it moved 6 pixels west. bounds(turf,-12,-12,24,24) is equivalent to bounds(turf,12).
In the final form, bounds() can use absolute coordinates and does not need an object to be Ref. Absolute coordinates start at 1,1 at the lower left corner of the map, by tradition.
The value returned by bounds_dist() is the number of pixels that the two objects would have to move closer together (if this is even possible, of course) to be touching but not overlapping.
A return value of 12 for instance means the two objects have a gap of 12 pixels between them.
A return value of 0 means the two objects are not overlapping, but their bounding boxes touch.
A return value of -2 means the two objects are overlapping by 2 pixels; they would have to move 2 pixels apart to separate.
Terminate the loop with the given label. If no label is specified, the
innermost loop containing the break
statement is assumed.
The zapper object kills the first mob it finds that doesn't belong to a
player. If none can be found, it kills the user. Be careful! Note how
this code takes advantage of the fact that the loop variable M
will be null
if the loop terminates normally.
For an example of how to use labeled loops, see the reference section for
the continue
statement.
This sends the html text or file to the user and optionally displays it in the web browser. The default action is to use the embedded browser panel in the Dream Seeker window; specifying an alternate window name (see below) causes it to appear in a popup window. Passing in 'null' for the html text causes the browser panel or named window to be closed.
The option parameters should either be omitted or they should be in a text
string of the following format:
"window=name;file=name;display=1;
size=300x300;border=0;can_close=1;
can_resize=1;can_minimize=1;titlebar=1"
You may use commas (,), ampersands (&), or semicolons (;) as the delimiter. Any or all of the parameters may be specified and they may be included in any order.
When display=0, all of the other arguments besides file are ignored.
WIDTHxHEIGHT
.
Note also that many display options can be controlled through the html
itself. For instance, to turn off the scrollbars, you can do:
<body scroll=no>
; to add a title, you can do:
<head><title>My Title</title></head>
; and so forth.
The following example displays a help page in a popup window.
This sends the specified resource file to usr (or anybody else) and
stores it in their cache
directory with the specified name. In
subsequent browse()
output, you can then refer to that file.
If your world is always running on the internet, you can save yourself
the trouble and simply link to the image files through a web server.
However, if it may be played offline, you can compile in the resource files
and manually send them to players with browse_rsc()
.
Note that no data is transmitted if it already exists in the user's
cache, so there is little overhead in calling this every time you are about
to use browse()
.
This instruction exists in order to call procs dynamically, since the proc reference or name may be an expression rather than a hard-coded value. This may serve the same purpose as a "function pointer" in C programs.
The following examples do not demonstrate why you would want to do this, but the syntax is illustrated. The first one calls a specific procedure by using a path reference to that procedure.
The next example calls an object procedure (or verb) by name, rather than by path.
In prior versions, call() was also used to access third-party libraries (.DLL files on Windows, .SO files on Unix), but this has been moved to call_ext() for clarity.
This instruction exists in order to access third-party libraries (.DLL files on Windows, .SO files on Unix), as long as the one or more of the following conditions is met:
The standard way of making external calls (and until version 515, the only way) uses strings for everything. Any arguments that are not strings are passed as empty strings instead. The call is prototyped in the DLL this way:
extern "C" char *func(unsigned int argc, char *argv[]);
The argc argument is a number of arguments, and argv is an array of the arguments themselves. The integer must be 32-bit.
As the library prototype is char**, the call_ext() arguments must be strings. Other types (like numbers) will be passed as the empty string ("") into the library function.
// test.dll, a win32 C++ library compiled in VC++: #include <string.h> // This is an example of an exported function. // Windows requires __declspec(dllexport) to be used to declare public symbols // The name of the function from within the dll may be compiler-dependent // (in this case it will usually be "merge" or "_merge"). // Google "name decoration" for more information on this exciting topic. extern "C" __declspec(dllexport) char *merge(int n, char *v[]) { static char buf[500]; *buf=0; for(int i=0; i<n; i++) { strcat(buf,v[i]); // we should bounds-check but it's a stupid example! } return buf; }
The char * pointer returned by the library is expected to be cleaned up by the library when it's unloaded, or it can be cleaned up on a subsequent function call. BYOND makes a copy of the string when the function returns and does not need it after that.
A newer and more flexible way of calling external libraries is now available, and it allows you to pass strings, numbers, and references, and also get other types of valus in return. This uses Byondapi and requires your external library to be compiled with the byondapi.h header file (if using C or C++). Byondapi also includes helpful C++ wrapper classes in separate files.
With Byondapi calls, the function name you use in call_ext() should be prefixed by byond: so that the engine knows what type of function it is. In your library, the call is prototyped like so:
extern "C" CByondValue func(u4c argc, CByondValue argv[]);
The u4c type is an unsigned 32-bit integer, defined in byondapi.h. CByondValue is also defined there. Interacting with a CByondValue structure requires the functions exported as part of Byondapi.
// test_byondapi.dll, a win32 C++ library compiled in VC++: #include <byondapi.h> #include <stdlib.h> #include <string.h> // a different take on the merge example above extern "C" BYOND_EXPORT CByondValue merge(int n, CByondValue v[]) { CByondValue result; u4c buflen = 1024; // initial size of buffer u4c totallen = 0; // length of total string so far (not including trailing null) char *buf = (char*)malloc(buflen); if(!buf) { // we couldn't allocate memory ByondValue_Clear(&result); return result; } for(int i=0; i<n; i++) { u4c len = buflen - totallen; // # of bytes left in buffer bool success = Byond_ToString(v[i], buf+totallen, &len); // on success, len is filled with # of bytes written (including trailing null) if(success) { totallen += (len-1); } // if we failed but len > 0, it's the new size of the buffer we need else if(len) { len = (len + 1023) & ~1023; // round up to 1K increments char *newbuf = (char *)mallloc(len); if(!newbuf) { // out of memory; stop here ByondValue_Clear(&result); return result; } memcpy(newbuf, buf, totallen+1); // include trailing null in copy free(buf); buf = newbuf; buflen = len; --i; // retry Byond_ToString() for this argument } // if we failed but len == 0, there was an error else { free(buf); ByondValue_Clear(&result); return result; } } ByondValue_SetStr(&result, buf); // create a new internal string free(buf); // free the buffer return result; } extern "C" BYOND_EXPORT CByondValue average(int n, CByondValue v[]) { CByondValue result; float total = 0.0f; int count = 0; for(int i=0; i<n; i++) { if(!ByondValue_IsNum(v[i])) continue; total += ByondValue_GetNum(v[i]); ++count; } if(count) ByondValue_SetNum(&result, total / count); else ByondValue_Clear(&result); // return null on failure return result; }
You are of course allowed to mix different argument types, so they don't all have to be numbers or all strings. Your library code can use the Byondapi functions to interact with these values.
For advanced users: on Windows, call_ext() uses the __cdecl convention by default. If you are designing or linking to a DLL that uses the __stdcall convention instead, you can inform call_ext() by prefacing the function name with the "@" symbol. E.g., call_ext("test.dll","@merge") would call a version of merge declared with the __stdcall convention. Likewise if you use the Byondapi version, you can use call_ext("test.dll","@byond:merge") or call_ext("test.dll","byond:@merge") (it doesn't matter which order the prefixes go in).
Returns the ceiling of A (the largest integer greater than or equal to A).
The result could be used as a unique directory name in a server-side save file. Each player could be stored in a separate directory. By converting to canonical form, possible problems resulting from punctuation (like the path delimiter '/') in the key would be avoided. If players are saved in stand-alone files, it could be equally useful for generating a unique file name.
Note that this may be used on any text string. It is not just limited to keys.
This example defines two procs for saving and loading players to a server-side file. These could be called in mob.Login() and mob.Logout(). Notice that instead of calling SaveFile.Write(M), this example instead calls M.Write(SaveFile) directly. The difference is that in this example we did not want a new mob to be created when loading the player but instead wanted to load information into an existing mob.
In this example, the ckey() proc was used, but it would be more efficient to use mob.ckey, which is the same value precomputed.
The true canonical form of a key is in all lowercase, but occasionally, it is nice to preserve case when stripping a key (or other text) of any special characters.
Note: This proc used to be named cKey, like ckey but with a capital k. To avoid confusion it has been renamed, but old code will still compile with a warning.
"Clamps" a number to a given allowable range from Low to High. If the number is already in that range, it is unchanged. Otherwise, the closer of Low or High is returned.
This is effectively equivalent to min(max(Number, Low), High), but with some slight differences. For one thing, clamp() is forgiving if you accidentally swap Low and High; it will just swap them back so Low is always lower. Also, because this is a single proc call it's slightly faster.
The list format will accept a list in place of a number as the first argument, and it behaves as if you looped through the entire list and ran clamp() on each number or null value. Please note the original list will be modified. If you want to leave the original list alone, use the Copy() proc to pass a copy to clamp() instead.
This instruction is NOT sensitive to case. It also ignores the
\proper
and \improper
text macros. The
case-sensitive version is cmptextEx().
This outputs "Equal!" since "Hi" and "HI" are the same, ignoring case.
This instruction is sensitive to case. The case-insensitive version is cmptext().
Because identical text is internally combined to conserve memory, cmptextEx(T1,T2) is equivalent to (T1 == T2).
This outputs "Not equal!" since "Hi" and "HI" are different when taking case into account.
Note: This proc used to be named cmpText, like cmptext but with a capital T. To avoid confusion it has been renamed, but old code will still compile.
Begins the next iteration of the loop with the given label. If no label is specified, the innermost loop containing the continue statement is assumed.
In a for(Init,Test,Inc) loop, the continue statement will jump to the Inc portion (if any) and move on to the conditional Test. In a for(item in list) loop, it will skip to the next item in the list. In a while or do-while loop, continue jumps to the condition in the while statement.
This displays a list of players who have a mob in the world. The
continue
statement is used here to avoid including the user in
the list. The same thing could have been achieved by using only the
if
statement. In more complicated situations, however, very
long conditional expressions and deeply nested if
statements
can be avoided by using continue
and its companion
break
.
Here is an example using a label to continue an outer loop from inside an inner one:
This displays a list of mobs who do not belong in anyone else's group. Notice the syntax for labeling a list. The name of the block is simply placed in the code followed by a colon and its contents are indented inside it.
Copy characters in T between Start and End. The default end position of 0 stands for length(T)+1, so by default the entire text string is copied.
If the start or end position is negative, it counts backwards from the end of the string.
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use copytext_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
Destroy an object and null out all references to it. Procs that are
executing with src
equal to that object are silently killed,
causing execution to return to the caller. If that is not what you want, you
should detach the proc from the source object by setting src
to
null
.
When an object is deleted, its Del() procedure is called. Currently, if the Del() procedure does not execute the default action (by calling ..()), then the deletion of the object is aborted. You should not depend on this, as it may change. In other words, be sure to always call the default handler after doing your own stuff.
While manual object deletion is useful in many cases, the search for live references to an object does take some time. The more active objects in the world, and the more variables in those objects, the longer the search will take. For larger projects, this search time can become significant. In these cases, as a best practice, manual deletion should be avoided by ensuring that all references to an object are taken care of when the need for object destruction arises. Objects that have no references are deleted automatically without the need for a search. See garbage collection for more details.
Execute Statement. If E is true (non-zero) do it over again. Continue until E is false (zero).
Statement may be a block of code or a single statement.
This outputs:
This is used to create an /exception datum, and is shorthand for calling new/exception(value, __FILE__, __LINE__). The value you provide will be in exception.name.
Src may be either a cache file, a savefile, or the name of an external file. Cache files are specified in single quotes and external files are in double quotes. If the path to the destination file does not already exist, it will be created.
If the source and target are paths ending in "/", the contents of the source directory (including sub-directories) will be copied to the target path.
This instruction could be useful when players upload files (like code) that you might want to dump to an external file.
This (somewhat dangerous) example allows players to upload code, recompile, and reboot the world. It assumes that DreamMaker is in the path where the shell looks for executable files and also that the name of the running world is world.dmb.
The file to copy may either be a file name (text string) or the return value of file() operating on the same. If a cache entry is passed as the argument, it will simply be returned with no action necessary.
Once a file has been copied into the resource cache (i.e. the world's .rsc file), it may be used as an icon or a sound or whatever is appropriate. Most internal operations involving resource files automatically perform this operation when you try to use an external file in place of a cache entry. For example, when assigning a file() object to atom.icon, fcopy_rsc() is implicitly invoked.
The main reason you would ever want to call this explicitly is if you are storing references to resource files in your own data structures and you want to ensure that all values are converted to cache entries so they may be directly compared to one another.
If the specified file ends in '/
', it is treated as a
directory. Any contents (including sub-directories) are deleted as well.
Be careful!
Returns a file object corresponding to the named file. This file object can then be used in a variety of ways. One would be to send it to a player to view using the browse() instruction. Output may also be appended to the file using the << operator.
Note that the file exists in the external filesystem (ie the hard disk) and not the cache. That means the path is specified in double quotes and will be evaluated at run-time rather than compile-time. The file need not exist at compile time and may even be modified at a later date. This is the principle reason for using a file in the filesystem rather than a cached resource file (specified in single quotes).
Many DM instructions that deal with files treat file("name") and "name" the same. There are cases such as browse() where a simple text string is not interpreted as a filename; it is in those situations where file() is really necessary.
This can be useful when interacting with external applications that generate output in a text file. For example, you might have an external program that mimics conversation:
Creates a graphical filter that can be assigned or added to a list of filters on an atom or image.
This proc uses named arguments, and the "type" value must always be included. To see which types of filters are available and what parameters they accept, see Filter effects.
A filter created with this proc is an abstract filter; it is not associated with any atom. When you add it to atom's filters, the atom gets a copy of this filter, so changing the abstract filter's values afterward will not change the atom's filters. For the same reason, an abstract filter can't be animated.
A filter that is part of an atom's filters list, like obj.filters[1], is an attached filter. Changing the values for an attached filter will change how that atom is displayed, and attached filters can be animated.
This instruction is NOT sensitive to the case of Haystack or Needle. The case-sensitive version is findlasttextEx().
If the start or end position is negative, the position is counted backwards from the end of the string. E.g., findlasttext("Banana", "na", -3) starts three characters from the end and will skip over the last "na".
Note: Unlike findtext(), a regular expression may NOT be used as the Needle. Searching backwards is simply too complex for the regular expression engine.
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use findlasttext_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
This instruction is sensitive to the case of Haystack and Needle. The case-insensitive version is findlasttext().
If the start or end position is negative, the position is counted backwards from the end of the string.
Note: Unlike findtextEx(), a regular expression may NOT be used as the Needle. Searching backwards is simply too complex for the regular expression engine.
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use findlasttextEx_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
When Needle is text, this instruction is NOT sensitive to the case of Haystack or Needle. The case-sensitive version is findtextEx().
This outputs "Found!", since "there" is a part of the string "Hi There", ignoring case.
If the start or end position is negative, the position is counted backwards from the end of the string. E.g., findtext("Banana", "na", -3) starts three characters from the end and only searches the final "ana".
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use findtext_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
When Needle is text, this instruction is sensitive to the case of Haystack and Needle. The case-insensitive version is findtext().
This outputs "Not found!", since "there" is not a part of the string "Hi There", taking into account case.
If the start or end position is negative, the position is counted backwards from the end of the string. E.g., findtextEx("Banana", "na", -3) starts three characters from the end and only searches the final "ana".
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use findtextEx_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
Note: This proc used to be named findText, like findtext but with a capital T. To avoid confusion it has been renamed, but old code will still compile.
Cause the icon attached to Object to be temporarily replaced with the specified icon or icon state for the duration of the animation. This is a purely visual effect and does not effect the actual value of the object's icon variable.
The target object may be any atom or image.
The path is of the form "dir1/dir2/.../file". Only files beginning with the "file" part are listed, so be sure to end a directory name with "/" if you wish to see its contents. Otherwise you will just get that directory name back with a "/" appended.
Only files and sub-directories directly contained in the specified path are listed (ie not the contents of the sub-directories too). The file names in the list do not include the path information but just the bare file name.
Returns the floor of A (the largest integer less than or equal to A).
The for proc can be used to iterate values over a fixed range or list. Consult the appropriate entry for more information.
This loops M through the mobs in view(), outputting the name at each iteration.
When you loop through a list, with the exception of looping through world, you're actually looping through a copy of that list. If the list changes, those changes won't have any bearing on this loop. If you want to be able to handle a list that might change, you'll need to use the for loop proc instead.
You can declare the variable right inside the for statement. Its scope is entirely contained within the for statement, so it will not conflict with a similar variable declared elsewhere in the same procedure.
If the loop var has a type, a hidden istype() call is included in the code. Only items of that type, or its descendants, are handled within the loop. That means null values and objects of unrelated types will be skipped.
The numeric loop form is a quick internal version of the for loop proc. It's equivalent to for(Var = Start, Var <= End, Var += Step) unless Step is negative, in which case a >= comparison is used instead. The main difference is that unlike in a for loop proc, the values of Step and End are calculated at the beginning and never change after that, so an expression like list.len that might be subject to change will not be read again—in much the same way that looping through a list only loops through a copy of that list.
Note: Although you can use fractional values for step in this numeric format, there may be accuracy considerations to keep in mind. See Numbers for more information.
First execute Init. Then if Test is true (non-zero), execute Statement. After this execute Inc. Continue checking Test, doing Statement, and performing Inc until Test turns out to be false (zero).
Statement may be a code block or a single statement. Semicolons may be substituted for commas inside the parentheses as a convenience to C/C++ programmers.
Init and Inc may be omitted. If Test is omitted, the loop will continue forever (unless a break, goto, or return instruction is used to get out of the loop).
This outputs:
Note: An Inc statement like i += 0.1 is perfectly valid, but you should keep in mind that numerical accuracy is not exact. See Numbers for more information.
Returns the fractional part of the number A, with the same sign. This is everything after the decimal point.
Sends a file to the target with the (optional) suggested name for saving to disk. The file may be a cache file (loaded at compile time) or an external file (accessed at run-time). Cache files are specified in single quotes, and external files are in double quotes.
This function could be used to distribute source code, supplementary documentation, or anything.
This example allows the user to download the icons from other objects in the game.
Creates a generator that can be used to produce a random value. This generator can be used in client-side particle effects, or it can be used in proc code. The types of values it can produce are numbers, 3D vectors (list of 3 numbers), or colors (a text string like "#rrggbb" or a color matrix).
Generator type | Result type | Description |
---|---|---|
num | num | A random number between A and B. |
vector | vector | A random vector on a line between A and B. |
box | vector | A random vector within a box whose corners are at A and B. |
color | color (string) or color matrix | Result type depends on whether A or B are matrices or not. The result is interpolated between A and B; components are not randomized separately. |
circle | vector | A random XY-only vector in a ring between radius A and B, centered at 0,0. |
sphere | vector | A random vector in a spherical shell between radius A and B, centered at 0,0,0. |
square | vector | A random XY-only vector between squares of sizes A and B. (The length of the square is between A*2 and B*2, centered at 0,0.) |
cube | vector | A random vector between cubes of sizes A and B. (The length of the cube is between A*2 and B*2, centered at 0,0,0.) |
The optional rand argument determines the type of random distribution.
UNIFORM_RAND | Default. Random values are uniformly likely to be chosen. |
NORMAL_RAND | Approximates a Gaussian normal distribution, but on a finite interval. Values closest to the mean are more likely to be chosen, while the extremes are much less likely. |
LINEAR_RAND | The probability of choosing a number is proportional to its absolute value. |
SQUARE_RAND | The probability of choosing a number is proportional to its square. |
The result of calling generator() is a datum of type /generator and you can get a random value from it by calling its Rand() proc.
If the direction is not directly lying along one of the four primary cardinal directions, the result will become the nearest diagonal direction (eg. if Loc2 is mostly north but a little to the east of Loc1, the direction returned will be NORTHEAST).
For a distance in pixels, use bounds_dist().
get_dist() will return -1 when Loc1 and Loc2 are the same object. If one or both of them is not on the map, an infinite value is returned.
Note: Prior to BYOND 515, get_dist() never returned a value greater than 127, which it counted as "infinite".
Calculate the position of a step from Ref in the direction Dir.
Calculate position of a step from Ref on a path away from Trg, taking obstacles into account. If Ref is farther than Max steps from Trg, 0 will be returned.
Calculate position of a step from Ref in random motion.
Calculate the position of a step from Ref on a path to Trg, taking obstacles into account.
If Ref is within Min steps of Trg, no step is computed. This is also true if the target is too far away (more than twice world.view steps). In either case, null is returned.
Calculate the position of a step from Ref in the direction of Trg.
Calculate a set of steps from Ref on a path to Trg, taking obstacles into account. The result of the proc is a list of directions that can be used with step(), or null if a path could not be found.
If Ref is within Min steps of Trg, no steps are computed. This is also true if the target is too far away (more than twice world.view steps). In either case, null is returned.
Jump to the specified node in the current proc.
This displays "The end".
Note: goto should be used judiciously. It's easy to fall into the trap of "spaghetti logic" where goto is relied on so much that it becomes too difficult to follow how the flow of code execution will proceed. Normally, you'll want to use a construct like while() or for() loops, and break and continue statements. goto is for more complex situations that aren't readily handled by any of these.
Interpolates between two or more colors along a color gradient. By default, gradients extend from an index of 0 to 1, but they are allowed to go beyond that if you choose.
The simplest way to use this proc is to interpolate between two colors:
Anything that applies to color gradients can be used in this proc, so you can have a looping gradient, or a gradient that uses a color space other than RGB.
In the first format where you specify all the items separately, you can use named arguments for index and space (the gradient's color space). If you don't specify an argument called "index", the last argument is assumed to be the index.
The gradient(Gradient, index) format is used for cases where you want to pass an existing gradient to the proc.
This is just like viewers()
, but it is a list of mobs that can
hear the center object. Currently, this is computed on the assumption that
opaque objects block sound, just like they block light.
Special characters such as < and > are not displayed literally in
html and may produce garbled output. To display these characters literally,
they must be "escaped". For example, < is produced by the code
<
and > is produced by the code >
.
The html_decode()
instruction takes a text string containing
such escaped symbols and turns them into their literal counterparts. The
more useful function is html_encode()
which does the reverse.
Special characters such as < and > are not displayed literally in
html and may produce garbled output. If you want to ensure that an entire
text string is displayed literally, you can "escape" those characters. For
example, < is produced by the code <
and > is
produced by the code >
.
The html_encode()
instruction does this for you
automatically. If you wanted to disallow html input from players, you
could use this to force their text to be displayed literally:
If a URL is included in the text, special characters like & that are part of the URL will be skipped. This keeps automatically created links in the output from being broken.
Note for BYOND oldies: the old-style formatting codes such as "\red" which are still parsed but not encouraged are completely stripped out by html_encode().
This is equivalent to new/icon(). It creates an /icon object, which is initialized to contain the same graphical data as the given file. If an icon state or direction are specified, only those parts of the original icon will be included in the new icon object.
Icons may have one or more internal states, which are identified by name. The state "" is the default.
If you are not using the TILED_ICON_MAP value for world.map_format, you can ignore the mode argument.
When graphics bigger than world.icon_size are used as an icon, and the map_format in use is TILED_ICON_MAP, they are internally broken up into tiles, one per icon state. The mode argument was added for big icons that get split into several smaller tiles. Those icons have several smaller states per true icon_state. For example if your 64×64 icon has a state named "open", it will contain states "open", "open 0,0", "open 1,0", "open 0,1", and "open 1,1" which are all used internally. (If the state name is blank, the sub-states are just "0,0", etc.) When using the TILED_ICON_MAP format, you need these for displaying the icon over several different atoms.
mode=0 will only show the sub-states ("open 0,0" and so on), all of which can be safely extracted in a single-tile icon via the icon() proc. mode=1 will show the main state names ("open"); any time you work with that state name you're working with the full-size icon. mode=2 will show all of the states.
If the expression E is true (non-zero) then execute Statement1. Otherwise, test E2, etc. Finally, if none of the expressions are true, execute Statement3. The else nodes are all optional.
Statement1, Statement2, and Statement3 may be a single statement or a code block with optional braces: {}.
This will display "TRUE" if T has value 1, and "FALSE" otherwise.
Images are "virtual" objects, which have a purely visual effect. Once created, they can be made to appear to selected players. The image() instruction is simply a short-hand for new/image().
The image remains attached to the location specified by loc. For example, if loc is a mob, the image will appear above the mob until it is destroyed.
The arguments icon_state, layer, and dir may be used to override the settings associated with the icon or object used to create the image. For example, the default drawing layer for an plain icon is FLY_LAYER (above all other objects), but you could change this to OBJ_LAYER to make it appear under mobs on the map.
Another common use of images is in making an overlay:
Since the loc
argument could never be a text string, the above
statement can be further shortened:
This is much preferable to achieving the same effect with
icon('pants.dmi',"red")
, since that involves the
overhead of creating a new icon file, which should only be done when it is
really necessary.
This returns the original compile-time value of a variable. It could be used to reset a variable to its default value or to check if a variable has changed.
Creates a prompt dialog that asks the user for a response. The current proc sleeps until they respond.
The only required argument is the message. The type may be any combination of input types allowed for verb arguments, which can be combined with the | operator. The null type will allow the user to cancel, e.g. as null | anything in contents.
If the target of the input prompt is not a player, the result will be the default value. If no default value is specified and null is allowed by the input type, null will be returned. Otherwise, an error will result, crashing the proc that called input().
A more common use for input() is to give a player a list of things to choose from. For example, this is a simple shopkeeper NPC, where the shopkeeper's inventory is its contents.
Using as num is another popular input choice, which you might use for haggling, deciding how many of an item to pick up or drop, etc.
This next part is important! Always validate input from a user to make sure it's correct.
You should be sure to sanitize any user input to make sure the value is valid. For instance, if you have a verb that gives gold to another player, you should check that the amount isn't negative and doesn't contain any fractions, and isn't more than they have.
Likewise if you're allowing a user to input text, it too should be sanitized. If they shouldn't enter multi-line text, you should strip out "\n" characters. If they're putting in something like a character name, strip out any HTML via html_encode(), or you can simply reject anything that contains invalid characters and make them do it again.
This returns a true value when given a file. Both objects returned by file() and files stored in the resource cache qualify.
This returns a true value when given an icon. Both /icon memory objects and icon files stored in the resource cache qualify.
Tests whether an object is a list. This includes user-defined lists, special lists like contents and overlays, and more.
Tests validity of a location. If all arguments are mobs, objs, turfs, or areas, this returns 1.
For a single argument this is equivalent to: (ismob(Loc) || isobj(Loc) || isturf(Loc) || isarea(Loc)).
Movable atoms are either objs or mobs, so this combines the isobj and ismob tests into a single proc.
Some math operations return the special number NaN if they're undefined, such as dividing 0 by 0. This tells you if a number is that type.
NaN is never greater than, less than, or equal to another number, even itself.
Tests whether an value is a pointer.
Note: This does not check if the pointer is still valid, like for instance if the object it belongs to has been deleted, or if it points to a list index that is now out of bounds.
This returns 1 if the given variable should be automatically saved when writing an object to a savefile and 0 otherwise. Variables which are not global, const, or tmp will return 1.
If you don't have an object instance to test, but just want to see if one prototype derives from another one, use ispath() instead.
Using implicit types, that same example can be rewritten as follows:
Any items in List that are not already text will be converted to text. The Glue string only goes between two items, so a single-item list is the same as converting that one item to text, and an empty list becomes an empty string.
If the start or end position is negative, the position is counted backwards from the end of the list.
Calling List.Join(Glue,Start,End) is the same thing, as long as List is a valid list.
Arrays like [1,2,3] will be converted to regular lists like list(1,2,3).
Object literals like {"a":1} will be converted to associative lists such as list("a"=1). Each item in the list is also decoded. Except in strict mode, non-string values are allowed as the "keys" in an associaitve list, even though that's not valid JSON, and strings used as keys can be left unquoted. BYOND doesn't care, as long as it can understand the formatted text it's given. The only exception is that a number isn't allowed to be an associative list key, and will be converted to a string instead, so {1:2} becomes list("1"=2).
Special numbers NaN and Infinity are recognized correctly (these are case-sensitive). All other numbers are parsed and stored in the regular BYOND format (32-bit floating point).
Since BYOND doesn't have dedicated boolean values, true and false are interpreted as 1 and 0, respectively.
The JSON_STRICT flag uses stricter JSON parsing rules and will not allow some things. In strict mode:
The JSON_ALLOW_COMMENTS flag allows you to include // single-line comments and /* ... */ long-form comments in the text to be decoded. This can be mixed with the strict flag. This flag is now used by default.
If Value is a simple list or a matrix, the result will be formatted as a JSON array, and each item in the list is encoded.
If Value is an associative list, the result will be formatted as a JSON object literal, and each item and associated value is encoded. (The keys in the object literal don't have to be strings. Even though that isn't valid JSON, BYOND doesn't care. Be aware however that strict JSON parsers will care.)
Datums are not serialized, but are converted to the equivalent of "[Value]" instead.
The special numbers NaN and infinity will be encoded as object literals in a form like {"__number__":"NaN"}.
BYOND formatting such as \red is removed from encoded strings.
Situations where a list has a reference to itself will cause the nested version to print a null value instead.
The JSON_PRETTY_PRINT flag introduces spacing into the output for improved readibility. Spaces will be added after colons and commas. Non-empty arrays and object literals will have a line break and tabs before each item, and a line break with one fewer tab before the closing bracket or brace. (The special formatting for numbers like Infinity, or the list form for matrices, will not be given tabs and line breaks.)
This outputs, "2", the length of the string "Hi".
This outputs, "3", the length of the list.
This outputs the length of the file.
Note: In strings containing non-ASCII characters, this is the length in bytes, not characters; a character may span multiple bytes. Use length_char() to work with character counts instead of bytes. See the Unicode section for more information.
Deprecated. Use length() instead.
This causes the recipient (O) to view the specified url. The url could be a web or BYOND address. In the latter case, the player will disconnect from the current world and connect to the specified one.
The format of a BYOND url is as follows:
To access a registered world, address:port may be replaced by the registered name in the hub. The optional topic data is processed by the world once the player has connected. If only a topic is specified, the current world processes it.
Assign elements to a list.
This creates a new list 'L' that initially contains elements 1, 2, and 3. The length of L is 3.
The list()
instruction may also be used to create associative
lists.
That creates a list with contents ("player, "score") and associated values ("James Byond", 2000) respectively.
The index values should be constants, and that usually means text constants. When these index values happen to be text strings that satisfy all the requirements for variable names, this may also be written in a convenient short-hand without the double quotes:
In other words, this is exactly the same syntax as for named arguments.
This instruction converts a list of parameter names and associated values into a single text string suitable for use in a URL or similar situation. The format of the resulting text string is:
Special characters such as '=' and '&' inside the parameter names or
values are written in the form: %xx
where xx
are
two hexadecimal digits representing the ASCII value of the character. For
Unicode characters, their UTF-8 encoding will
be processed this way, which may make up multiple %xx
sequences.
In addition, spaces are converted to +.
This parameter format is the same one used by most HTML forms and is known by the MIME type application/x-www-form-urlencoded. It is often used in DM to pack information into topic links.
The original list has items "name1", "name2", and so on. These in turn are associated with the corresponding values "value1", "value2", and so on.
The above example creates a simple parameter list which associates the item "offense" with the value "jwalk" and the item "time" with the value "10:00". This will produce the text string "offense=jwalk&time=10:00".
Object values in the list (like say a mob) get turned into references in the parameter text, just as though you had embedded them with "\ref[Object]". When read back in with params2list(), you could convert these values back into real references by using locate().
Tells the player's client (or multiple players) to load the specified resources now, and how long to keep them. If you do not specify a keep time, 0 is used which will use the default time.
This may be useful for loading sounds into memory before they play, or to load an icon as soon as possible even if it hasn't been displayed yet. This can avoid delays later on when the resources are needed.
Dream Seeker keeps most assets loaded for at least 5 minutes (300 seconds) after their last use. However if you think a more appropriate time is closer to half an hour, you can set a keep time of 1800 seconds. Or if you want them to stay loaded indefinitely, set a keep time of -1.
In cases of extreme memory duress, Dream Seeker's garbage collector will get more aggressive and can still override your choices if need be.
world
.)
Types are matched in the same manner as istype(). In other words, locate(/obj) could return an instance of something derived from /obj, such as /obj/armor.
If there is more than one instance of the specified type, the first one found will be chosen.
This looks for a mob of a type /mob/shopkeeper in the world (world.contents).
This "teleports" the usr to a turf of the type /turf/Home.
This moves the usr to the turf at coordinates (x,y,z) = (1,2,3).
The logarithm is the power to which you have to raise X in order to get Y. In other words, the following is true (ignoring round-off error):
One nice property of this function is that it gradually increases, with a slope that continuously tapers off. In other words, it can be useful to represent diminishing returns from some input, such as money, experience points, and so forth.
Make all of the characters of T lowercase.
If no arguments are provided, a new default (identity) matrix is created.
There are also several "shortcut" matrix calls that can be made:
This example displays 3.
If a single argument is specified, this is expected to be a list and the maximum item from the list is returned. Items to be compared may be numbers, text strings, or null, but numbers and text strings may not be mixed.
This proc implements MD5 hashing. A hash function is a one-way process
that compacts information to a short value: a hash. The same value will
always have the same hash. Among other uses, most computers use hashing
to store passwords. By storing just the hash, the password file contains
very little sensitive information, but the password can still be verified
by confirming that md5(password)==hash
. MD5 is a widely-used
hash function.
In the example, a few vars belonging to a mob were saved along with a hash
of those values. When the mob is loaded again, the game compares the hash
to the values to make sure it's still accurate. If the values or hash had
been changed by a sneaky player, they wouldn't match. (But a sneaky player
could still calculate hash
themselves if they knew the exact
text used to make it, so this should be kept secret.)
If the argument is a file, md5() will read the file and return the MD5 hash of the file's entire contents. If the file doesn't exist, it returns null. The file may be a cache file or an external file.
Note that you must pass the result of file() in order to compute the hash of an external file's contents at runtime. Otherwise md5() will treat the filename as text and return the hash of the name only.
If T is anything but a text string or file, the proc returns null.
This example displays 1.
If a single argument is specified, this is expected to be a list and the minimum item from the list is returned. Items to be compared may be numbers, text strings, or null, but numbers and text strings may not be mixed.
Send a missile of the given Type between two locations. The effect is purely visual. When Type is an object, its icon is used for the missile.
This returns the name of a var or proc, or the last part of a type path. This proc only exists at compile-time.
The main purpose of this proc is to turn a proc reference into a name, which is useful in some esoteric situations.
A new instance of Type is created. The arguments (Args) are passed to its New() proc. A handy short-cut: if Type is not specified and new() is being used in an assignment, the variable type of the left-hand-side will be used as the default type.
The atom types /area, /turf, /obj, and /mob all take a location argument specifying the initial position. If not specified, it defaults to null.
Newly created areas or turfs replace any existing area or turf at the specified location.
list(new
A,new B,new C,...)
.
This causes new mobs to be created with a readme scroll in their inventory.
It is possible to make simple initializations when you want variables to have values other than the default for the particular type you are creating.
This is the most common use of "modified types", but it is not specific to the newlist instruction. Anywhere a type value may be used in DM, it may be followed by a list of initializations. The general syntax for a modified types is:
path {var1 = val1; var2 = val2}
The semicolon is necessary if you put several variable assignments on the same line. The braces are necessary, even though they are generally optional in DM (since the compiler looks at your indentation). The reason is that the path + initializations must be parsed as a single expression, which is a different context from the usual use of braces in DM when you are defining a true type. Also, indentation inside of an argument list is always ignored anyway.
For some games using concepts of procedural generation, it's nice to be able to reliably create pseudo-random numbers in a repeatable, reliable way. This proc takes all the numbers put into it and hashes them together to get a value from 0 to 1. That output value will be the same for any given set of input numbers. This can be used on its own or as part of a more in-depth noise algorithm.
If the first argument is a string, that may be used in future versions to specify the type of hash to use. For now it is not used.
Non-numbers provided to the proc will be interpreted arbitrarily. Don't do that.
This proc is case-sensitive. A common use for this proc is in parsing. For instance nonspantext("apples, oranges",", ") will return 6, because the first 6 characters don't match a comma or a space.
If the start position is negative, the position is counted backwards from the end of the string.
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use nonspantext_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
Get the text string for a number. The number of significant digits determines when scientific notation is used. The default is 6, so scientific notation will only be used when there are more than 6 digits.
The Radix format is intended for converting numbers to bases other than 10, although you can still use 10. In this format, Digits represents the minimum number of digits to use, and the result will be left-padded with zeroes if necessary. Also, in this form only the integer part of the number is used, and it can't be any larger than what a 32-bit integer could store (about 4 billion). These limitations may be lessened or removed in the future, but this format was mainly intended for simple conversions.
The results from obounds() are identical to bounds(), but obounds() leaves Ref out of the results.
This is just like hearers()
, but it excludes the center object
and its contents from the list. It is a list of all other mobs that can hear
the center object.
This instruction is identical to oview() except visibility is ignored. All objects are included in the list whether they are visible or not. The center object and its contents are excluded.
This is used in conjunction with the << output operator to send output to a particular control in the player's skin. If null is sent, the control will be cleared.
As with winset(), the control name may be in the form ":[type]" which sends to the default control of that type, e.g. ":output".
The control ID can be followed by a colon and extra info such as a name or a grid cell, which can send the output to the control in a different way. In a grid, this can output directly to a specific grid cell, like so:
For a browser control, the extra info is a JavaScript function. The format for sending a script to the browser control is output("[params]","[control]:[scriptname]") where "[params]" is a URL-encoded list of string arguments to the javascript function, as formatted by list2params().
And this can't.
"}, ":browser"); #define LP(str) list2params(list(str)) mob/verb/newtext(T as text) usr << output(LP(T), ":browser:replace")This allows for the creation of more dynamic interfaces, since javascript provides access to many client-side operations and flicker-free updates.
This instruction is just like view() except it doesn't include Center or its contents in the list.
This is just like viewers()
, but it excludes the center object
and its contents from the list. It is a list of all other mobs that can see
the center object.
This instruction converts a parameter text string to a list of individual parameters and associated values. The format of the parameter text is:
The field separator ; may be used in place of &.
Special characters such as =, ;, and &
inside the parameter names or values should be written in the form
%xx
, where xx
are two hexadecimal digits
representing the ASCII value of the character. (For
Unicode characters, this may be several
%xx
sequences using UTF-8 encoding.) For example,
= would be written %3d
, ; would be
%3b
, & would be %26
, and %
would be %25
. These "escaped" codes are automatically translated
into the corresponding character when read by params2list().
This parameter format is the same one used by most HTML forms and is
known by the MIME type application/x-www-form-urlencoded. It is
often used in DM to pack information into topic links. Though DM does not
require it, the standard format is for newlines to be written as CR LF pairs
(%0d%0a
) and spaces to be written as + characters. That
means if you want to write a + symbol, you will have to use
%2b
.
The list produced from the parameter text has items "name1", "name2", and so on. To access the values associated with these, you use the parameter name as the list index.
The above example defines a simple parameter text string containing two
parameters: "offense" and "time". These are associated with
the values "jwalk" and "10:00". The for
loop
illustrates how one might loop through the list and print out each setting.
Note that all values are stored as text strings in the list. If you wish to perform a numerical operation (such as addition), you should convert the value to a number first using text2num(). If the value is an object text reference, you can convert that into the object itself by using locate().
If you have multiple items with the same name, they will be combined into a list of text strings. For example, "key=value1;key=value2" would set list["key"] to a list containing "value1" and "value2", not necessarily in that order.
Randomly chooses an item from a list or from the arguments provided. If only one argument is included and it is a list, then the item is chosen from that list.
When not using the list form, you can make a particular value more or less likely to be chosen by providing a relative probability like this:
A value for P of 200 makes it twice as likely as the norm, 50 half as likely, and so on.
There is no analogous weighted format for the list version of this proc.
Many DM procedures make use of a pseudo-random number generator. You can use rand_seed() to initialize the generator. The sequence returned by the generator is identical each time it is initialized with the same seed, so you could use this to reproduce the same output from an algorithm that uses the random number generator. If you never call rand_seed(), the generator is initialized with a seed from the system clock, so it is effectively random.
Note that with multiple realtime algorithms making calls to the generator at unpredictable times, you are likely not to get the same result even when using the same seed. The overall sequence will be the same, but individual sub-components of your world might call it in a different order.
The pseudo-random number generator is system dependent, so do not expect the sequence generated from a particular seed to be identical on two different machines or operating systems.
This instruction is identical to view() except visibility is ignored. All objects are included in the list whether they are visible or not.
A Dist of 0 includes Center, the contents of Center (normally usr.contents), its location (normally the turf a mob is standing on), and any other contents of that location. A value of 1 extends the region to the neighboring squares on the map and so on. You can also use a rectangular box size using a text string such as "13x11". Both arguments are optional and may be passed in any order.
This is a shortcut for "\ref[Object]".
This gets a reference count for a value, not including the reference that was placed on the stack while evaluating this proc.
A return value of 0 can mean one of several things: Either this was the last reference and the object was subsequently deleted after refcount(), or the value doesn't support reference counting.
Note: A nonzero return value does not necessarily mean the object will be deleted when its count reaches zero; mobs for instance will not be soft-deleted by the garbage collector if their key var is set, and some objects like clients and areas never soft-delete. A zero value also does not necessarily mean the object is immortal; it may be transient, like the args list in a proc that only lives as long as that copy of the proc lives.
Creates a regular expression, stored in a /regex datum, that can be used for searching and/or replacing text.
Quotes a piece of text so that it can be used inside a regular expression without fear of being treated as pattern instructions.
When Needle is text, this instruction is NOT sensitive to the case of Haystack or Needle. The case-sensitive version is replacetextEx(). If the Needle is a lowercase word, the replacement will be changed to uppercase or all caps if the found text is uppercase or all caps.
This outputs "Two on two", where the first case's "One" is identified as uppercase.
You may use a proc as the Replacement value. In that case, the proc will be called with the found text as an argument, and its return value will be the replacement. There will be no automatic correction to uppercase or all caps in this case.
When the Needle value is a regular expression, this proc behaves identically to the regex Replace() proc. Case-sensitivity, and whether one match or all are replaced, depend on the regular expression.
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use replacetext_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
When Needle is text, this instruction is sensitive to the case of Haystack and Needle. The case-insensitive version is replacetext().
This outputs "Two on two", where the first case's "One" is identified as uppercase.
You may use a proc as the Replacement value. In that case, the proc will be called with the found text as an argument, and its return value will be the replacement. There will be no automatic correction to uppercase or all caps in this case.
When the Needle value is a regular expression, this proc behaves identically to the regex Replace() proc. Case-sensitivity, and whether one match or all are replaced, depend on the regular expression.
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use replacetextEx_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
Stop execution of the current proc and return the value of E to the caller. If no value is specified, the value of . will be returned, which defaults to null.
A way of representing a color to be used in conjunction with icon arithmetic, atom.color, or other effects. The colors rgb(0,0,0) and rgb(255,255,255) represent black and white, two corners of the "color cube".
This proc returns a text string in the form used by HTML (#RRGGBB). rgb(255,0,128) for example becomes "#ff0080". If you use an alpha component, the format is #RRGGBBAA. You can use strings like this in most procs that use colors such as icon blending operations, and you can also use the short form #RGB or #RGBA. So if you know in advance that you want to use the color white, you can simply use"#fff" instead of rgb(255,255,255).
You can create colors other ways by specifying a different color space. A color space can be specified by using a named "space" argument, or by using a 5-argument format (you can leave the alpha value blank or null to skip it), or by using named arguments for the other components.
Named arguments that are valid in rgb() are:
With the exception of space, only the first letter of the argument name matters, so r and red are the same thing.
Parses a color into a list with 3 or 4 component values; the 4th value is alpha, if it's part of the color provided.
By specifying a different color space, you can convert a color into a different format.
The sides of the dice are numbered 1 through the total number of sides and each is equally likely.
An alternate form takes the dice parameters in a single text value such as "3d4". This may be useful when you want to store the dice information in a single variable. You can even specify an offset, such as "3d4+5". That adds 5 to the sum of 3 dice having 4 sides each.
The first format returns the floor of A (the largest integer less than or equal to A), and has been deprecated in favor of floor(A). The second format rounds A to the nearest multiple of B.
This is similar to link() but instead of a URL, you can pass a file to be viewed directly. The file may be a cache file or an external file.
This example defines a picture to be associated with each mob and a verb for viewing another mob's picture. Players can also configure their own pictures.
proc settings:
verb/set
name
desc
category
hidden
popup_menu
instant
invisibility
src
background
waitfor
Procs and verbs are the same "type" so these attributes may be set for both procs and verbs; most of them only apply to verbs, so they only take effect if the proc is invoked as a verb (by adding it to a verb list).
To avoid lag from procedures that hog the CPU for too long, you can turn on background processing. This will cause it to periodically sleep for long enough to allow other events to be processed.
The following example is a typical "ticker" procedure. It spawns off an infinite loop which does some work and then sleeps before iterating again. By running this in the background, you ensure that the work being done does not create large delays. You could achieve a similar thing by sprinkling calls to sleep(0) or sleep(-1) in the "working" part of the loop.
Since the background procedure sleeps at unpredictable times, you must be aware that race conditions are possible if the background procedure interacts with variables modified by other procedures. It's still much safer than multi-threaded programs because the background procedure never interrupts other code; but other code may interrupt the background procedure.
Note that procedures that are called by the background procedure do not automatically run in the background unless they too have the background setting turned on. For instance, the code in the above example does not imply that the mob Tick() procs would run in the background. This is convenient, because you should only ever apply background processing to code after checking that there are no potential race conditions involved.
If you have an eye for race conditions, you might think that the above code
has one in which a mob gets deleted after it is assigned to M but before the
call to M.Tick() is executed. However, background processing is only
interrupted at loop points in the code, so the above code is safe. It
would only ever be interrupted at the end of the for
or
while
loops.
By default, procs that sleep usually expect their callers to wait for them to finish, so the callers must sleep as well. Using set waitfor=0 will disable that behavior, causing any sleep to return control to the caller immediately.
This setting will also dictate what happens if a callee sleeps. Consider an example where proc A calls proc B which calls proc C, and proc B has waitfor set to 0. When proc C sleeps, proc B will also sleep, but proc A will continue running as if proc B returned early. The . var currently in proc B will be used as its return value to A. When proc C wakes up and finishes, it will wake up proc B, but now B's return value will be ignored since A is no longer waiting for it.
In older versions, the New proc always had waitfor set to 0 by default, but this was later changed. Now 1 is always the default, so setting waitfor to 1 will result in a warning that it is no longer necessary.
This proc implements SHA1 hashing. A hash function is a one-way process
that compacts information to a short value: a hash. The same value will
always have the same hash. Among other uses, most computers use hashing
to store passwords. By storing just the hash, the password file contains
very little sensitive information, but the password can still be verified
by confirming that sha1(password)==hash
. SHA1 is a widely-used
hash function.
In the example, a few vars belonging to a mob were saved along with a hash
of those values. When the mob is loaded again, the game compares the hash
to the values to make sure it's still accurate. If the values or hash had
been changed by a sneaky player, they wouldn't match. (But a sneaky player
could still calculate hash
themselves if they knew the exact
text used to make it, so this should be kept secret.)
If the argument is a file, sha1() will read the file and return the SHA1 hash of the file's entire contents. If the file doesn't exist, it returns null. The file may be a cache file or an external file.
Note that you must pass the result of file() in order to compute the hash of an external file's contents at runtime. Otherwise sha1() will treat the filename as text and return the hash of the name only.
If T is anything but a text string or file, the proc returns null.
This function is used to run an external program. The syntax of Command depends on the server machine's operating system. Be sure to redirect input and output to files if there is any. Also realize that the command will fail if the program you try to run is not in the path where the shell expects to find executable files (unless you specify a full path).
Since shell() allows arbitrary access to the system, each call requires authorization from the person hosting the world, unless running in trusted mode. Authorization is only sought when running in Dream Seeker, since Dream Daemon is intended to be non-interactive. Calling shell() with no arguments is a way of checking if it is allowed by the current safety settings. It will return true if running in Dream Seeker (regardless of safety mode) or if running in Dream Daemon in trusted mode.
The calling proc will sleep until the command is finished executing.
This example displays the output of the "dir" command to the user.
If no address is specified, the current world is shut down.
Pause the current proc (and its callers) for a specified amount of time. If no delay is specified, it will be scheduled to resume as soon as other immediately pending events are processed.
Note that sleeping in some procedures results in the return value being
lost. For example, if you sleep inside Entered()
or
Exited()
, it will be as if you returned immediately where you
started sleeping. This is because Move() calls them in away that says
the return value should be ignored. Also if a proc has its
waitfor setting changed to 0, it
will return the value of the . var to its caller immediately if it or
one of its callees sleeps.
Also be aware that a sleeping procedure whose src object gets
deleted will automatically terminate when execution returns to it. This is
to protect you against trying to access properties or procedures of a
deleted (and therefore null
) object. If you do not want the
procedure to be terminated, you should set src to
null
.
One common use of sleep
is to create what is known as a
ticker. That is an infinite loop that performs some periodic
operation.
Notice how such infinite loops are usually created using
spawn
to prevent the caller from getting locked up. You could
call this procedure from world.New()
to start it rolling.
Note: sleep time is in 1/10s units, not server ticks. If your
world.tick_lag
or world.fps
value is different from
the default, sleep(1) still means "sleep for 1/10s". To sleep for
exactly N ticks, call sleep(N * world.tick_lag)
.
If the ticker does intensive processing during each iteration, you probably want to run it in the background like this:
Calling sleep() with a negative argument (such as sleep(-1)) causes it to do a backlog check. Only if other pending events have become backlogged will it sleep. This is similar to running in the background, but you manually control where the backlog checks are made. The difference between this and sleep(0) is that sleep(0) always sleeps the current procedure for as short a time as possible, whereas sleep(-1) only sleeps the current procedure if other scheduled events have become backlogged. Therefore, sleep(-1) will tend to run the current procedure at a higher priority with fewer interruptions. It is appropriate when there is a single task that needs to be done before anything else can happen, and you just want to make sure that network and user I/O are not terribly lagged in the process.
This instruction is NOT sensitive to case. The case sensitive version is sorttextEx().
This outputs "ascending", since "A" comes before "B" in the alphabet.
This instruction is sensitive to case. The case-insensitive version is sorttext().
Note: Uppercase letters are lower in the alphabetical order than lowercase letters.
This outputs, "descending", since "B" comes before "a" in the alphabet.
Note: This proc used to be named sortText, like sorttext but with a capital T. To avoid confusion it has been renamed, but old code will still compile.
This is used to play a sound file.
The sound file must be a music or sample file. Music files include MIDI (.mid or .midi), and module formats .mod, .it, .s3m, .xm, and .oxm. A sample file used for sound effects can be .wav, .ogg, .raw, .wma, or .aiff.*
The following example plays some sound files. Note that
sound()
is not even necessary when you don't need to set any
additional parameters.
*See Notes under sound support for more information.
This proc is case-sensitive. A common use for this proc is in parsing. spantext("apples, oranges",", ",7) will tell you that, starting at position 7, you need to skip 2 characters to get past any commas or spaces.
If the start position is negative, the position is counted backwards from the end of the string.
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use spantext_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
Run Statement after a delay. Statement may be a single statement or a code block enclosed in (optional) braces and indented. If delay is negative, the spawned code is executed before continuing in the main code. If it is zero, the spawned code is scheduled to happen right after other existing events that are immediately pending.
This will display "Storm clouds are brewing!"
and then call the
storm() proc after 3 seconds.
The important feature of spawn() is that the caller does not have to wait around for the spawned code to finish.
Any vars you have defined in the proc itself, including arguments, will be copied between the spawned code and the code that runs right away. This means that if one part modifies one of those vars, the other part will not see that change. Changes made to objects, lists, datums, etc. however will be visible to both code blocks.
Pointers to any vars that belong to the proc will stay with the original proc, not the copy.
Cuts out a section of a text string and inserts a different piece of text in its place. This is basically equivalent to copytext(Text,1,Start) + Insert + copytext(Text,End), but faster.
The Start and End index values can be negative, which count backwards from the end of the string. If the index values are out of range, there will be no error; they will simply be clamped to the beginning or end of the string. If End comes before Start, the two values are swapped.
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use splicetext_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
Splits up a text string and returns a list. The delimiter is case-sensitive (unless you use a case-insensitive regular expression), and can be more than one character long.
Where multiple delimiters are next to each other, they're considered to be separating an empty string. Therefore splittext("a,,b,c", ",") would return a list with four elements instead of three. Splitting empty text returns an empty list.
If a regular expression is used as the delimiter, any capturing groups in the expression will be included in the list, in order. (The whole match itself will come first, if include_delimiters is true.) So for instance splitting by regex(",") will not include the comma, but splitting by regex("(,)") will. Groups that were not part of the match will be null.
If the start or end position is negative, the position is counted backwards from the end of the string. Please note that the start and end positions do NOT trim the string; if you want to split a trimmed string, trim it with copytext() and send the result to splittext() instead.
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use splittext_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
.dmb
file).
html/CGI.dm
from the html
library.
-ports 1234,1236,1240-1250
.
This is used in a Stat() proc to send a stat line to usr, the person looking at an object. A stat line has an optional name part which must be unique for each stat line (or successive calls will replace previous ones).
The stat line gets appended to the current stat panel. The current panel may be changed by using statpanel().
If no name is specified and the value is a list, this is the same as calling stat on each item in the list. This can be used (in conjunction with statpanel) to create an inventory panel or something similar.
This example displays the mob's description and inventory all in one panel. The code ensures that only the mob may see his own inventory, but you don't have to worry about that unless you change client.statobj to something other than one's own mob.
This is used in a Stat() proc to change the default panel (for subsequent stat lines) or to send one line to the specified panel. Name and Value are both optional. If neither is specified, this simply changes the default panel. Otherwise, the default panel is unchanged and a stat line is appended to Panel.
This example displays the mob's description in one panel and inventory in another. Only the mob may see his own inventory, but you don't have to worry about that unless you change client.statobj to something other than one's own mob.
Move Ref in the direction Dir.
Move Ref on a path away from location Trg, taking obstacles into account. If Ref is farther than Max steps from Trg, no action will be taken.
Move Ref randomly.
Move Ref on a path to the location Trg, taking obstacles into account. If Ref is within Min steps of Trg, no action will be taken. This is also the case if the target is too far away (more than twice world.view steps).
Move Ref in the direction of the location Trg.
The "switch" instruction is a compact notation for a lengthy "else-if" chain. The expression E is compared to the values A1, A2, B1, B2, etc. When a match is found, the following statement (or code block) is executed. An optional "else" statement is run if no match is found. Once a matching switch condition is found, no further conditions will be tested.
The values A1, A2, etc. must be constants. As a convenience, a range of values may be specified in the form: A1 to An.
The switch instruction is MUCH more efficient than a lengthy "else-if" chain, because the expression E is evaluated only once. The conditional values may be any constant expression, such as a number or text string.
This outputs:
Note: Currently the compiler does not throw a warning or error if there is a conflict between two different if blocks in a switch, e.g. when you define if(1 to 10) and if(5 to 20) which overlap from 5 to 10. If two different blocks could handle a given value, the choice of which block takes over is not defined.
Complicated or lengthy embedded expressions in a text string can sometimes make the string difficult to read. In this case, one can use trailing arguments. The position in which the expression should be substituted should be marked with [] and the expression should then be passed as an argument after the text string.
ASCII codes are numerical values corresponding to keyboard and special characters. Among other things, they are used to represent many symbols in HTML. This proc converts a text string to its corresponding ascii representation.
With Unicode, things may get more complicated. DM stores text with UTF-8 encoding, so at this position there might be several bytes strung together to make a single character. The value of pos is in bytes, not characters. When the return value is 128 (0x80) or higher, multiple bytes are used for the charcter. In that case the next character position is not pos + 1 like it is for regular text, but you can use pos + length(ascii2text(result)) instead. Or, you can determine the byte count from this table:
Character code | Size in bytes |
---|---|
0 - 0x7F | 1 |
0x80 - 0x7FF | 2 |
0x800 - 0xFFFF | 3 |
0x10000 - 0x10FFFF | 4 |
Alternatively, you can use test2ascii_char() to work with character positions instead of bytes, at a performance cost.
Appends text to a file. If the file does not exist, one will be created.
This can be useful when interacting with external applications that read output from a text file. For example, you might have an external program that mimics conversation:
If T is a text string for a number, return the number. Any non-numeric text following the initial portion will be ignored. If there is no initial numeric portion, the result is null.
The optional radix, which defaults to 10, can be any integer from 2 to 36.
T is changed from a text string to the equivalent type path, or null if there is no such type.
This keyword throws an exception, which will stop executing the current proc and go to the most recent catch block if one is present. The catch block will receive the thrown value. If there is no try/catch in use, the exception will be passed to world.Error() (if present), then the current proc will end and control will return to the caller.
You can use the EXCEPTION macro to create a new /exception datum, which contains a value or message as well as the file and line number where the exception was created. The thrown value does not have to be an /exception datum; you can throw anything, even null.
A time value (UTC) is converted to text representing the time. The world's local time is used, unless you specify a timezone argument which will add an offset to UTC.
The default format is "DDD MMM DD hh:mm:ss YYYY", which produces results such as "Wed, May 23 15:41:13 2001". As you can see, the fields in the format text are replaced by components of the date and time. The following list contains all of the recognized fields. Anything else in the format string is inserted directly into the output.
Because world.timeofday is in a range of 0 to 864000, values in this range are treated as a time for the current date. This way time2text() can return accurate results for world.timeofday. Any other values are interpreted as coming from world.realtime and will have the right time and date.
Trims whitespace from both ends of a text string.
All Unicode whitespace characters are counted, whether they can cause a break or not.
Returns the integer part of the number A. That is, this rounds toward 0 to an integer.
The try and catch keywords are used for error handling. Any code that runs inside of a try block will, if an error happens or the throw keyword is used, stop executing and jump to the matching catch block. (This is also true of indirect proc calls. If you call a proc from inside a try block, any errors in that proc will be sent to the catch.)
For every try there must be a catch, even if it does nothing. The catch block takes an optional value that can receive the error.
Because the value in the catch keyword is optional, you can simply use the catch keyword alone. It is also not necessary to include any code under the catch keyword, if the error is meant to be ignored. (However, it is not usually a good idea to ignore errors.)
The throw keyword is used if you want to throw an error deliberately. When you use throw, the error thrown does not have to be an /exception datum, but can be anything you like.
This proc can also be applied to an icon or a matrix.
Only multiples of 45 are allowed for angles. If an invalid angle is used, it will be treated as the closest multiple of 45 to 0.
If the supplied Dir is invalid, such as 0, or something like UP or DOWN, the result is a random direction unless the angle is also 0.
An icon that is not square will not be turned.
If the icon is an /icon datum, a new datum will be created as the result.
In this example, fruit_types is initialized to contain /obj/fruit, /obj/fruit/apple, /obj/fruit/peach, and /obj/fruit/mango.
This procedure can also be used to list procs and verbs.
Capitalize all of the characters of T.
Most non-alphanumeric characters are converted to another format in a URL. To send these characters literally, they must be "escaped".
The url_decode()
instruction takes a text string containing
such escaped symbols and turns them into their literal counterparts. Usually
this is done for you automatically in Topic()
. The more useful
function is url_encode()
which does the reverse.
Special characters such as spaces are not used literally in URLs.
If you want to ensure that an entire text string is sent literally, you can
"escape" those characters. For example, a double quote (ASCII 34) is produced
by the code %22
, where 22 is hexadecimal for 34.
The url_encode()
instruction does this for you
automatically. Using format=1
will treat the URL as already
encoded and only re-encode characters that don't belong in the result.
Otherwise PlainText is treated as part of a query string; in this case spaces
are converted to +
instead of %20
, and most
characters are escaped.
Built-in proc vars:
This is the default return value. If a proc exits without calling return or if no arguments are specified the value of . will be returned. The default value of . is null.
This is a list of the arguments passed to the proc or verb.
Here, add() may be called with any number of arguments, each accessed through the args list.
This is not really a variable but is used to force treatment of the following variable or proc as global. This may be necessary if a local or src-level variable has the same name.
This example outputs "local" and then "global".
This is a variable equal to the object containing the proc or verb. It is defined to have the same type as that object.
If a player named "Bob" calls "eat bread", the output will be "Bob eats the bread."
Note that src has no meaning for global procs, derived from /proc, unless they are invoked as verbs (by being attached to a verb list).
This is a mob variable (var/mob/usr) containing the mob of the player who executed the current verb, or whose action ultimately called the current proc.
If a player named "Bob" calls "eat bread", the output will be "Bob eats the bread."
Essentially, usr is an implicit parameter that is passed to every proc or verb. Each procedure inherits the value from its caller. While it can simplify your code in some situations, it can also lead to subtle problems if you are assuming that usr is automatically assigned the value of src when you call a verb programmatically. It is not.
The only time usr is assigned for you is when a player executes a verb, clicks something with the mouse, clicks a link (see Topic), or any other such action.
Note: A good rule of thumb is to never put usr in a proc, only verbs. Typically usr in a proc is an unsafe programming practice. If src would not be the correct choice, it is better to send another argument to your proc with the information it needs.
Certain built-in procs such as atom/Click() are called automatically by a client counterpart like client/Click(); usually atom/Stat() is called by client/Stat(); and so on. It is mostly safe to apply usr as directed in those situations, because these procs are pseudo-verbs. It is mostly not safe to apply usr in a movement proc such as Move() or Enter(), because objs and non-player mobs may move autonomously without setting usr.
Although usr is often set in mob/Login() when a client first connects, you should not assume it is valid if Login() is called any other way. Common cases occur when creating a new character, loading a player's mob from a savefile; or explicitly when setting a mob's key or changing the value of client.mob. It is safest to use src in mob/Login(), which is always correct, rather than usr.
usr is the default point of reference for several procs like view() and range(), because of their common use in verbs. It is also the default recipient for input() and alert() messages. When using these in procs, be aware of that so you can change the default reference value to something more appropriate.
A Dist of 0 includes Center, the contents of Center (normally usr.contents), its location (normally the turf a mob is standing on), and any other contents of that location. A value of 1 extends the region to the neighboring squares on the map and so on. Both arguments are optional and may be passed in any order.
The default range is actually controlled by the size of the map viewport
size, which is configured with world.view
. Since the default
value of that variable is 5, the default range is also 5. You may use any
valid view size, so an explicit view size such as "11x17" is also valid.
Be aware of the following distinctions:
In many cases, the three different statements could produce the same result, but they are not identical in general. For example, the first statement takes into account the visual capabilities of usr, which might include such things as the ability to see in the dark or to see invisible objects.
The second statement, since it is from a non-mob would just do a plain visibility calculation with no special visual capabilities. In many cases, you would want to use viewers() or hearers() instead.
The third statement produces a list of visible objects as the player sees
things, which might be different than how the mob sees things if
client.eye
and client.mob
are different.
This is just like view()
, but it is a list of mobs that can
see the center object, rather than a list of objects visible to the center
object.
Move Ref in the direction Dir continuously. Each step will be preceded by Lag time of inactivity.
A call to a walking function aborts any previous walking function called on Ref. To halt walking, call walk(Ref,0).
This function returns immediately, but continues to process in the background.
Moves Ref on a path away from Trg continuously, taking obstacles into account. Each step will be preceded by Lag time of inactivity. If Ref is farther than Max steps from Trg, no action will be taken.
A call to a walking function aborts any previous walking function called on Ref. To halt walking, call walk(Ref,0).
This function returns immediately, but continues to process in the background.
Moves Ref randomly. Each step will be preceded by Lag time of inactivity.
A call to a walking function aborts any previous walking function called on Ref. To halt walking, call walk(Ref,0).
This function returns immediately, but continues to process in the background.
Move Ref on a path to Trg continuously, taking obstacles into account. Each step will be preceded by Lag time of inactivity. If Ref is within Min steps of Trg, no action will be taken. This is also true if the target is too far away (more than twice world.view steps).
A call to a walking function aborts any previous walking function called on Ref. To halt walking, call walk(Ref,0).
This function returns immediately, but continues to process in the background.
Move Ref in the direction of Trg continuously. Each step will be preceded by Lag time of inactivity.
A call to a walking function aborts any previous walking function called on Ref. To halt walking, call walk(Ref,0).
This function returns immediately, but continues to process in the background.
If E is true (non-zero) execute Statement. Continue testing E and doing the while block until E becomes false (zero).
Statement may be a block of code or a single statement.
This outputs:
Creates a clone of a window, pane, menu, or macro set that exists in the world's skin file. The original object as it exists in the skin file (not its current state) is used as a template to build the clone. The clone will exist only for the player you choose.
If a window is not visible by default, it will have to be shown with winset() or winshow(). A pane may be shown by using it in a Child or Tab control. Menus or macros must be assigned to a window with winset() before they will work.
If window_name is "window", "pane", "menu", or "macro", and the skin file does not have a control of that name already, the proc will create a new control of that type from scratch.
A new window is invisible by default. For windows and panes, you should give them a size with winset() before adding any controls so you can set their anchors properly.
Once a clone is created, it can be deleted via a winset() call:
Tells if a control exists and if so, what type it is. The return value is an empty string if the control does not exist, but otherwise it is the type of control.
This proc will not tell you if a control has been defined in the skin file but is not in use yet.
Because the client must be contacted to get this information, winexists() will sleep the current proc.
Retrieves info from a player about the current state of their skin. If control_id and params are each just a single value, then the return value will be a simple string with the value of that parameter. If control_id or params is a semicolon-separated list like the kind used in list2params(), then the result will be in a similar format, and can be converted to a list form using params2list().
The control_id can be a window name, or in a "[window].[control]" format, or just the control's ID itself as long as it is unique. Another valid form is ":[type]" which selects the default control of that type, e.g. ":map" for the main map. As mentioned above, you can retrieve info on more than one control at once by separating them with semicolons, like "button1;button2".
This outputs:
If the returned result is actual text for any parameters, the single quote or double quote characters may be escaped with a backslash. An actual backslash will also be escaped with a backslash.
You can also use a special wildcard format to retrieve info about all the controls in a window, menu, or macro set. If control_id is "mainwindow.*" for instance, then any control that is part of mainwindow—and mainwindow itself—is included in the result if it has the parameter(s) you're looking for. Use params2list() to interpret the result.
Note: Because the client must be contacted to get this information, winget() will sleep the current proc.
Calling winget() with a blank or null control_id can return some values that belong to the client as a whole, not to specific controls. They can also be used for embedded wingets.
Sets parameters for a player's skin. The parameter list can be created by making a list and using list2params(), or it can be done manually by just using a string like "is-visible=true;text-color=#f00". You can also just use a list directly, which will be passed to list2params() internally.
The control_id can be a window name, or in a "[window].[control]" format, or just the control ID as long as it is unique. In some special cases it can also be null. Another valid form is ":[type]" which selects the default control of that type, e.g. ":map" for the main map.
If you want to use a text string that may include spaces, surround the string with double quotes and escape them using a backslash, e.g. "text=\"This is some text.\"". Backslashes can also be used by preceding them with another backslash. For filenames, use single quotes around the file. Sometimes escapement may need to go several levels deep; for example to set up an input control with a default say command, you will need to escape it twice:
Desired command: say "
Escaped form with quotes: "say \""
Final form: \"say \\\"\"winset(usr, "mainwindow.input", "command=\"say \\\"\"")
You can set more than one control's parameters at once by leaving the control_id argument null, and including the control as part of the parameter list:
Some "global" winset options will let you change things that affect the client as a whole, not just specific controls.
You can reset the skin to its beginning state, removing all runtime-created controls and windows, by calling winset(player, null, "reset=true").
Another use for winset() is to send commands to the client that normally can only run from there, like .profile or .quit. To do this, leave the control_id argument null, and just use a parameter called "command":
Shows or hides a window in the player's skin. This is a shortcut, equivalent to setting is-visible via winset().
The /regex datum holds a regular expression that can be used for searching and/or replacing text. Rather than searching for a specific piece of text, a regular expression is a pattern to search for. This can include things like wildcards. See Regular expressions for more information.
A new regular expression can be created with regex() or new/regex().
Finds the regular expression pattern within the "haystack" text. The following vars are set by the match:
In a global expression (using the "g" flag), Find() can be called repeatedly on the same piece of text and the Start position will be advanced automatically unless you specify it.
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use Find_char() to work with character counts instead of bytes, at a performance cost. See the Unicode section for more information.
Calling new/regex() is the same as calling regex(). It will create a new /regex datum.
Finds the regular expression pattern within the "haystack" text, and replaces the match with the given replacement value.
In a non-global expression (not using the "g" flag), the values of src.index and src.next are set as they would be in a global Find(). See the Find() proc for more info.
Note: In strings containing non-ASCII characters, byte position and character position are not the same thing. Use Replace_char() to work with character counts instead of bytes. See the Unicode section for more information.
If the replacement value is text, the $ character is treated as special. If you want to use the actual dollar sign, it must be escaped with a second dollar sign. Otherwise, the $ character is one of these special values:
Replacement | Value |
---|---|
$1 through $9 | $1tt> is whatever was in the first parentheses group, $2 is the second, and so on. |
$` | The text that came before the match |
$' | The text that came after the match |
$0 or $& | The whole match itself |
If replacing matches with a proc, then the proc will be called with the match as its first argument, and any capturing groups as the following arguments. Whatever the proc returns will be used as the replacement text in place of the match.
The flags that were used to create this regular expression. Changing this value after the datum is created will not change how the expression behaves.
After a call to Find(), if this regular expression had any parentheses groups, whatever text was matched in those groups is stored here in a list.
After a call to Find(), this var contains the index where the match was found.
Replace() on a non-global pattern will also store the index of the match here.
After a call to Find(), this var contains the text that was matched.
The pattern that was used to create this regular expression. Changing this value after the datum is created will not change how the expression behaves.
If this is a global pattern (using the "g" flag), then after a call to Find() this var contains the index where next Find() should begin.
Replace() on a non-global pattern will also store the index of the next place to begin a search here. The position to search will be based on the replaced text, which is stored in the text var.
If this is a global pattern (using the "g" flag), then after a call to Find() this var contains the text that was searched. If that same text is searched again, the next var is the default starting position.
Replace() on a non-global pattern will store the text after replacement here.
A database file in DM is called a "savefile". All of the contents of a savefile reside in a single file. The contents of the file are stored in database directories. These should not be confused with real directories in the external file system. The database directories are all contained inside the one file.
Each database directory contains a list of sub-directories and a buffer in
which data may be written. The absolute path to a directory has the following
format: "/Dir1/Dir2/...". The current directory may be set by assigning its
absolute path name to savefile.cd
. A relative path (one that
doesn't begin with "/") may also be used, in which case the new path starts at
the current directory. The path "." stands for the current directory, ".."
for its parent, "../.." for its parent's parent, etc.
A savefile may be created with new/savefile(name)
. The
optional name argument may be an external file name (existing or to be
created) in double quotes or a file from the resource cache in single quotes.
Of course, a variable containing either of these types of values may also be
used. If no name is specified, a temporary file will be created, which will
be destroyed when the savefile is no longer in use. If a resource cache is
specified, a temporary file will be created and the contents of the cached
file will be copied into it. Changes will therefore only be temporary.
Built-in savefile operators:
Reads a value from a buffer into a variable. If Path is not specified, the current buffer will be used. Otherwise, the buffer at the specified path will be accessed. Whenever the current directory is set, reading starts at the beginning of that buffer (replacing any previous contents). For this reason, when the Path parameter is given, the first value in the specified buffer is always read. If there is no data in the buffer or the end of the buffer has been reached, null is returned.
If the value read is a previously written object, its own directory will be opened and the object's Read proc will be called to load any data that was written in the object's Write proc.
If the value read is a savefile (ie a savefile inside of a savefile), it is treated a little differently. Instead of returning a savefile object, it returns data cached in the world's rsc file. This is to give you control over what file this data is copied into before it is opened as a savefile. If you want to just open it up in a temporary file, do something like this:
Writes Val to a buffer. If Path is not specified, the current buffer will be used. Otherwise, the buffer at the specified path will be written to. Whenever the current directory is set, writing starts at the beginning of that buffer (replacing any previous contents). For this reason, when the Path parameter is given, the specified buffer is always overwritten.
If Val is an object, a separate directory will be created for the object and the object's Write proc will be called. In addition to data that may be written by the Write() proc, the type of the object is stored in a buffer called "type". In the case of turfs, the location of the turf is also recorded so that it can be recreated at the same position. All other objects must be repositioned after the object is recreated (like in the object's Read() proc).
Single operations that write multiple values (such as saving an object) are handled somewhat specially to avoid two references to the same object creating duplicate entries in the savefile. After the object being referenced is written once, successive references to the same object will be saved simply as references rather than as full objects. If this was not done, two references to the same object would be read back in as two separate objects. This also avoids infinite loops that would result when objects contain references back to themselves.
Converts all or part of a savefile to a human readable text format, similar in syntax to DM. If no file is specified, the savefile text is returned as a text string instead of being written to a file.
The following example shows how to export and later import a savefile. The user's mob is written into a directory with the same name as their ckey and the result is written to a text file.
[html_encode(file2text(txtfile))]" mob/verb/read() var/savefile/F = new() var/txtfile = file("players/[ckey].txt") F.ImportText("/",txtfile) F[ckey] >> usr
Flushes any pending write operations for this savefile.
Reads a text file or string and writes it into a savefile. See ExportText for an example.
If source is an ordinary string, it will be treated as savefile contents to be parsed. If it's a file() reference, it will be treated as a filename and the file's contents will be loaded.
In order to modify a savefile, exclusive access to the file must be guaranteed, so that other processes reading or writing to the file do not experience data corruption. This is known as "locking" the file. While the file is locked, only the world that obtained the lock may access it.
Normally, you do not need to worry about this, because a lock is automatically obtained upon the first attempt to write to the file. In a CGI application, where many instances of the program might be running simultaneously, the normal locking, which just tries once and crashes the proc on failure, would not be ideal.
Explicitly calling Lock() allows you to specify a timeout and it also allows you to handle the case in which no lock could be obtained. If you want it to wait indefinitely, use -1. Just be careful if there are several files read by multiple processes that it is not possible for deadlock to occur.
Obtaining a lock will fail if the file is locked by another world or if it is even open by any other world.
If you are using Lock(), then you probably also want to specify a timeout when you open the savefile, since that too can fail due to the file being locked by another process.
You call this via new/savefile(filename,timeout). The timeout is used to determine how long to wait if the file is locked. Normally (timeout=0), if the file is locked, the proc crashes with a runtime error. If you specify a timeout, then it will keep trying to open the file and if this fails, it will simply return with savefile.name being empty (ie a false value).
If the first argument is an entry in the world's rsc cache, the data will be copied into a temporary file and accessed from there. Changes to this, and any other temporary file, are not saved. When you close the file, it simply gets deleted.
Exclusive locks are automatically released when the savefile is closed, but if you want to keep reading the file and allow other processes to do the same, then you can explicitly unlock it.
Note that this does not allow other processes to lock the file. It only allows them to read from it. As long as the file is open by more than one process, no lock may be obtained.
savefile vars:
Sometimes when transitioning between BYOND versions you'll want to keep a savefile in a condition where it can be read by older versions. The byond_version var tells it to avoid using newer savefile features (unless forced to) above a certain build number. (The build number changes more often than the BYOND version number.) A value of 0 means not to worry about it.
This can be changed on a per-savefile basis so that you can use newer features in some files and avoid changing others. For instance, character saves might want to go on using older features for a while, but for saving parts of the map you might prefer to allow newer features to be used.
The default savefile compatibility version can be set at compile-time:
Sometimes when transitioning between BYOND versions you'll want to keep a savefile in a condition where it can be read by older versions. The byond_version var tells it to avoid using newer savefile features (unless forced to) above a certain version number. A value of 0 means not to worry about it.
This can be changed on a per-savefile basis so that you can use newer features in some files and avoid changing others. For instance, character saves might want to go on using older features for a while, but for saving parts of the map you might prefer to allow newer features to be used.
The default savefile compatibility version can be set at compile-time:
Note: By default, worlds compiled before BYOND 515 will set this value to 514.
This is the path name of the current directory. Setting it causes the current directory to change. (We are talking about the current database directory inside the savefile. It is not a real directory on the filesystem.) It is perfectly legal to change to a non-existent directory. This new directory will not be saved to disk unless its buffer (or one of its children) is modified, however.
The name of each child directory of the current data directory is stored in the list savefile.dir. New directories may be created with savefile.dir.Add() and removed with savefile.dir.Remove(). To test for the existence of a directory, use savefile.dir.Find(). (Note that these are database directories inside the savefile—not real directories on the filesystem.)
The order of directories is not necessarily preserved, so do not assume, for example, that newer directories will be at the end of the list.
If there is more data to read in the current buffer, eof is 0; otherwise it is 1. Setting eof to 1 moves to the end of the buffer and 0 moves to the beginning. Setting it to -1 deletes the current buffer.
The external "real" filename is stored in file.name. It is initialized when creating a new file. If none is specified, a temporary file with a random name will be created.
A /sound datum is created by the sound() proc or by new/sound(). It can be used to change the way a sound file will play. When you're ready to play the sound, just send it to a player like so:
The sound file can be supplied as a list of choices, in which case the client will play the first compatible sound in the list.
Built-in sound vars:
This is the file that will be played when the sound is sent to a player.
If this value is a list of files rather than a single file, the client will play the first compatible sound in the list. This can be useful for developing webclient games, where .ogg is preferred by most browsers for audio, but .mp3 is needed for others.
This var may be filled in by the SoundQuery proc.
Set to 1 to repeat the sound indefinitely once it begins playing, 2 to repeat it forwards and backwards.
This var may be filled in by the SoundQuery proc.
Set to 1 to wait for other sounds in this channel to finish before playing this one.
The SoundQuery proc fills in this value with the total duration of sounds that are queued to be played on this channel.
For sound effects, set to 1 through 1024 to choose a specific sound channel. For values of 0 or less, any available channel will be chosen. If you play a null sound (no file) on channel 0, the settings for the datum will affect all channels.
If you want to make any changes to your sound later on via the SOUND_UPDATE status flag, you must specify a channel for it.
This var may be filled in by the SoundQuery proc, but only for sounds that had a specified channel to begin with.
Note: If you don't specify a channel to play a sound, the client will choose a channel automatically but it will not conflict with any specific channels you choose for other sounds later. This means if you play some sounds with channel 0 but then later want to play something on channel 1, you don't have to worry about channel 1 being taken.
Set to a percentage from 0 to 100 of the sound's full volume. It will play at that volume when it is sent to a player.
Any value from -100 to 100 will play this sound at a multiple of its normal frequency. Set to 2 to play at double speed, for example, or -1 to play backwards. A value of 0 or 1 will play the sound at its normal frequency.
Set to a specific frequency (in Hz) outside of the -100 to 100 range to change the playback rate of the sound. A negative value is also allowed. The value 0 means that the sound should be played as-is. This value will take effect when the sound is sent to a player.
CD-quality sound is sampled at 44.1 KHz, which is a value of 44100. Other common sample rates for .wav files are 8000, 11025, and 22050. (11025 is usually a good quality for most sound effects without making file size too large.) If you know the file's sample rate, doubling the value will play it at a higher pitch and twice as fast; halving it will play it at a lower pitch and twice as slow.
To make a sound play at a different speed but keep its pitch intact, set frequency to the speed multiple you want (e.g., 1.2 for 20% faster) and set pitch to the inverse (e.g., 1/1.2).
Can be used to set a starting time, in seconds, for a sound. It can also alter the current time index when using the SOUND_UPDATE flag.
This value is also set by the SoundQuery proc and represents the current time index of a sound playing on the client.
This value is set by the SoundQuery proc and represents the length, in seconds, of a sound playing on the client. Frequency is taken into into consideration, so a higher frequency means the sound takes a shorter time to play and therefore has a shorter length.
Other uses for this var may be considered in the future.
Alter the panning of a sound. -100 is full left, 100 is full right, and 0 is at the center. This value will take effect when the sound is sent to a player.
For music, pan can range from -100 (no pan separation) to 0 (full separation).
Used to set client skin information related to this sound. This can be set to an associative list or a parameter string such as you would get from list2params().
The only parameter currently defined is on-end, which is a command the client will run when the sound ends.
Can be used to shift the pitch of a sound up or down. This works similarly to frequency except that it doesn't impact playback speed.
The value of this var should be a multiple relative to 1, so for instance to go up a full octave, the value would be 2; to go down an octave, use 0.5.
Note: The filter that handles pitch shifting only goes from 0.5 to 2. The player will stack up to three filters if it has to, so the range is really from 0.125 to 8. You will however hear much poorer quality at more extreme values.
To make a sound play at a different speed but keep its pitch intact, set frequency to the speed multiple you want (e.g., 1.2 for 20% faster) and set pitch to the inverse (e.g., 1/1.2).
Sounds with higher priority may steal channels from sounds of lower priority. The maximum value is 255. 0 is the lowest.
Alter the way the sound is heard by affecting several different on/off values which combine as bit flags.
Use the | operator to combine these values. The setting you choose will take effect when the sound is sent to a player.
SOUND_MUTE | SOUND_UPDATE will mute a sound, but it will continue to play. It can be unmuted while still playing by resending it with status=SOUND_UPDATE.
SOUND_PAUSED | SOUND_UPDATE will pause a sound. It can be restarted from its current position by resending it with status=SOUND_UPDATE.
SOUND_STREAM will create this sound as a stream. Streams take less memory, but can not play multiple times at once, nor can they play in reverse. This flag is only valid the first time that a sound is played in a particular mode (normal vs. 3D). Changing the flag later will not change the stream status of the sound in that mode.
SOUND_UPDATE updates the settings for the sound currently playing on the specified channel. If this flag is not set or no channel is specified, the sound will start again from the beginning.
Some of the flags for this value may be filled in by the SoundQuery proc.
Set to different values to give your sound a 3D effect which will be applied when it is played. Positive values for x will sound as if they come from the right, positive y sounds like it is above the player's head, and positive z sounds like it is directly ahead. The effect of 3D sound depends on the player's computer's sound card, and is greatly enhanced when wearing headphones.
Depending on the value of falloff, the settings for the location of the sound can also affect its volume. Once the distance passes the value of falloff, the volume will diminish.
If these values are all set to 0, you should set environment if you want to treat it as a 3D sound. Otherwise BYOND will assume this is meant to be a non-3D sound such as music or the interface.
Within the falloff distance a 3D sound stays at the constant loudest volume possible. Outside of this distance it attenuates at a rate determined by the falloff.
Higher values will make sounds fade out more slowly as they get farther away. The distance of a sound is set by x, y, and z.
Changes the environmental reverb for all 3D sounds until another environment is specified. Only 3D sounds react to the environment. Please see the EAX2 documentation at http://developer.creative.com/ for detailed information about these settings.
This value may be a number which selects a preset, or a list to choose settings manually. The default value (-1) specifies no change in environment. A numeric value from 0 to 25 specifies a set of reverb presets for the environment. The environment presets are:
As of BYOND 515, setting environment to a negative number below -1 will turn the environment off. The generic environment is not the same as off.
A 23-element list represents a custom environment with the following reverbration settings. A null or non-numeric value for any setting will select its default.
If set to an 18-element list, this value customizes reverbration settings for this sound only. A null or non-numeric value for any setting will select its default. Please see the EAX2 documentation at http://developer.creative.com/ for indepth information about these settings.
Here's a rundown of some of the terms: Direct refers to the amount of effect when the sound is on a direct path to the listener. Obstruction is when an object is blocking the direct path but there are other ways for the sound to reach the listener. Occlusion means the sound is on the other side of a wall, and mostly blocked. Exclusion means the sound is on the other side of a wall, but a doorway or window is allowing it to pass through to the listener. You can use a little of each of these effects to increase realism.
Turfs cover the surface of the map. They are derived from /turf which derives from /atom.
This example defines the turf prototype /turf/floor and /turf/wall.
Turfs cannot be moved. They can only be created or destroyed by changing world.maxx, world.maxy, or world.maxz. When you create a new turf with new(), it always replaces the old one.
Built-in turf procs:
Built-in turf vars:
The default parent_type of /turf is /atom.
Variables are derived from var.
Value defaults to null.
User types may be derived from anything except for /world, /list, /client, and /savefile.
The const type modifier defines a constant value. This may be useful for centralizing the location of a value that is used repeatedly so that it can be easily configured. It has the advantage over #define macros of obeying the normal scope rules for variables. This means, for example, that a const variable declared inside a proc will not conflict with other variables declared elsewhere.
This example defines an upper limit on the number of items a mob may carry.
The final type modifier indicates that a variable should not be overridden by a subtype or another compile-time assignment statement.
This modifier also applies to procs. A final proc may not be overridden.
The global type modifier makes a var permanent and shared. This means only one copy of the var is maintained. Otherwise, separate copies of the var are maintained for each instance containing the var (be it a proc, mob, obj, etc.)
This example increases the count each time the proc is called. If count were not declared global, the displayed count would be 1 every time.
The tmp type modifier indicates that an object variable should not be automatically written to the save file. This could mean that the variable is transient—that is, it is calculated at run-time and need not be saved. It could also indicated that the designer will handle saving of that variable specially and wishes to bypass the automated routine.
It is especially important to use tmp when you have references to
external objects that should not be saved along with the object. For
example, suppose players have a leader variable which indicates
who or what they are following. You would not necessarily want the leader
to be saved in the player's savefile. Therefore, you would need to use
tmp
when defining the variable.
Verbs may be attached to mobs, objs, turfs, and areas. Players can then use them as commands if they have access to the source.
Verbs are fundamentally the same "type" as procs, so their vars are the same.
Whenever a player in the world types the command "poof", this verb will be invoked.
In addition to the normal access control (see the verb src setting) verbs can be dynamically added and removed from objects. One way to do this is to use new() with the following syntax:
The Destination specifies the object to receive the verb. Name and Desc optionally specify a new name and description for the verb.
This example defines two verbs (accessible to mobs of type /mob/DM). One verb kills other mobs. The other adds the kill verb to another mob (giving the second mob the ability to kill).
In some situations, the ability to dynamically change an object's verb list is quite useful, but most of the time it is far more convenient to do the same thing by manipulating objects rather than verbs directly. For example, the previous example can be handled by having an object with the kill verb attached it it. Then players have greater versatility in manipulating the verb by simply moving the object around.
The use of an implicit verb source in this example gives the user access to the kill verb without having to specify the source scroll as long as the scroll exists in the user's inventory. In other words, the player types "kill rat" rather than "kill kill rat".
The parameters to a verb are referred to as arguments. For verbs, the input type and possible value list may also be specified.
The possible input types are:
text // a quoted text string password // un-echoed text (for use with input() only) message // multi-line text command_text // raw command text from the rest of the input line num // a number icon // an icon file from the user's computer sound // a sound file from the user's computer file // any type of file from the user's computer key // a key from the user's BYOND key file color // a color (see rgb proc) null // indicates that the argument is optional mob obj turf area anything
These can be combined with the '|' operator. The first group are called constant input types because they turn on various types of literal values that the user can type in (like a number or a text string). The second group work in conjunction with a list of objects or values. They are called input type filters because they may be used to filter out certain types of values from the list. For example a mob or an obj within sight would be specified as follows:
A default value may be specified which takes effect in the case of null arguments. For example:
In this example, the input type null
did not have to be used
explicitly, because assigning a default value (in this case
usr
) turns it on by default.
The anything
input type can be used to combine values in a
list with other constant input types. Here, this is done with the
null
input type:
For input types containing mob, obj, turf, or area, the possible value list defaults to view().
If no input type is specified, the variable type will be used to determine whether it is a mob, obj, turf, or area.
This example defines a verb with two arguments: a target mob, and some text.
The expression used to to provide a list of possible values for a verb argument may reference the value of arguments prior to the one being expanded. It may even reference the value of the argument being expanded, but this will always be a text string equal to what the user has typed so far.
In addition, there is a special variable called "expanding" which is only accessible in this context. It is 1 if the user's input is being expanded and 0 if the user's final input is being validated. In certain rare cases, you may wish to tell the difference between these two cases. For example, you could use this to have possible values which do not show up in the expansion lists, but which are accepted when typed in full.
verb settings:
verb/set
name
desc
category
hidden
popup_menu
instant
invisibility
src
background
Procs and verbs are the same "type" so these attributes may be set for procs as well; most of them do not have any meaning, however, unless the proc is invoked as a verb (by adding it to a verb list).
Verbs in the same category are visually grouped together in the verb
panels. The default is "", which is displayed in the default panel titled
"Commands". You can change that default by setting
client/default_verb_category
.
To hide a verb from all panels, set the category to null. The verb may still show up in right-click popup menus, so you may want to use the hidden or popup_menu verb properties instead.
The desc attribute sets the descriptive help string for the verb. The player may access this by hitting the 'F1' key after entering the command. This will normally produce a list of each argument by type followed by the desc text. If you wish to override the syntax description, put your modified version inside parentheses at the beginning of the desc text.
This will produce the help text:
If the syntax description had not been supplied, it would have produced:
A hidden verb is not visible to players (in menus or in expansion lists) but if typed in full can still be accessed.
An alternate way to hide a verb from the command-line and verb panels is to make "." the first character in the name. The verb will not show up in command-expansion (ie when hitting spacebar) until the "." has been typed. This could be useful for hiding verbs that would otherwise clutter up the verb list, while still making them relatively easy to use. If you think this is a random quirky feature, you are right. To put "." in front of the name, use the name setting.
Normally a player can only call one verb per tick, but they can call any number of "instant" verbs in the same tick. This setting is useful for commands called by the game's interface, or for more responsive controls like for instance the use of "combos" in fighting games.
Verbs with the instant setting can be used on the same tick as a regular verb, but only one regular verb can be used each tick. Instant commands will jump ahead of non-instant commands in the queue.
Any verbs that are already built-in, such as movement commands, cannot be modified to use this setting. (Some, such as mouse commands, already use it.) You can, however, create replacement verbs of your own for most of them.
An invisible verb is only accessible to players who can see invisible objects. This is different from a hidden verb which does not clutter up the verb list but which is still accessible when typed in full.
The name attribute of a verb defaults to the node name. Setting the name attribute explicitly may be necessary if the name includes characters not allowed in node names.
Use this to prevent a verb from showing up in the popup "context" menu when users right-click on objects.
With the first format, if src is in List for a particular player, then that player will have access to the proc. The player must explicitly specify the name of the source on the command line.
The second format behaves the same, except the source is not read from the command line. If more than one possible source exists, one will be chosen at random.
When usr or world is specified for the first format, it will be expanded to usr.contents and world.contents respectively.
The default setting depends on the type of src:
The function of this variable has been replaced by
invisibility
, which provides a full range of settings.
The world node is used to define some global properties for the world. Like the other types, the world has overridable vars and procs. New vars and procs cannot be defined under world though; to make global vars and procs, use /var and /proc instead.
Built-in world procs:
Adds credits to a player's account. The proc will return 1 if it is successful, or 0 if the attempt failed and should not be tried again. This feature is intended for games that make use of the credit system, and for security all such games must use a hub password.
This proc will return null if there was no way to reach the hub. Use isnull() to check for a null value. Contacting the hub may take a few moments, so it is a good idea to use spawn() to avoid holding up the rest of the game.
Note: You can specify a different hub path and hub_password by adding these as extra arguments, but this is not recommended for security reasons. If you use this feature, it should only be on games that cannot be downloaded by the public.
Removes a medal from a player. The proc will return 1 if it is successful, or 0 if the medal was not already awarded. If the world already knows this medal was not earned, the hub will not be contacted.
This proc will return null if there was no way to reach the hub. Use isnull() to check for a null value. Contacting the hub may take a few moments, so it is a good idea to use spawn() to avoid holding up the rest of the game.
Note: You can specify a different hub path and hub_password by adding these as extra arguments, but this is not recommended for security reasons. If you use this feature, it should only be on games that cannot be downloaded by the public.
When the world is destroyed, only the Del() proc of the world
object is called automatically. If you want to delete any other objects, you
must do so from within world/Del()
. Once this procedure returns,
any other procedures which may still be executing are immediately aborted and
all objects are silently destroyed.
To prevent accidental hangs during world/Del()
from preventing
shutdown, a timeout is applied to any sleeping operations such as
sleep
, world.Export()
, and so on. If the total time
slept exceeds the timeout, world/Del()
is aborted. Currently,
this timeout is set at 30 seconds.
Called when a runtime error happens, or the throw keyword is used, without a try/catch to handle it. The return value is ignored.
This example defines a verb that will broadcast a message to everyone in this world as well as sending it in the form of topic text to another world whose address is stored in the variable ShadowWorld. This address could be manually set or could be the result of calling startup().
It is also possible to access an HTTP server via world.Export(). Simply use an http address such as: http://www.byond.com. This returns a list of HTTP header parameters as well as the extra values "STATUS" and "CONTENT". The value associated with the "STATUS" entry is the HTTP status code returned by the web server (as text). The value associated with the "CONTENT" entry is the requested resource.
Note that the HTTP request is submitted using the GET method as opposed to the POST method. Support for POST may be added in the future.
If the Clients argument is used, it accepts a client that is currently loggedin, a mob belonging to such a client, or a list of any of these. The remote server will receive a list of their keys in world.Topic().
This command is for retrieving configuration information that is shared by applications installed on the same system. The configuration data is accessed by specifying the configuration "set" and the parameter within that set. The "sets" defined so far are:
If no parameter is specified, a list of the names of all available parameters is returned.
The format of the configuration data itself is currently being defined. It will generally be a sequence of parameters (such as produced by list2params()). For example, each ban entry would have the user's ckey or ckeyEx as the parameter, and might have data such as "reason=jerkish;message=You+jerk!".
Ban files store information on a game-specific basis. You will only be able to read and write values that are set for the game you are running (defined by the value of world.hub). It is possible for a host to specify universal bans as well, but these will not be accessible via GetConfig or SetConfig. If you are using "ban" as the config_set, IP addresses are recognized automatically. (See the ban format info below.)
It is possible, but rarely useful, to specify a configuration "space" of SYSTEM, USER, HOME, or APP. Settings made in the SYSTEM space are shared by all BYOND software on the computer. The USER space is shared by all software owned by the same user. The HOME space is shared by all worlds running with the same safe home directory. The APP space is shared by all software running from the same filesystem directory. By default, the USER space is used, and if that cannot be modified (in safe mode), then HOME is used instead. These distinctions are sometimes important on a UNIX machine, where there are many BYOND sites belonging to different users, but even then, the default behavior is almost always what you want.
The configuration space is specified inside the configuration set parameter like this:
When reading configuration settings, the spaces are always lumped together. In cases of settings with the same name but different values, APP overrides HOME, which overrides USER, which overrides SYSTEM.
If you want to create or read bans at runtime by using the "ban" config set, these are the main parameters currently used:
The old "keyban" and "ipban" config files are now just aliases for "ban".
Retrieves the number of available credits in a player's account. This feature is intended for games that make use of the credit system, and for security all such games must use a hub password.
This proc will return null if there was no way to reach the hub. Use isnull() to check for a null value. Contacting the hub may take a few moments, so it is a good idea to use spawn() to avoid holding up the rest of the game.
The best time to call this proc is before a player does something that would allow them to spend credits, and/or just afterward, so they can see what is left in their account.
Note: You can specify a different hub path and hub_password by adding these as extra arguments, but this is not recommended for security reasons. If you use this feature, it should only be on games that cannot be downloaded by the public.
Checks to see if a medal has been awarded to the player in question. If the medal has been awarded, the return value is 1. If not, 0.
You can also use GetMedal() to read a list of all medals a player has earned for the hub entry, by leaving the medal argument blank. If you also leave the player argument blank, you will get a list of all medals available to the hub entry. In both cases the result can be parsed with params2list().
This proc will return null if there was no way to reach the hub or otherwise verify the medal's status. Use isnull() to check for a null value.
Whenever possible, GetMedal() will avoid contacting the hub by using the information it was given when the user logged in. If contacting the hub is required, the proc may take a few moments to return a result. It is a good idea to use spawn() to avoid holding up the rest of the game.
You can add an optional hub path argument if you want to look at a medal for a different hub entry.
Retrieves information about scores that is kept on the BYOND hub.
This proc will return null if there was no way to reach the hub. Use isnull() to check for a null value. Contacting the hub may take a few moments, so it is a good idea to use spawn() to avoid holding up the rest of the game.
In this form, you can get information about individual scores. This is the most common way to use GetScores().
The key is an arbitrary text value. Usually a player's key is a good choice, but you can also use the name of their character, or anything else you like, as long as it is unique. The key is case-insensitive.
Scores and stats use data fields, which might be things like "Score", "Level", "Class", etc. To retrieve all the fields associated with a key, leave the fields argument blank. To retrieve only certain fields, you can send a separated list like "Score;Level" which is in the same format returned by list2params().
If you leave the key argument blank, you will get a complete list of keys that have scores and stats associated with them.
In this form, the proc gets a list of the top scores for a certain field, and gives you the keys and scores in order. To get the top 10 players by level, for instance, you would use GetScores(10,"level"). This returns a parameter list with the top keys and scores, so it might be in a form like "Bob=100;Anita=80;David=20;Charlie=5".
The count and skip arguments are always numbers, not text. The count is the number of scores to retrieve, and skip is the number to skip over to get to them. So count=10 and skip=0 is the top 10, while count=10 and skip=5 is #6 through #15. If you leave out skip, it's a 0.
The way you set up your hub entry is how the top scores are determined. If you told the hub that the "score" field is always sorted from highest number to lowest, then that's what you'll get. If "birthplace" is set up to use an alphabetical order, that's the order that GetScores() will use. If a field cannot be sorted, this form of GetScores() will return an empty text string.
If you don't specify a field, your hub entry may have a default field to use. For instance if your hub page displays "Score", then "Level", then the "Score" field is the default.
Note: You can specify a different hub path and hub_password by adding these as extra arguments, but this is not recommended for security reasons. If you use this feature, it should only be on games that cannot be downloaded by the public.
This example defines a mob proc called Export() which writes the mob to a savefile and sends it to another server (specified by Addr). The remote server opens it as a savefile and creates the mob (if the same mob type is defined on both servers and mob.Read() is compatible with the sending server's mob.Write()).
Note that another method of transferring player mobs is to use the key savefile (accessed by client.Export() and client.Import()). Direct server to server communication on the other hand could transfer data (like non-players) without the need for player involvement at all.
Savefiles are the most common type of file to transfer, but world.Import() simply returns a reference to an item in the world's .rsc file, which could be any type of file. This particular example demonstrates how to open such a file as a temporary savefile. (It gets dumped from the cache into a separate temporary file, which is then opened as a savefile.) Other types of files would be handled differently. For example, you could use fcopy() to dump the cached item to its own separate file.
By default, this procedure checks the "ban" configuration file. If an entry is found for the current world (based on the value of world.hub), the parameter text is converted into a list (using params2list()), and the result is returned. Otherwise, null is returned.
A ban that applies to all worlds on the host's computer will not call IsBanned(). The connection will simply be denied.
This procedure is called internally whenever a new user connects (before client/New() is called). If the result is true, access is denied. If you want to ban a user but still allow them to log in (perhaps with reduced functionality), you can put "Login=1" in the parameter text. If you want to display an explanation to the user about why they are banned, you can also put "message=X" in the parameter text, where X is the message to display to the user. A reason for the ban can be added with a "reason=X" field. Of course, you can also override IsBanned() and insert these values directly into the list that is returned.
When you ban people from paging you, this also causes them to be added to the keyban list. Even if they are already connected, IsBanned() will be re-evaluated and acted upon at that time. When you remove pager ban, they are removed from keyban as well.
Additional data elements may be added to the ban list in the future. The current definition includes just the following items:
Since the data in the "ban" file is in application/x-www-form-urlencoded format, it is probably not desirable to edit the file by hand. No built-in facilities for editing the file have been provided (aside from automatic addition of pager bans), but an interface could be created, using GetConfig and SetConfig to read and write the data. Extra features could also be added such as automatic inference of key associations by IP address.
Checks a player for their subscription status to this game. This is a simpler alternative to client.CheckPassport(), which is deprecated, and also allows you to check even when the player has gone offline.
This proc will return null if contacting the hub was required, but there was no way to reach the hub. Contacting the hub may take a few moments, so it is a good idea to use spawn() to avoid holding up the rest of the game.
Note: You can specify a different hub path and hub_password by adding these as extra arguments, but this is not recommended for security reasons. If you use this feature, it should only be on games that cannot be downloaded by the public.
This causes the world to be hosted on the specified network port. A value of 0 or "any" requests that any available port be used. The value "none" causes the port to be closed so that no new connections are possible.
This proc may be overridden. If it is, calling ..() is necessary to open the port. If ..() is not called, it will not open.
The "ports" configuration option in cfg/byond.txt can be used to control what ports worlds may open. The -ports command-line option may also be used. See startup for the syntax.
Removes credits from a player's account, if they have enough. The proc will return 1 if it is successful, or 0 if the attempt failed (usually because the player doesn't have enough credits). This feature is intended for games that make use of the credit system, and for security all such games must use a hub password.
This proc will return null if there was no way to reach the hub. Use isnull() to check for a null value. Contacting the hub may take a few moments, so it is often a good idea to use spawn() to avoid holding up the rest of the game.
Note: You can specify a different hub path and hub_password by adding these as extra arguments, but this is not recommended for security reasons. If you use this feature, it should only be on games that cannot be downloaded by the public.
Interacts with the built-in server profiler without requiring the host to do so via Dream Daemon, or an authorized player via Dream Seeker.
The command value is built from bitflags, so it can combine any of these three values via the | operator:
These additional values are also defined for convenience:
By default, data will be returned as a list. The first six values are the column names: "name", "self", "total", "real", "over", and "calls", corresponding to the columns in the profiler. These are followed by the profile data for each proc, with the data being in the same column order. E.g. the next six items represent the first proc in the profile.
The optional format argument however can be used to return the data in other formats. Currently the only accepted value is "json", which will output the same data in JSON format.
Using "sendmaps" in the type argument will profile the routines used to send map informaiton to players. Unlike the proc profiling this only has three data columns: "name", "value", and "calls". The value column might be a time or number value, depending on what's being measured.
The JSON format will include a unit property data that is not a raw number, such as a time value.
Reload the world from scratch. Any connected players will automatically relogin. This would be useful if you needed to recompile the world after changing some code.
In a UNIX environment, you can cause a running server to reboot by sending it the signal SIGUSR1.
If you override this proc, you must call ..() if you want the reboot to complete normally.
For reboots initiated by Dream Seeker, usr will be the mob belonging to the player who sent the command.
This command is for storing configuration information that is shared by applications installed on the same system. The configuration data is accessed by specifying the configuration "set" and the parameter within that set.
For more information, see GetConfig.
Awards a medal to a player. The proc will return 1 if it is successful, or 0 if the medal was already awarded. If the world already knows this medal was earned before, the hub will not be contacted.
This proc will return null if there was no way to reach the hub. Use isnull() to check for a null value. Contacting the hub may take a few moments, so it is a good idea to use spawn() to avoid holding up the rest of the game.
Note: You can specify a different hub path and hub_password by adding these as extra arguments, but this is not recommended for security reasons. If you use this feature, it should only be on games that cannot be downloaded by the public.
Updates scores that are kept on the BYOND hub.
The key is an arbitrary text value. Usually a player's key is a good choice, but you can also use the name of their character, or anything else you like, as long as it is unique. The key is case-insensitive.
Scores and stats use data fields, which might be things like "Score", "Level", "Class", etc. Use list2params() to set the fields that you want to change. Fields that you do not include in the list will not be changed. A field with a blank value will be deleted.
Sending an empty text string for the fields will erase the scores for that key.
This proc will return null if there was no way to reach the hub. Use isnull() to check for a null value. Contacting the hub may take a few moments, so it is a good idea to use spawn() to avoid holding up the rest of the game.
Note: You can specify a different hub path and hub_password by adding these as extra arguments, but this is not recommended for security reasons. If you use this feature, it should only be on games that cannot be downloaded by the public.
This proc allows you to do any updates just before map info is sent out. One possible use for this is to run a movement loop, or sync up any user interface input that might have arrived and deal with it all at once.
Note: The tick will not wait if this proc sleeps. It effectively has set waitfor=0 already built in. It's a good idea not to sleep in this proc or any of its callees at all, since it will keep getting called every tick.
This example allows other servers to send this server topic text of the form "shout:msg" and will broadcast the message to all the players in this world.
The Keys argument is either null, or a list of user keys. Any keys in the list are logged in to the remote server.
Always validate the input in Topic() calls to make sure it's correct and the query you're recieving is legitimate.
Built-in world vars:
This is the network address of the machine hosting the world. If it cannot be determined, it will be null.
The full network address of the world may be formed by concatenating the world address and port: "byond://[address]:[port]".
In CGI mode, this is the web address of the world.
This is the local address only. If the world is hosted via a router, the external IP address may be different. Use internet_address to find the external address, if available.
This is the default area type to be placed on the map wherever no area is specified. A value of 0 turns off the default area.
This is the build number (minor version) of BYOND being run by this server. Typically this is not useful information, but it can come in handy when diagnosing issues reported by players when hosting with a beta build.
This is the version of BYOND at run-time. A game designed to work around known bugs in older versions could use this to adapt its behavior accordingly.
Number of days items that are not in use will be saved in the resource cache (.rsc file). Files uploaded by players are stored in the world's .rsc file for future use. If the file is not used for the specified amount of time, it will be removed to save space.
Setting this value to 0 causes items to be saved for the current session only. This is used by the CGI library, because web browsers cannot make use of server-side caches when uploading files anyway.
This value must be a whole number.
This is a list of every object in the world. Objects in this list are in no particular order.
This example displays a list of every area in existence. As a convenient short-hand, one may simply write for(A) or for(A in world) instead of the full for(A in world.contents).
This is the percentage of a server tick that the server spends processing running procs and the work of sending map information to players. A value of 0 would indicate very little cpu usage. A value of 100 would indicate full cpu usage, which could mean that the server cannot complete all the necessary computations during a tick to finish in time for the next tick. In this case, timed events (such as sleep) may take longer than requested.
When deciding on a value for tick_lag, one could use this value to determine if the CPU is fast enough to tick at a higher rate.
The map_cpu var is a subset of this, measuring only time used for sending map information.
This option is for direct execution of .dmb
files in UNIX.
The most common use is for writing CGI programs that are executed by the web
server.
The first parameter in the executor text string is the path to DreamDaemon. The one listed above is the standard UNIX location.
Optional parameters may follow. The most common are -CGI and -logself.
This example creates a CGI program to be executed by a web server. It
puts its error output in the file projname.log
.
All of this is configured for you when you include
html/CGI.dm
from the html library.
The value of world.fps defines the speed of the world in frames (server ticks) per second. By default this is 10 fps, which is a good speed if all objects move in full tiles. Higher values yield smoother results, but at a cost to performance. Timing of many events may be limited by the system clock, so fps values beyond 40 or 50 may cause unwanted effects like jitter even for projects that are not very demanding in terms of performance.
For projects making use of pixel movement, higher fps is usually desired. 40 seems to be a good value for general use, but in worlds that have a large number of players, you may wish to lower the value and give players a higher step_size per tick instead.
This var exists for convenience; it is calculated by 10 / world.tick_lag. The value of world.tick_lag is actually more accurate, but it is easier to think of world speed in terms of frames per second. The actual tick rate has a resolution of 1 ms.
When reading world.fps, the result is always given as a whole number to gloss over rounding error.
If you set client.tick_lag or client.fps to a value other than 0, you can make the client tick at a different (usually faster) rate.
At runtime, this value may be changed to let the BYOND hub know about certain changes in the game's status. An example for using this value is if the number of players in the game gets too high and most new logins are rejected, you can set game_state to 1 to let the hub know this server is full.
The following values are accepted:
Note that this value does not affect how your world actually reacts to new players logging in. It is only used by the hub and website.
If the information is made available by the pager, this will provide the key of the world's host. If the host is not known, this value will be either null or an empty string.
This is a registered BYOND hub path. The default value of null is for unregistered games. Registered games (don't worry, it's free!) have their own hub page showing a brief description of the game, the author, an optional installation package, and links to online games. The hub path is a string of the form "YourName.GameName" and can be found in your hub console.
Even unregistered games show up in the hub when they are live (that is online with people connected). It just doesn't show any of the extra info like a description, and there is no way for people to find out about it when nobody is logged in.
If you do not want your game to show up in the hub (like while you are in
the initial stages of development), just compile with
visibility=0
. Either that, or turn off your pager or your BYOND
locator when you are connected to it.
You (or the players) might also wish to turn off the notice of a live game in the hub when there is no longer any room for new players or if it is too late in the game for new people to join. At such times, you can simply set the visibility to 0.
If you configure your hub page to require a hub password, you must also
specify world.hub_password
.
If world.hub
is set, any live session of the game will be
attached to the specified BYOND Hub page. Under the default settings,
any game can set world.hub
and attach itself to any BYOND
Hub page.
To beef up security, you can set a hub password in your hub's
configuration page via the BYOND website. This will ensure that
only authorized copies of your game can attach themselves to your
hub page when live. Then simply copy that password into your code as
world.hub_password
so that your game's live broadcast will
be accepted by the hub.
Note that for security reasons, reading this variable at runtime will return a hashed version of the value that was set.
This is the tile size that will be used as a default for icons in the world. It can be set to a single number that represents both the width and height, or you can use a format like "[width]x[height]" (such as "16x48") to specify width and height separately.
This value affects several calculations, including icon operations and gliding between turfs.
Note: If you do not use a square icon size and you are using a topdown map format, you may experience display issues if setting client.dir to EAST or WEST. A non-square tile with a topdown map format will also interfere with pixel movement. For this reason, square sizes are recommended when using any topdown-view map format.
This is the network address of the machine hosting the world, as it is seen by the outside network (from the Internet) and the hub. If it cannot be determined, it will be null.
The full network address of the world may be formed by concatenating the world address and port: "byond://[address]:[port]".
This var exists because world.address may not be accurate if the world is hosted on a machine behind a router using NAT. The value returned by internet_address can be given to other players who wish to log in.
Sending output to world.log may be useful for debugging purposes. The output goes to the same place run-time proc errors are displayed.
You can assign world.log to a file name or file() object to redirect output to that file. (There is also a command-line option to Dream Daemon that does this.)
Setting this to 0 disables the very long loop protection. By default, loops in the code which undergo a very large number of iterations or recursions are aborted (by crashing the proc). This prevents the proc from locking up the server for too long.
You may need to disable this feature if your code has some very long loops in it. Before doing that, make sure it's not infinitely long! Your program will utterly crash if it runs out of system stack space, which can happen in a very deep or infinite recursion.
Note: The compiler will now generate a warning when you disable loop_checks. It is not advisable to disable the check unless you're trying to debug something, since you can cause the server to hang. Generally if you have a loop so long it can cause the regular loop checks to freak out, you need to make a change to the loop behavior anyway.
This value says how the world will display maps. In a normal overhead tiled map the value is TOPDOWN_MAP for the top-down format. For older games that predate this feature, the value is TILED_ICON_MAP.
If you use a map format other than top-down, the HUD will still use a tile format like it would in top-down display. HUD objects are not projected into whatever map_format you use and they are not affected by changing client.dir. The size of the HUD is rounded up to the nearest number of full screen tiles; the size of each tile is defined by world.icon_size.
This is the default map format. Icons are drawn in a tile form and viewed from overhead. In this layout, the layer assigned to each atom is very important. The number of tiles shown is set by client.view or world.view.
Because this format is familiar and easy to understand, it is the default setting. Most of the vars related to maps and atoms are designed and documented with this format in mind.
If map_format is set to ISOMETRIC_MAP, the map is displayed in isometric form. Isometric tiles are displayed in a foreshortened diagonal perspective, where the "north" direction actually displays as northeast on the player's screen, and "east" shows up as southeast. The value of client.view or world.view is used to calculate the minimum number of tiles to display, and extra tiles to each side will be shown to fill in the corners.
In an isometric map, the tile width set in world.icon_size is the most important factor. This should be a multiple of 4 for best results. The minimum tile height is half that value, and any extra height is used to show vertical structures that "stick up" off the map surface. When you draw an isometric tile icon, start with a flattened diamond shape at the bottom that is only half as high as it is wide.
Isometric maps behave differently during drawing than top-down maps. In isometric, tiles that are nearer to the viewer's perspective are drawn in front of tiles farther back, regardless of layer. Layers only count within an individual tile. This means that if you want to have a vertical structure "stick up" to partially hide something behind it, the icon sticking up should always be on a tile forward from the one being partly covered. E.g. if you have a wall taking up part of your tile, it needs to be at the "back" end of the tile to properly hide anything on the tiles behind it.
The pixel_x and pixel_y values, step_x and step_y values, and the gliding that happens when moving between tiles, are based on the width set by world.icon_size. If you set world.icon_size="64x128" to show tall buildings, only the 64 matters for pixel offsets. Use pixel_w and pixel_z to adjust the position of atoms (or the client) horizontally or vertically without respect to client.dir or the map format.
Note: Offsets for x and y also affect the layering order used to draw the icons. Any object with a pixel offset onto another tile is considered part of whichever tile is closer.
If you use an icon wider than one tile, the "footprint" of the isometric icon (the actual map tiles it takes up) will always be a square. That is, if your normal tile size is 64 and you want to show a 128x128 icon, the icon is two tiles wide and so it will take up a 2×2-tile area on the map. The height of a big icon is irrelevant--any excess height beyond width/2 is used to show vertical features. To draw this icon properly, other tiles on that same ground will be moved behind it in the drawing order.
One important warning about using big icons in isometric mode is that you should only do this with dense atoms. If part of a big mob icon covers the same tile as a tall building for instance, the tall building is moved back and it could be partially covered by other turfs that are actually behind it. A mob walking onto a very large non-dense turf icon would experience similar irregularities.
The SIDE_MAP format is like a cross between TOPDOWN_MAP and ISOMETRIC_MAP. It looks very similar to a top-down view but it is intended for more of a 3/4 perspective, where tiles lower on the screen are considered closer to the viewer. Because this impacts the way layers work, most of the layering behavior is the same as with isometric.
In a 3/4 perspective the tiles are often foreshortened, so pixel offsets are adjusted to account for this. For example, you may set world.icon_size to "32x24", but the tile is considered to be a perfect square if you look at it from the top down. Because the width is 32 pixels, the virtual height is also 32, so if you use pixel_y=32 the atom will appear one tile further back than it normally is. (This adjustment doesn't affect screen objects or pixel_w/pixel_z.)
Changing client.dir preserves the same tile size regardless of orientation.
This is the percentage of a server tick that the server spends processing information about the map to send to players. A value of 0 would indicate very little cpu usage. A value of 100 would indicate full cpu usage, which means that the server cannot complete all the necessary computations during a tick to finish in time for the next tick. In this case, timed events (such as sleep) may take longer than requested.
The world map is a three-dimensional block of turfs with coordinates ranging from (1,1,1) to (maxx,maxy,maxz). If set at compile time, it provides a lower bound and will be increased as needed by the map files.
The default value is 0, indicating no map. If any of the map dimensions are set to non-zero values at compile time, the others will default to 1.
New territory created by increasing the map boundaries is filled in with the default turf and area (world.turf, and world.area).
The world map is a three-dimensional block of turfs with coordinates ranging from (1,1,1) to (maxx,maxy,maxz). If set at compile time, it provides a lower bound and will be increased as needed by the map files.
The default value is 0, indicating no map. If any of the map dimensions are set to non-zero values at compile time, the others will default to 1.
New territory created by increasing the map boundaries is filled in with the default turf and area (world.turf, and world.area).
The world map is a three-dimensional block of turfs with coordinates ranging from (1,1,1) to (maxx,maxy,maxz). If set at compile time, it provides a lower bound and will be increased as needed by the map files.
The default value is 0, indicating no map. If any of the map dimensions are set to non-zero values at compile time, the others will default to 1.
New territory created by increasing the map boundaries is filled in with the default turf and area (world.turf, and world.area).
When a player connects to the world, the world is searched for a mob with the player's key. If one is found, the player is connected to that mob. If none is found, a new mob of type world.mob is created and the player is connected to this new mob.
The default value is /mob. Setting world.mob to 0 prevents the creation of default mobs.
This example will connect new players to mobs of type /mob/newbie. They are welcomed when they connect.
Controls how movement works on the map.
TILE_MOVEMENT_MODE allows you to easily discard any and all pixel movement, so if step_x or step_y coordinates or unexpected atom bounds were loaded from a savefile, for instance, they would be eliminated. If you use any other movement mode, you can give an atom the TILE_MOVER flag and it will behave as if it were in this mode, while other atoms are free to do their own thing.
LEGACY_MOVEMENT_MODE exists to distinguish between old and new movement behavior. In older versions of BYOND before pixel movement, turfs took their contents into consideration by default in Enter() and Exit(). This doesn't really make sense for newer games, so in any other movement mode the turf behavior will ignore its contents. mob.Cross() is also affected, since it would return 0 by default in legacy mode when both mobs were dense; now by default it checks mob.group.
This is the name of the world.
This is a list of parameters passed to the world from the command-line -params option when the server was started. The parameter text is passed through params2list() to generate the world.params list.
This example displays the value of each parameter.
This is the network port of the world. If the world does not have an open network port, this is 0.
This read-only variable indicates the ID of the server's process on the system running it. The result is a number, unless for some unexpected reason the number won't fit in a num type, in which case it will be text. (In practice it should always be a number.)
This is the time (in 1/10 seconds) since 00:00:00 GMT, January 1, 2000 (also known as the BYOND era).
Because this is a large number, BYOND's number system isn't capable of
enough precision to deliver the exact number of 1/10 second ticks. It usually
rounds off to the nearest several seconds. For more accurate readings use
world.timeofday
.
Returns 1 if the world is currently hosted and the port can be reached by players (as determined by the BYOND hub), 0 if not.
If the port is not reachable, there may be a brief period during which the hub is still attempting to make contact; during that time the port is assumed to be reachable. Currently, the reachability test times out and fails after 30 seconds.
Setting this to 1 causes the world to be suspended when there are no players, even if you have sleeping procs waiting to happen. The default value is 0, which means the server will only sleep if there are no players and no procs waiting to happen. The main purpose of the variable is to save the cpu from doing work when there is nobody around to appreciate it. On the other hand, that doesn't give the poor NPC's a break from the nasty humans.
This is a short text string used in BYOND hub to describe the state of a game in progress. For example, you might want to indicate if new players will be able to actively play, or whether they would have to join as spectators.
This variable indicates the operating system type at run-time. It will be one of the following constants:
This is the smallest unit of time (one server tick) measured in 1/10 seconds. The duration of events that take some finite amount of time (like sleep) will be rounded to a whole number of ticks.
Players are limited to one command (including movements) per server tick, so this value can be used to adjust the responsiveness of the game. If the network is too slow to keep up with players, their commands will get queued up, which can be annoying when trying to move. In this case, tick_lag should be increased so that the stored up movement commands are discarded. On the other hand, if you have a very fast network, you may wish to decrease tick_lag to speed up the response time to player commands.
Often it is more convenient to set world.fps instead of world.tick_lag, since fps (frames per second) is an easier way to think of server ticks. world.tick_lag is 10 / world.fps and vice-versa, so a tick_lag of 0.25 is equal to 40 fps.
If you set client.tick_lag or client.fps to a value other than 0, you can make the client tick at a different (usually faster) rate.
This is the approximate percentage of the server tick that has been used already. A value under 100 means there's time to do more calculations, which can include any pending procs that are still waiting to run on this tick. When the value is over 100, the tick is running long and your world will experience lag.
Keep in mind that sending maps to clients is the last thing that happens during a tick, except for handling any events such as player commands that might arrive before the next tick begins. Therefore in a verb, tick_usage might have a higher value than you would expect to see in a proc that loops and sleeps.
This gives the amount of time (in 1/10 seconds) that the world has been running. In actual fact, it is the number of server ticks that have passed multiplied by world.tick_lag. Therefore if the server sleeps (when no players are connected) this time is not counted. Also, if the server runs overtime during a tick (because procs take longer than tick_lag to finish) this still only counts as one tick. This value is therefore a measure of "game time" rather than real time.
This is the time (in 1/10 seconds) since 00:00:00 GMT today. It is
basically identical to world.realtime
but doesn't include any
information about the date. This is a much smaller number; hence it is more
accurate.
This is the time offset from UTC, in hours, for the world's time zone. It can be used in the time2text() proc, although it is the default time zone for that proc.
This is the default turf type to be placed on the map wherever no turf is specified. A value of 0 turns off the default turf.
This is the full network address of the world. (For example, byond://dan.byond.com:6005.)
If you are distributing your game to players, you can use this variable
to automatically notify them of new releases. To do so, you will first need
to set world.hub
to the hub
path of your game. You can then advertise the current version by
configuring that value in your
hub console.
When players boot up an outdated version of your game (as indicated by
comparing world.version
with the version advertised by BYOND
hub), they will be notified of the new release.
This is the default map viewport range. The default value of 5 produces an 11x11 viewport. A value of -1 turns off the map display altogether. The client may automatically scale down icons in order to conveniently fit the map on the player's screen.
For non-square views, you can assign this to a text string of the form "WIDTHxHEIGHT". For example, "11x11" is equivalent to a view depth of 5, but you could make it wider like this: "13x11".
This setting also affects the default range of the view()
,
oview()
, range()
, and orange()
procedures.
If the entire map is small enough to fit on one screen
(arbitrarily defined to be 21x21 or less),
the default view
is automatically adjusted to fit the map. In
this case, client.lazy_eye
is also automatically turned on by
default, since you probably don't want the map to scroll around.
This controls whether the world advertises itself in the BYOND Hub when it has an open network port for accepting players. The visibility of the world still depends on whether any of the connected players has their location reporter turned on, and that in turn relies on the pager being turned on.
This section of the reference should help explain some concepts that may be harder to understand or that can use more clarification.
This is mostly no longer needed. A negative value for plane is the preferred way to do show objects in the background. It can still be used however when you want to rearrange objects in the same plane when using PLANE_MASTER for visual effects.
BACKGROUND_LAYER is a special high value that can be added to the regular layer of any atom.
The purpose of this value is to make an atom appear below any regular atoms, even if they share the same plane. In an isometric map for instance, HUD objects will always appear above the map, but makeing a HUD object appear behind the map was basically impossible without this feature until plane was implemented.
When using this special layer, it should be added to the layer an atom normally uses. For instance an obj should have a layer of BACKGROUND_LAYER + OBJ_LAYER.
This can be mixed with TOPDOWN_LAYER and EFFECTS_LAYER, but it will take precedence over both. Anything with BACKGROUND_LAYER will always appear below anything without it on the same plane.
Images or overlays with FLOAT_LAYER can be left alone. They will automatically have the same layer as whatever atom they are attached to.
BYOND allows you to use icons that are not the same size as the tile size defined in world.icon_size. These icons can be manipulated with the /icon datum using their raw, native size, and shown on the map in full size. To use the old behavior where an atom can display only an icon of the normal tile size, use the TILED_ICON_MAP value for map_format instead.
When you use an icon of non-standard size on an atom, the icon is "anchored" to the southwest corner of the atom. If you are using a top-down view (world.map_format=TOPDOWN_MAP), the icon will appear to spread out further to the east and north. In an isometric map (world.map_format=ISOMETRIC_MAP), the icon will cover additional tiles north and east as well. The "footprint" of an isometric icon--the actual map tiles it covers--is always square, so if your tile size is 64x64 and you use a 128x64 icon, the 128-pixel width means the icon will cover a 2x2 section of map tiles.
It is important to remember that using a big icon is a visual effect only. It will not affect how the atom bumps into other atoms or vice-versa.
Big icons will affect layering--the order in which icons are drawn. In general, because a big icon is covering more than one tile of the map, it will try to draw above any other tiles in that space that are on the same layer. This way, you can set a turf to use a big icon without having to change the turfs to the north and east. If an atom has a big icon, any overlays and underlays attached to it will be pulled forward as well, so they will draw in front of anything on their same layer. In isometric mode this is about the same, except that the layer isn't that important--anything in the way will just be moved back behind the big icon.
Note: Big overlays will not "pull forward" on their own. If the main atom uses a single-tile icon, a big overlay attached to it will not try to draw in front of other icons on the same layer. This is so that name labels, health bar overlays, etc. will not cause any odd behavior. To be safe, you should always specify a layer when adding an overlay.
In isometric mode, layering is affected by the "distance" between the atom and the viewer, so putting a regular-sized icon and part of a big icon on the same tile could cause layering oddities. Tiles that are covered by a big icon will tend to be drawn behind the big icon as mentioned above. For this reason, any atoms whose icons cover more than one tile (the extra height of an isometric icon doesn't count) should always be dense, and you should block movement onto any tile covered by them.
When manipulating icons with the /icon datum, you can still use Blend() to combine icons of different sizes. By default, the icons will be lined up at their southwest corners. You can change the position at which the second icon is blended.
A color gradient is a special list that defines a range of colors that you can smoothly interpolate between. A simple example is a gradient from red to white:
Applying a number like 0.2 to this gradient would give you a color that's 20% of the way from red to white. More complex gradients however are also possible.
The format of a gradient is a list that contains a number (the position along the gradient, from 0 to 1 unless you use values outside that range) followed by a color. You can have as complex a gradient as you like. If you reuse the same number twice in a row, the gradient will have a sudden color change at that point.
It is also possible to skip numbers or colors, and they will be filled in automatically with the previous number or color. The exceptions are at the beginning and ends of the list; at the end of the gradient, the last color is assigned a number 1 by default, and the first is assigned 0. If you skip colors at the beginning, they will be filled in with the first color you use.
Include "loop" anywhere in the list to make this a looped gradient. If you don't, any numbers outside the gradient's range will be clamped to that range. E.g., in a normal gradient ranging from 0 to 1, a number of 1.2 is interpreted as 1 without a loop and 0.2 with a loop.
Here are some more examples:
You can also include "space" in the list, and give it an associated value that describes the color space this gradient uses to interpolate between colors. For instance, "space"=COLORSPACE_HSL will use HSL interpolation instead of the default RGB. See Color space for more information.
Currently, color gradients are only used by particle effects and the gradient proc. With particles, if you use a gradient the particle's color is given as a number, and that number is used to look up its real color from the gradient. The number can change over time, thus changing the particle's color.
A color matrix is used to transform colors, in the same way that a matrix represented by the /matrix datum is used to transform 2D coordinates. A transformation matrix is 3x3, of which only 6 values are needed because the last column is always the same. A color matrix, because it transforms four different numbers instead of two, is 5x5.
|rr rg rb ra 0| |gr gg gb ga 0| [r g b a 255] x |br bg bb ba 0| = [r' g' b' a' 255] |ar ag ab aa 0| |cr cg cb ca 1|
In easier-to-understand terms, this is how the result is calculated:
It is helpful to think of each row in the matrix as what each component of the original color will become. The first row of the matrix is the rgba value you'll get for each unit of red; the second is what each green becomes, and so on.
Because the fifth column of the matrix is always the same, only 20 of the values need to be provided. You can use a color matrix with atom.color or client.color in any of the following ways:
Reading a color var that has been set to a matrix will return the full 20-item list, where every 4 items represent a row in the matrix (without the fifth column).
In the MapColors() icon proc, the values are sent as arguments, not as a list.
This is mostly no longer needed. A negative value for plane is the preferred way to do show objects in the background. It can still be used however when you want to rearrange objects in the same plane when using PLANE_MASTER for visual effects.
EFFECTS_LAYER is a special high value that can be added to the regular layer of any atom.
The purpose of this value is to make an atom appear above any regular atoms. For instance, in an isometric map if you want to display a character's name below them, it does not make much sense to have nearer objects cover up that name, so you can tell the name overlay to use EFFECTS_LAYER + MOB_LAYER and it will show up on top of all the normal icons on the map. This has been somewhat obviated by plane but may still be useful in some cases.
When using this special layer, it should be added to the layer an atom normally uses. For instance an obj should have a layer of EFFECTS_LAYER + OBJ_LAYER.
This can be mixed with TOPDOWN_LAYER, in non-topdown map formats. Anything in TOPDOWN_LAYER will display on top of EFFECTS_LAYER, and TOPDOWN_LAYER + EFFECTS_LAYER will be above both.
This can also be mixed with BACKGROUND_LAYER, which takes priority over everything else.
Images or overlays with FLOAT_LAYER can be left alone. They will automatically have the same layer as whatever atom they are attached to.
Filters are a way of adding special effects to an icon, or a group of icons (see KEEP_TOGETHER in appearance_flags), by post-processing the image. A filter object describes a specific form of image processing, like for instance a blur or a drop shadow. Filters can be added or removed at will, and can even be animated.
A filter is created by using the filter proc like so:
These are the filters currently supported:
Uses an icon or render target as a mask over this image. Every pixel that is transparent in either the image or the mask, is transparent in the result.
The x and y values can move the mask from its normal position. By default, the mask is centered over the center of the image.
The MASK_INVERSE flag will invert the alpha mask so that opaque areas in the mask become transparent, and vice-versa. There is also a MASK_SWAP flag which treats the source image as the mask and vice-versa, which might be useful for some effects.
Note: Unlike many other filters, this filter is taken into account for mouse-hit purposes.
Blurs the image by a certain amount in a circular formation, as if the image is spinning. The size of the blur can roughly be thought of in "degrees" worth of blur. As the distance from the center increases, the blur becomes more noticeable since the same amount of angular motion has to travel farther along a circle.
Typically this blur is used with an entire plane, but it could be used to give a sense of motion blur to a spinning object.
Note: Large blurs will look worse toward the edges due to limited sampling. Loss of accuracy will appear where size × distance is greater than about 300. You can increase accuracy by breaking up large sizes into multiple filter passes with differing sizes. The blur used is Gaussian, so combining blur sizes A and B will give a total size of sqrt(A2+B2).
Post-processing effect that makes bright colors look like they're a strong light source, spreading their light additively to other nearby pixels. This is a complex effect that involves multiple shader passes. For both performance and visual reasons, it is usually best applied to an entire plane rather than to individual objects.
The color threshold determines which pixels this effect applies to. If any of the red, green, or blue components of the pixel are greater than the same component for the threshold, that pixel will bloom. The blooming pixels then have their colors spread outward to create a glow that gets added to the original image.
The offset and size parameters are used to control the glow effect. They work the same as they do in the drop shadow filter: offset causes the light to grow outwards, and a blur of size is then applied to soften it. Often just using a blur alone will produce a pleasing effect. By playing with these two values you can make the bloom effect appear differently.
The alpha value is applied to any light contributions from bloomed pixels that get added to the original image, so values lower than 255 can make the effect less pronounced. This can be very useful if you choose to animate the filter.
Blurs the image by a certain amount. The size of the blur can roughly be thought of in "pixels" worth of blur.
Note: Large blurs will result in reduced performance. The highest size that can be handled easily in this filter is 6. Higher sizes require multiple passes, although the filter will "cheat" and use low-quality passes for much higher sizes.
Applies a color matrix to this image. Unlike with the atom.color var, you can apply color conversions other than the regular RGBA color space, depending on the value of space. See Color space for more information.
Uses an icon or render target as a template for various warping effects on the main image.
In the displacement map, pixels that have a higher red component will make the image appear to warp to the left, lower reds warp it to the right, and gray (r=128) will cause no horizontal warping. The green component affects the vertical: higher to warp upward, lower to warp downward. Transparent pixels in the displacement map will have no effect.
This can be used for very complex distortion, unlike other distortion filters such as wave and ripple that are confined to specific equations.
Applies a drop shadow to this image. This is a combination of multiple filters, since it will apply an outline if offset is included, a Gaussian blur to the shadow, and will underlay the shadow beneath the image.
You can also think of this filter as an outer glow.
If you use a size less than 0, the shadow will appear inside the image instead. This would be an inset shadow, or inner glow.
Composites another image over or under this image. Using the FILTER_OVERLAY flag, which is the default, puts the second image on top of what's already here. FILTER_UNDERLAY puts it underneath.
The x and y values can move the mask from its normal position. By default, the second image is centered over the center of the first.
The color, transform, and blend_mode vars are available for convenience. Because the bottom image is drawn over a blank background, blend_mode is always applied to the top image. All of the other vars apply to the second image being drawn.
Note: Transforms use default bilinear scaling, since PIXEL_SCALE is not available here.
Note: Like most other filters, this filter is not taken into account for mouse-hit purposes. Any layered icons will be strictly visual.
Applies Gaussian blur in one direction only. The amount and direction are both specified by x and y. The size of the blur is equal to sqrt(x*x + y*y).
See Gaussian blur for more information.
Applies an outline to this image.
At larger sizes, the outline is less accurate and will take more passes to produce. Performance and appearance are best at sizes close to 1 or less.
flags can be a combination of the following values:
Blurs the image by a certain amount outward from the center, as if the image is zooming in or out. As the distance from the center increases, the amount of blurring increases, and near the center the blur is hardly visible at all. The size value is smaller by default for this filter than it is for other filters, since it's typically used with an entire plane where the distance from the center can easily be several hundred pixels.
Typically this blur is used with an entire plane.
Note: Large blurs will look worse toward the edges due to limited sampling. Loss of accuracy will begin when size × distance is greather than 6. You can increase accuracy by breaking up large sizes into multiple filter passes. The blur used is Gaussian, so combining blur sizes A and B will give a total size of sqrt(A2+B2).
Draws random rays that radiate outward from a center point. (That point may be outside of the image.) As they move outward, their alpha value diminishes linearly. These are meant to be animated. The offset value determines the "time", where every jump of +1 can be a very different set of rays, and every 1000 units this filter will repeat.
The threshold value can be thought of as a way of culling lower-strength rays. Ray strength is anywhere from 0 to 1 at any given angle, but values below threshold may as well be 0. Values above that are re-scaled into a range of 0 to 1.
The factor parameter allows you to tie the ray's length to its strength. At 0, the length of every ray is the same. At 1, the length ranges from 0 to size. Generally speaking, the higher factor is, the more the rays will appear to move outward as they strengthen and inward as they weaken.
Ray color can be provided as a matrix. Only the diagonal values of the color matrix will be used, but using a matrix will allow you to set values outside of the normal color range.
flags can have the following values:
Applies a ripple distortion effect to this image.
This filter is meant to be animated. A good animation will typically start at a radius of 0 and animate to a larger value, with size decreasing to 0.
The falloff parameter can be tweaked to your liking. A value of 1 should look reasonably like ripples in water, with the inner ripples losing strength. A value of 0 will cause no reduction in strength.
The equation governing the ripple distortion is size × sin(2πr') ÷ (2.5 × falloff × r'2 + 1), where r' = (radius - distance) ÷ repeat.
Up to 10 ripples can be stacked together in a single pass of the filter, as long as they have the same repeat, falloff, and flags values. (See the wave filter for the WAVE_BOUNDED flag.)
Applies a wave distortion effect to this image.
The x and y parameters specify both the direction and period of the wave; the period is sqrt(x*x + y*y).
This filter is meant to be animated, from whatever offset you want to offset+1, and then repeating. With multiple waves, you can produce a very convincing water effect.
The equation governing the wave distortion is size × sin(2π(d - offset)), where d is the number of wave periods' distance from the center along the x, y direction.
The WAVE_SIDEWAYS flag will cause the distortion to be transverse (perpendicular) to the wave instead of in the same direction as the wave. The WAVE_BOUNDED flag limits the distortion to the confines of this image, instead of lettings its pixels spill out a little further from the distortion (and likewise, transparent pixels spill inward).
Up to 10 waves can be stacked together in a single pass of the filter, as long as they have the same WAVE_BOUNDED flags.
A generator is an object that can produce a random number, vector (list of 3 numbers), color (as a text string), or color matrix (list of 20 numbers) in a specified range according to rules you set down. It is used primarily for particle effects, since it can run on the client.
There are several types of generators:
Generators can also be chained together with math operators and some procs. The second value can be a regular value instead of a generator, so for instance you can multiply a vector by 2, or by a matrix to transform it.
Operators | Action |
---|---|
+ - * / | Arithmetic operators. You can multiply a 3D vector by a color matrix (where red,green,blue in the matrix correspond to x,y,z) to do a 3D transform, or by a 2D matrix for a 2D transform. |
- (unary) | Negate the value, same as multiplying by -1. |
turn(), generator.Turn() | Rotate a vector clockwise in the XY plane. |
Gliding is a "glitz" effect applied by BYOND to cover up the visual sins of tile-based movement, by making objects and the map appear to move smoothly from one tile to another instead of immediately jumping. It is also available to smooth over small jumps in pixel movement that might occur, for instance if the client FPS is set higher than the server's.
To control the gliding speed of an atom, set glide_size
to the
value of your choice. If this is not set, the client will attempt to adjust
the speed manually. glide_size
is measured in server ticks, so
if client.fps
is set to a value greater than world.fps
,
it will be scaled appropriately.
Whether an object glides or jumps is based on how far it moves relative to its step_size value, which by default is a full tile width. If the movement goes too far past step_size in the X or Y directions, it's no longer a glide.
The animate_movement var can be used to control the way in which an object glides, or suppress gliding altogether.
By using the LONG_GLIDE flag in appearance_flags, a diagonal glide will take just as long as a cardinal-direction glide by moving a fullt glide_size pixels in the dominant X or Y direction. Otherwise, gliding tries to move by that many pixels in strict Euclidean distance (a straight line) and diagonal glides take longer.
In LEGACY_MOVEMENT_MODE, gliding is turned off if you set any of the bound or step vars for an atom to a non-default value. The only gliding that occurs in this case is when client.fps is higher than world.fps. All other movement modes base gliding on an atom's glide_size value.
HUD stands for Heads-Up Display, and refers to any atoms that appear on the screen but don't move when the player moves. These are also called screen objects. Any movable atom can be added to the HUD by setting its screen_loc var, and adding it to client.screen for each user who is supposed to see it. This can be used to display a character's vital stats, scores, etc.
If you want to have something like a health meter or name attached to a moving atom, use overlays or /image objects instead. An /image object is similar to a screen object in that it can be shown to only certain players instead of being shown to everyone.
The size of the screen depends on client.view (or world.view), world.map_format, and world.icon_size. In a normal topdown map format, client.view is the same as the screen size; in other map formats the screen might be a different size.
The screen_loc var can be set to a value like "1,1" (the southwest tile of the screen), "4,NORTH" (fourth tile from the west, along the north side of the screen), "SOUTHEAST", and so on. You can also include pixel offsets, percentages, and specify two corners to tile an icon repeatedly from one end to the other. See screen_loc for more details.
screen_loc can also be used to stretch the bounds of the HUD. A value of "0,0" will cause the atom to appear to the southwest of the southwest-most tile on the visible map, outside of the regular map bounds. Using HUDs in this way, you can provide a nice decorative "frame" for your map.
More complex
You can use HUDs in other map controls as well, by preceding screen_loc with the name of the map you will use followed by a colon. For instance, screen_loc="map2:1,1" will show an icon in the southwest corner of the map2 control. The actual size of a secondary HUD is based on how far out the icons in it extend in any direction. If you have one icon at "map2:1,1" and another at "map2:4,3", then that HUD will be four tiles wide and three high.
Isometric projection is a form of pseudo-3D in which the 2D icons used by BYOND can be arranged in a way to give the appearance of three dimensions. If you look at a map top-down, each tile on the map is a square. The map is rotated 45° clockwise and then tilted at an angle (30°) so that each square now looks like a foreshortened diamond from the viewer's perspective. What was once north now points to the northeast end of the viewer's screen; what was once east now points southeast to the viewer. Tiles that are more to the south or east are "nearer" to the viewer, and tiles that are north or west are "farther". The actual direction the map faces can be changed by using client.dir.
It is important to remember that this is an illusion of 3D, not real 3D.
To use isometric mapping, set world.map_format to ISOMETRIC_MAP. You should set world.icon_size so the tile width is a multiple of 4 pixels. The width of the tile is highly important. The height of your tiles should be at least half that value. BYOND uses a 2:1 isometric format, meaning that the diamond base of each tile is half as high as its width. For example if you have a 64x64 tile size, every diamond in the map will be 64 pixels wide by 32 high, and you have an extra 32 pixels at the top of your icon for vertical projections like buildings. If you set the tile size to 64x80, the base is still a 64x32 diamond and you have 48 pixels left over for vertical structures.
In this mode pixel_x and pixel_y will offset icons along the "ground". To adjust horizontal and vertical positions, use the pixel_w and pixel_z vars.
The layer var behaves differently in isometric mode. Because some tiles are nearer to the viewer than others, the tiles that are farther back need to be drawn first so they are behind any tiles that should go in front of them. So in isometric mode, the back row of tiles (a diagonal line of them) is drawn first, followed by the next row forward, and so on. The layer var only matters when icons overlap each other in the "physical" space, like an obj sitting on a turf.
When pixel or step offsets, or gliding, place an object on multiple turfs, it is drawn on top of the nearer turf (assuming its layer is higher).
Using icons wider than the regular tile size can have an impact on layering as well. See Big icons for more information.
Because of the order in which icons are drawn, you may want to limit the ability of an atom to cut diagonally around corners. While moving northeast behind a dense wall, for instance, a mob might temporarily appear in front of the wall because its pixel offsets (from gliding) temporarily put it on the same tile as the wall. If you do not want to limit corner-cutting, a simple workaround for this case is to give the wall a higher layer than the mob.
Screen objects (in client.screen) are always drawn on top of all isometric tiles, as is the case in other map modes as well.
Since it may be desirable in some games to use a topdown map for some situations (like a special battle map), you can add TOPDOWN_LAYER to any atom's layer—e.g., TOPDOWN_LAYER+TURF_LAYER—to make it appear in topdown mode. Topdown and isometric tiles really aren't meant to be mixed, but if they do mix you'll see topdown tiles always display above isometric tiles, just like screen objects do. The best way to use this is to apply TOPDOWN_LAYER to every tile in a certain part of the map that the players can't walk to.
If you want to use an overlay that should not be covered by other "nearer" icons on the map, such as a name or health meter, you can add EFFECTS_LAYER to the overlay's layer. Icons with EFFECTS_LAYER will draw above regular icons. Then objects with TOPDOWN_LAYER will draw on top of everything else. However, be aware that EFFECTS_LAYER has largely been superseded by the plane var.
In this mode, world.view or client.view is used to define the minimum number of map tiles you will see, not the screen/HUD size which is calculated from client.view. Extra map tiles are shown to fill out the screen size. HUD objects use screen coordinates, so 1,1 is still the lower left.
The actual HUD size is always a full number of tiles, whose size is defined by world.icon_size. If you have a tile size of 64x64, and world.view=6 (a 13x13 map), a full 13x13 diamond of map tiles will be shown. The width of this diamond is 13 tiles. The height is only half that, plus whatever vertical space is needed to show the icons in that area. Then everything is rounded up to a full tile size, so the result is a 13x7-tile screen. This is the formula you need if you want to calculate the screen size:
If you use TOPDOWN_LAYER, any topdown sections of the map will be limited to this same view.
In DM, all numbers are stored in floating point format. Specifically, single-precision (32-bit) floating point. This is important to know if you think you will be working with large numbers or decimal values a lot, because the accuracy of the numbers is limited.
32-bit floating point numbers can represent integers from -16777216 to 16777216 (224). Non-integer values can get about as small as 2-126 and as large as 2127.
Floating point numbers do not handle most decimal values precisely. For instance, 0.1 is not exactly 0.1, because floating point numbers are stored in a binary format and in binary, 1/10 is a fraction that repeats forever—the same way 1/3 repeats as 0.33333... in decimal numbers. It ends up being rounded off, either a little higher or a littler lower than its true value. This means that the following loop won't work like you might expect:
You might expect that code to loop exactly 1000 times, with i going from 0 up to 99.9 before stopping. The truth is more complicated, because 0.1 stored in floating point is actually greater than the exact value of 0.1. Other values might be more or less than their exact numbers, and as you add these numbers together repeatedly you'll introduce more and more rounding error.
Even more insidious, if you add 0.1 a bunch of times starting from 0, and then subtract it out again the same number of times, the result you get may not be 0. This is counterintuitive, because you might expect rounding errors to reverse themselves in the same order they crept in. Unfortunately it doesn't work that way.
You can correct for rounding error somewhat by using the round proc to adjust the loop var each time, although for performance reasons it might be preferable to find another alternative.
Only fractions whose denominators are powers of 2 are immune to this rounding error, so 0.5 is in fact stored as an exact value.
Another place floating point may lose accuracy is when you try to add numbers of very different sizes. For instance as stated above, the upper limit for accurate integers is 16777216. If you try to use a number such as 100 million it will only be approximate, so adding 1 to that number won't actually change it because the 1 is so much smaller, it will be gobbled up by rounding error.
Also for the same reasons stated above, division will cost you accuracy. Again you can divide by powers of 2 easily enough, and you can divide an integer by any of its factors (like dividing 9 by 3) without a problem, but a fraction like 1/3 will repeat forever so it gets rounded to as much precision as floating point can manage.
In decimal, floating point numbers have at least six decimal digits of precision. Since they're actually stored in binary, their true precision is exactly 24 bits.
A particle set is a special effect, whose computations are handled entirely on the client, that spawns and tracks multiple pixels or icons with a temporary lifespan. Examples of this might be confetti, sparks, rocket exhaust, or rain or snow. Particles are rendered on a special surface and that gets attached to an obj or a mob like an overlay.
Particles can exist in 3 dimensions instead of the usual 2, so a particle's position, velocity, and other values may have a z coordinate. To make use of this z coordinate, you can use a projection matrix. (The value of the z coordinate must be between -100 and 100 after projection. Otherwise it's not guaranteed the particle will be displayed.)
To create a particle set, use new to create a new /particles datum, and then you can set the datum's vars. The vars can be set to constant values, or generator functions that will allow the client to choose from a range of values when spawning those particles. (The easiest way to handle this is to create your own type that inherits from /particles, and set up the parameters you'll want at compile-time.)
After the datum is created, it can be assigned to an obj or mob using their particles var. The particles will appear on the map wherever that obj or mob appears.
These are the vars that can be used in a particle set. "Tick" refers to a BYOND standard tick of 0.1s.
Particle vars that affect the entire set (generators are not allowed for these) | ||
---|---|---|
Var | Type | Description |
width | num | Size of particle image in pixels |
height | ||
count | num | Maximum particle count |
spawning | num | Number of particles to spawn per tick (can be fractional) |
bound1 | vector | Minimum particle position in x,y,z space; defaults to list(-1000,-1000,-1000) |
bound2 | vector | Maximum particle position in x,y,z space; defaults to list(1000,1000,1000) |
gravity | vector | Constant acceleration applied to all particles in this set (pixels per squared tick) |
gradient | color gradient | Color gradient used, if any |
transform | matrix | Transform done to all particles, if any (can be higher than 2D) |
Vars that apply when a particle spawns | ||
lifespan | num | Maximum life of the particle, in ticks |
fade | num | Fade-out time at end of lifespan, in ticks |
fadein | num | Fade-in time, in ticks |
icon | icon | Icon to use, if any; no icon means this particle will be a dot Can be assigned a weighted list of icon files, to choose an icon at random |
icon_state | text | Icon state to use, if any Can be assigned a weighted list of strings, to choose an icon at random |
color | num or color | Particle color (not a color matrix); can be a number if a gradient is used |
color_change | num | Color change per tick; only applies if gradient is used |
position | num | x,y,z position, from center in pixels |
velocity | num | x,y,z velocity, in pixels |
scale | vector (2D) | Scale applied to icon, if used; defaults to list(1,1) |
grow | num | Change in scale per tick; defaults to list(0,0) |
rotation | num | Angle of rotation (clockwise); applies only if using an icon |
spin | num | Change in rotation per tick |
friction | num | Amount of velocity to shed (0 to 1) per tick, also applied to acceleration from drift |
Vars that are evalulated every tick | ||
drift | vector | Added acceleration every tick; e.g. a circle or sphere generator can be applied to produce snow or ember effects |
The icon and icon_state values are special in that they can't be assigned a generator, but they can be assigned a constant icon or string, respectively, or a list of possible values to choose from like so:
The list used can either be a simple list, or it can contain weights as shown above.
Changing a var on a particle datum will make changes to future particles. For instance, you can set the datum's spawning var to 0 to make it stop creating new particles. (Note: If you are changing a vector or color matrix, such as gravity, you need to assign a new value. You can't for instance set particles.gravity[2] = 0 because it won't do anything to update the particle stream.)
The same particle datum can be assigned to more than one movable atom. However the particles displayed by each atom will be different.
Pixel movement is a concept that allows atoms to escape the constraints of BYOND's historically tile-based movement, and move in smaller steps. In the past this had to be done with soft code, but that was sometimes inconvenient and it did not perform as well in projects with many objects moving.
The key to understanding pixel movement is to use the bound and step vars. You use the bound family of vars to define a bounding box for a movable atom, instead of just making it one full tile in size. The step vars can give it a movement speed and offset it from the corner of the tile it's standing on.
Those are for movable atoms only; they do not apply to turfs.
If world.movement_mode is set to TILED_MOVEMENT_MODE, all movable atoms must be aligned to the tile grid: their step_x/y/size values must be multiples of the icon size, and their bounds must also land on tile boundaries although the atom can be bigger than one tile. In other movement modes you can specify that only specific atoms use this behavior, by giving them the TILE_MOVER appearance flag.
As an example, if your players' mobs have icons that only cover the center 24×24 pixels of a regular 32×32 icon, then you would set the mobs' bound_x and bound_y to 4--because there are 4 pixels unused to the left and bottom--and bound_width and bound_height to 24.
The mob's physical location on the map depends on four things: Its loc, its step_x/y values, its bound_x/y values, and its bound_width/height. The lower left corner of the bounding box, relative to the turf the mob is actually standing on, begins at step_x+bound_x on the left and step_y+bound_y on the bottom.
The physical position of the bounding box is not affected by the pixel_x/y/z vars. Those are still strictly visual offsets.
The turfs the mob is covering can be read from the read-only locs var. The mob will also appear in the contents of those turfs.
Note: This means if an atom is in a turf's contents, its loc is not necessarily that turf. The contents list is made to include "overhangers" from another tile for ease of use.
All of the step and walk procs have been upgraded to take an additional argument, which is the speed at which the atom should move. If that argument is left out, the atom's own step_size is used by default. The step_size determines how fast the step_x and step_y values will change when moving.
Move() has two new arguments that handle the position change gracefully. These are the step_x and step_y values for the target location.
Pixel movement changes the behavior of the Move() proc, because a lot of things are possible that were not possible when BYOND only supported moving one tile at a time. For starters, a Move() is either a "slide" or a "jump" depending on the distance. A slide is when the move can be stopped partway; a jump is strictly pass/fail. Anything greater than one tile and the mover's regular step_size is considered a jump. Changing z levels is also a jump, as is moving to/from a non-turf.
If step_x and step_y aren't within a good range, the new loc and the step_x/y values may be changed so that the southwest corner of the mover's bounding box is standing on its actual loc, or as close to it as possible.
Enter() and Exit() can be called for several turfs and/or areas, not just one at a time. It is also possible for them not to be called at all, if the moving atom moves within a turf but doesn't cross a new turf boundary. Enter() and Exit() are only called when first attempting to enter or fully exit. The behavior of these procs depends on world.movement_mode; in legacy mode, they look at some of the contents of the turfs as well as the turfs themselves, to preserve behavior found in older BYOND versions.
Cross() and Uncross() are the equivalent of Enter() and Exit() but apply to objects the mover will either overlap or stop overlapping. (For turfs, Enter() and Exit() call these procs by default, since the mover is both stepping into and onto a turf.) Likewise Crossed() and Uncrossed() are the equivalents of Entered() and Exited().
If an atom is sliding, its movement can be halted if it encounters an obstacle partway along its route. Bump() will still be called for any obstacles the atom runs into, but Move() will return the number of pixels moved (the most in any direction). When sliding at a speed so fast that the distance is bigger than the atom itself, the move will be split up into several smaller slides to avoid skipping over any obstacles.
Gliding, which is used to show smooth movement between atoms in tile movement, is mostly not used in pixel movement. It only applies when the client uses a higher fps than the server.
The bounds() and obounds() procs have been added to grab a list of atoms within a given bounding box. That box can be relative to an atom, or in absolute coordinates.
bounds_dist() tells the distance between two atoms, in pixels. If it is positive, that is the minimum distance the atoms would have to traverse to be touching. At 0, they are touching but not in collision. A negative value means the two atoms are in collision.
Note: Currently this feature applies only to particle effects, using the transform var.
Normally icons in BYOND can only be transformed in 2D, using a simple 3x3 matrix. This is represented by the /matrix object, which cuts off the last column because it isn't used. However particles can have coordinates in x, y, and z, and the whole particle set can be given a transformation matrix that handles all three dimensions.
The easiest transformation for particles is a simple 2D one, which you can do by setting the particle datum's transform var to a /matrix object.
a d 0 x y 1 * b e 0 = x' y' 1 c f 1
When an x,y point is multiplied by the matrix, it becomes the new point x',y'. This is equivalent to:
x' = a*x + b*y + c y' = d*x + e*y + f
This is called an affine transform because all the operations are "linear" in math terms. (That is, every term in the formula above has a single variable, not raised to a higher power than 1.)
3D affine transforms of this type are also affine transformations. There is no special object for this so a list is used (see below).
xx xy xz 0 x y z 1 * yx yy yz 0 = x' y' z' 1 zx zy zz 0 cx cy cz 1
The way to read the vars above is that the first letter says what input component is being transformed (x,y,z, or c for "constant"), and the second letter is the output component.
x' = xx*x + yx*y + zx*z + cx y' = xy*x + yy*y + zy*z + cy z' = xz*x + yz*y + zz*z + cz
To use this kind of matrix, you can cut off the 4th column and provide the values in a list form, in row-major order:
Note the 4th row is also optional.
This is the most interesting matrix, since if you use all 4 columns you're actually altering an "axis" called w. This isn't a real axis, but is just a number that the resulting vector will be divided by.
xx xy xz xw x y z 1 * yx yy yz yw = x'w' y'w' z'w' w' zx zy zz zw wx wy wz ww w' = xw*x + yw*y + zw*z + ww x' = (xx*x + yx*y + zx*z + wx) / w' y' = (xy*x + yy*y + zy*z + wy) / w' z' = (xz*x + yz*y + zz*z + wz) / w'
In a regular affine transform, w always stays at 1. In projection you can think of w as a distance from the "camera". 1 is where objects are their "normal" size. If you make the z value affect w' by setting zw, you basically make an object look smaller at higher z values.
This is a simple projection matrix where x,y,z are left untouched, but there's a projection effect. The "D" value is how far away the "camera" is from z=0, so a point at z=D looks like it's twice as far away.
1 0 0 0 0 1 0 0 0 0 1 1/D 0 0 0 1
This 4x4 matrix is handled as a list just like the 3x4 affine matrix:
Regular expressions are patterns that can be searched for within a text string, instead of searching for an exact match to a known piece of text. They are much more versatile for find and replace operations, and therefore useful for parsing, filtering, etc.
Some example regular expressions are:
Pattern | Code | Meaning |
---|---|---|
B.*D | regex("B.*D") | Find B, followed by any number of characters (including none), followed by a D. |
[0-3] | regex(@"[0-3]") | Find any digit from 0 to 3 |
foo|bar | regex("foo|bar","i") | Find foo or bar, case-insensitive |
\d+ | regex(@"\d+","g") | Find all sequences of digits |
These are some of the patterns you can use. If you want to use any of the operators as an actual character, it must be escaped with a backslash.
It is highly recommended that you use raw strings
like @"..."
for your regular expression patterns, because with a
regular DM string you have to escape all backslash \
and open
bracket [
characters, which will make your regular expression
much harder for you to read. It's easier to write @"[\d]\n"
than
"\[\\d]\\n"
.
Pattern | Matches |
---|---|
a|b | a or b |
. | Any character (except a line break) |
^ | Beginning of text; or line if m flag is used |
$ | End of text; or line if m flag is used |
\A | Beginning of text |
\Z | End of text |
[chars] | Any character between the brackets. Ranges can be specified with a hyphen, like 0-9. Character classes like \d and \s can also be used (see below). |
[^chars] | Any character NOT matching the ones between the brackets. |
\b | Word break |
\B | Word non-break |
(pattern) | Capturing group: the pattern must match, and its contents will be captured in the group list. |
(?:pattern) | Non-capturing group: Match the pattern, but do not capture its contents. |
\1 through \9 | Backreference; \N is whatever was captured in the Nth capturing group. |
Modifiers | |
Modifiers are "greedy" by default, looking for the longest match possible. When following a word, they only apply to the last character. | |
a* | Match a zero or more times |
a+ | Match a one or more times |
a? | Match a zero or one time |
a{n} | Match a, exactly n times |
a{n,} | Match a, n or more times |
a{n,m} | Match a, n to m times |
modifier? | Make the previous modifier non-greedy (match as little as possible) |
Escape codes and character classes | |
\xNN | Escape code for a single character, where NN is its hexadecimal ASCII value |
\uNNNN | Escape code for a single 16-bit Unicode character, where NNNN is its hexadecimal value |
\UNNNNNN | Escape code for a single 21-bit Unicode character, where NNNNNN is its hexadecimal value |
\d | Any digit 0 through 9 |
\D | Any character except a digit or line break |
\l | Any letter A through Z, case-insensitive |
\L | Any character except a letter or line break |
\w | Any identifier character: digits, letters, or underscore |
\W | Any character except an identifier character or line break |
\s | Any space character |
\S | Any character except a space or line break |
Assertions | |
(?=pattern) | Look-ahead: Require this pattern to come next, but don't include it in the match |
(?!pattern) | Look-ahead: Require this pattern NOT to come next |
(?<=pattern) | Look-behind: Require this pattern to come before, but don't include it in the match (must be a fixed byte length) |
(?<!pattern) | Look-behind: Require this pattern NOT to come before (must be a fixed byte length) |
The optional flags can be any combination of these:
Flag | Meaning |
---|---|
i | Case-insensitive matching |
g | Global: In Find() subsequent calls will start where this left off, and in Replace() all matches are replaced. |
m | Multi-line: ^ and $ refer to the beginning and end of a line, respectively. |
After calling Find() on a /regex datum, the datum's group var will contain a list—if applicable—of any sub-patterns found with the () parentheses operator. For instance, searching the string "123" for 1(\d)(\d) will match "123", and the group var will be list("2","3"). Groups can also be used in replacement expressions; see the Replace() proc for more details.
To get the most out of BYOND's visual effects, it helps to understand how the map is displayed.
Every atom has an appearance that holds all of its visual info (and sometimes a little non-visual info). This appearance has to be turned into sprites in order to be rendered.
Although many atoms need little more than a simple icon and icon_state and produce only a single sprite, some are more complex with overlays, underlays, maptext, etc. Also there may be image objects and visual contents involved, although they're not part of the atom's appearance.
For a simple icon and icon_state, just one sprite is generated. The client looks up the icon it's given. Then it looks up an icon state, which may be influenced by whether the atom is moving or not since you can have moving and non-moving icon states. Then it determines which direction to draw and which frame of the icon's animation (if any) to use.
So with several simple icons, and not worrying about layers for now, a list of sprites lays out like this:
Now let's consider what happens when an appearance has overlays.
The underlays list is processed first, then overlays. These lists contain appearances themselves, rather than actual atoms. This means that overlays are recursive: an overlay can have overlays itself. To picture how that works, just replace one of the overlays above with another list.
Any atom can have an image object attached, which can be shown to only specific players. Most atoms, and image objects, can have visual contents that display other atoms as if they're overlays.
As you see this is very similar to overlays. Just like overlays, image objects and visual contents have appearances of their own (and may also have their own images or visual contents), so this may be recursive as they add new overlays, etc.
A couple of things to keep in mind:
Any appearance may have maptext attached. That maptext draws above the icon but is grouped with it. That grouping will be discussed further below.
Particle effects also get grouped with the main icon in a similar way to maptext.
For simplicity, from this point forward the diagram will just treat underlays, overlays, image objects, and visual contents as overlays.
An appearance's color and alpha vars (from here forwarded they'll just be referred to by color) and transform are inherited by any overlays, which also includes images and visual contents. You can avoid that inheritance by giving those overlays special appearance_flags: RESET_COLOR, RESET_ALPHA, and RESET_TRANSFORM.
The appearance's filters are only applied to the main icon.
When color and transform are inherited, they "stack". The inherited color and transform values are applied after those of the overlays.
There are times it's desirable for an appearance and all its overlays to be treated as a single unit so any colors or filters can be applied all at once. One simple example is if the appearance has an alpha of 128 to make it translucent, you probably want to draw the whole atom faded instead of drawing each sprite faded, one on top of the other.
By using the KEEP_TOGETHER value in appearance_flags (called KT for short), an appearance will group all of its underlays and overlays together. If this is an atom with image objects and visual contents, those will be grouped with it as well.
With KEEP_TOGETHER all of these sprites are rendered to a temporary drawing surface, and then the main appearance's color, transform, and filters are all applied to the combined drawing. This comes with a trade-off, since you can no longer use flags such as RESET_COLOR to opt out of inheritance.
If an overlay doesn't want to be part of a KT group, it can use the KEEP_APART flag (KA for short). If there are multiple nested KT groups, KA will only escape the innermost group.
If an overlay inside a KT group has a different plane than the group's owner, it will be separated as if it defined KEEP_APART, except it can escape multiple nested groups.
Any appearance can have a layer or plane, and these influence how it gets sorted. (There's also a concept called a "sub-plane" that's influenced by whether an atom is a HUD/screen object or special layers like BACKGROUND_LAYER.)
If a sprite is created with FLOAT_LAYER (any negative value counts as a floating layer) its layer has to be resolved, or "unfloated". The main sprite for an atom can never float; it has to have a real layer. Its overlays and underlays with floating layers will reorder themselves in numerical order, then look for the next closest sprites in the rendering list that has a non-negative layer.
A similar process happens with FLOAT_PLANE. Planes can have negative values but FLOAT_PLANE and the values close to it are special. Sprites with floating planes have to resolve those as well.
Once all atoms that will appear on the map are assembled into a rendering list of sprites, the order in which they're rendered on the map is determined in this order:
In a typical topdown map, layer is basically all that matters after the plane and subplane are taken into account. There is a legacy concept called micro-layers that helps break ties between sprites with the same layer; for instance if an atom is moving it's usually desirable to draw it above other atoms with the same layer; this applies only to topdown maps.
Sometimes it's helpful to group multiple sprites on one plane as if the plane itself were a KT group. For this, appearance_flags has a value called PLANE_MASTER. An object with this flag will act as a "parent" for everything else on the plane. All other sprites on the plane will be grouped together and rendered on a temporary drawing surface, and then the plane master's color, transform, and filters will be applied.
A plane master does not, however, get an icon or maptext of its own; they're simply ignored. It can have overlays added to the group.
There are other topics not covered in this article, such as render targets and special map formats. Any details on how those features impact rendering are discussed in their own articles.
The side-view map format is used for 3/4 perspective, where the map is basically similar to a top-down view but is usually foreshortened. Just like with isometric projection, tiles that are closer to the bottom of the screen are considered to be closer to the viewer. This is a form of pseudo-3D in which the 2D icons used by BYOND can be arranged in a way to give the appearance of three dimensions.
It is important to remember that this is an illusion of 3D, not real 3D.
The layer var behaves much the same way it does in ISOMETRIC_MAP mode.See isometric maps for more information.
When using this mode you may want to use a foreshortened world.icon_size, like a 32x24 format instead of 32x32 for example, and use taller icons for any vertical structures like walls or buildings. If you set world.icon_size to use foreshortening, then pixel_y (or pixel_x, depending on the orientation of client.dir) will be adjusted for you; the same applies to step_x and step_y. For example, with world.icon_size set to "64x32", the physical tile—what you would see if you were to look at it straight down from above— is considered to be 64x64, so you would need pixel_y=64 or step_y=64 to offset by a whole tile. This adjustment does not apply to screen objects, pixel_w, or pixel_z.
In BYOND 3.0, any file like a large .bmp would be treated like a regular icon that had been broken up into several tile-sized icon states. All tiles then were 32x32 pixels. An image that was 100x100 would therefore take at least 4x4 tiles to display. The icon was padded to the right and the top with blank space to become an even multiple of 32x32, and then broken up into sections. The lower left section was given an icon_state of "0,0", the next to the right was "1,0", and so on, up to the upper right which was "3,3". Another icon state, a 32x32 thumbnail of the big image, was also included.
BYOND 4.0 expanded on this concept by allowing icons to be defined that had individual graphics bigger than 32x32, and it would break each one up into tiles just like 3.0 did. If an icon had a state called "open" then it might break down into "open 0,0", "open 1,0", and so on, while the actual "open" state would be a thumbnail image. To show the whole image, you would have to have a separate atom or overlay for each individual tile.
In newer versions, breaking big icons into tiles is no longer done by default. Instead, icons are shown and manipulated in their native size. To use the old method of breaking icons into tiles, set world.map_format to TILED_ICON_MAP. This is the default for all projects compiled before version 455.
When using tiled icons, there are some important things to note:
This example shows a big icon being applied to an atom in tiled mode, as overlays:
By default, BYOND displays all maps in top-down format, so world.map_format is set to TOPDOWN_MAP unless you say otherwise. This view means players are looking down on the map, and "north" corresponds to the top of their screen. (This can be changed by setting client.dir.)
A related map_format, used by older games, is TILED_ICON_MAP. This is also topdown but it handles icons differently.
In this form, the layer var behaves exactly as you would expect: Icons with a lower layer are drawn beneath icons with a higher layer. The only exception is when you use big icons, which will be drawn above any other icons on the same layer. Also an atom's underlays will be drawn behind it unless their layer is changed, and its overlays will draw in front of it unless otherwise stated.
Topdown mode also guarantees that world.view or client.view will set the exact screen size used by the HUD, except for HUD objects that appear outside of the normal bounds.
Screen objects (also called the HUD) cannot be intermixed with topdown icons. They will appear on top of other icons, unless using a lower plane or a special layer like BACKGROUND_LAYER.
TOPDOWN_LAYER is a special high value that can be added to the regular layer of any atom. This is only available when using a non-topdown world.map_format, such as isometric mapping.
The purpose of this value is to make an atom appear as if it belongs in a top-down map, when using a map_format other than TOPDOWN_MAP or TILED_ICON_MAP. This can be handy for title screens, or for special battle maps or the inside of a building in an RPG.
When using this special layer, it should be added to the layer an atom normally uses. For instance a turf should have a layer of TOPDOWN_LAYER + TURF_LAYER. Usually you will want one part of the map to have TOPDOWN_LAYER, and for players to be unable to walk to there from the regular map. Mixing topdown icons and icons in the normal map_format in view of each other could look very strange. For safety's sake, the easiest thing to do is to keep them on separate z layers.
This can be mixed with EFFECTS_LAYER. Anything in TOPDOWN_LAYER will display on top of EFFECTS_LAYER, and TOPDOWN_LAYER + EFFECTS_LAYER will be above both.
This can also be mixed with BACKGROUND_LAYER, which takes priority over everything else.
Images or overlays with FLOAT_LAYER can be left alone. They will automatically have the same layer as whatever atom they are attached to.
BYOND was originally written to handle 8-bit ("ANSI") characters only. However as time has marched on, Unicode has become ubiquitous for supporting multiple languages, special characters, and emojis. To adapt to this, BYOND now supports Unicode.
When ANSI was king, every character was exactly one byte in width, because the only valid characters were between 1 and 255. (And technically, BYOND reserved 255 for its own use.) Now, BYOND uses an encoding called UTF-8 to store characters that can't fit in one byte.
UTF-8 breaks up characters with codes of 128 or higher into multiple bytes, like so:
Character code | Size in bytes |
---|---|
0 - 0x7F | 1 |
0x80 - 0x7FF | 2 |
0x800 - 0xFFFF | 3 |
0x10000 - 0x10FFFF | 4 |
Importantly, BYOND's text procs are based on the byte position, not the character position which may be lower. In other words, length("abcdéfg") is greater than 7; it's 8, because é takes up 2 bytes in UTF-8. That also means f is at position 7, not position 6.
Why do the text procs work with byte position instead of character position? Because ultimately, it's faster. Going by character position would require counting every byte in a string (at least when it uses UTF-8) until the right character position was found. This would be detrimental to performance in most cases.
For the most part, this distinction should be fairly invisible to you. Most code isn't going to encounter problems, but if you do a lot of text processing you should be aware of it.
In particular, text2ascii() returns the Unicode value at a specific position, which may cover several bytes. If you loop through a string calling this proc for each character, you'll have to make adjustments for cases when multiple bytes have been read.
The read-only [] index operator also uses byte positions.
If you read a byte or cut text at an inappropriate point, any broken characters resulting from the cut will be turned into the Unicode replacement character � which is 0xFFFD.
Most of the text handling procs have slower _char versions (e.g., copytext_char) that use character positions instead of byte positions.
These should be used sparingly if at all; whenever it's possible to use byte positions, you should. When you do use a _char version of a proc, prefer using -offset instead of length_char(text)-offset for positions near the end of the string. Most text procs allow negative position values that count backwards from the end, and counting a small number of characters backward is faster than counting a lot of characters going forward.
Code written in ANSI will be up-converted to UTF-8 by Dream Maker, based on your current locale when the code is loaded.
BYOND games used to have very limited interface options, all effectively sharing the same layout. In BYOND 4.0, skins were introduced, allowing developers more control over the layout.
A skin consists of macro sets for keyboard/gamepad input, menus, and windows and/or panes. All of these are considered controls that a game can interact with via winset(), winget(), output(), and a few other procs.
About the simplest possible skin is a single window with a single map control, and a single macro set.
Several commands can be executed on the client that are not verbs, but raw instructions for Dream Seeker.
The .winset command allows you to use conditional expressions, like so:
condition ? choice1 : choice2
The condition is the same as any other parameter you might use in .winset, but instead of setting the parameter, it checks to see if it's true. If so, then the parameters in choice1 will be set. Otherwise, the parameters in choice2 are set. This example makes the window background red if bigbutton is checked.
.winset "bigbutton.is-checked=true ? window.background-color=#ff0000 : window.background-color=none"
If you want to look for values that don't match instead of values that do, use != instead of = in the condition.
.winset "bigbutton.is-checked!=false ? window.background-color=#f00 : window.background-color=none"
The choice2 item is optional.
.winset "bigbutton.is-checked=true ? window.background-color=#f00"
Because it's often useful to do more than one thing at a time, choice1 and choice2 don't have to be just one parameter. You can use multiple parameters, but they are separated with a space instead of a semicolon. (A semicolon indicates the conditional expression is over.)
.winset "bigbutton.is-checked=true ? window.text-color=#fff window.background-color=#f00 : window.text-color=none window.background-color=none"
Commands that are initiated by the skin (like button.command, map.on-show, etc.) have a special syntax that allows you to include information that would normally require a winget call. By including [[something]] in the command, the double-bracketed text will be replaced by the result of running a winget on that parameter.
A value of [[id.parameter]] will run a winget on the control with the given ID. Just using [[parameter]] will run a winget for the control that initiated this command. You can also use parent in place of the ID to do something with the parent of the control, or parent.id for access to a sibling control. Position and size parameters can be further broken down by appending .x or .y to get at the numbers directly.
Several commands already support some special cases like [[*]] or [[width]] or such, where the special-case values are relevant to the command. An example is that in on-size the value of [[*]] is a size value. The Any macro, gamepad macros, and mouse macros, also support this syntax; see macros for more info.
You can choose how embedded wingets get formatted by following the value with as and a type, such as [[window.size as string]]. There are several types you can use, and different types of parameters get formatted differently:
The arg type is the default, unless the [[...]] expression has double quotes on both sides, in which case escaped is the default.
Controls can be created or deleted at runtime. (Only controls you created during runtime may be deleted.) To create a control, call winset() using the id of the new control, and the parameter list should include type, parent, and probably also pos, size, and any anchors.
To delete the control again, set its parent to a blank value.
Menu items and macros work similarly, except they have no positional info. For those, the name parameter is important when you create them, and you will either need command or (for macros) map-to to do anything with them.
A progress bar or interactive slider. This can be made to use several different orientations. Its value can be read or set as a percentage from 0 to 100.
A browser panel integrated into the skin.
Browsers are capable of displaying HTML documents, and can also interact with the skin.
A longstanding behavior of BYOND is the ability to create a new browser window by sending an extra argument to the browse() proc. Since the advent of skins in BYOND 4.0, this behavior was kept. When you create a new browser popup, the window name you specify for the popup is used for the name of a new window control, and within that window there will be a new browser control simply called browser.
If you want to interact with the new browser, its full "decorated" id is windowname.browser.
Sending output() to a browser will send a document to display there, but if you follow the browser's control name with a colon and a function name, you can call a JavaScript function in the document displayed within that browser.
The text that you send as output will be parsed like URL parameters, where mutliple arguments to the function are separated by & or ;, which is why url_encode() is wrapped around the json_encode() call in this example.
To allow better access to the skin via JavaScript, two new URL formats have been added. If window.location is set to these from JavaScript in a browser control, they can be used to interact directly.
Winset URL: byond://winset?id=[control ID]&[property]=[value]&...
This works like an ordinary winset() call from the server. If id is omitted, it's the same as a winset with a null ID. You can also leave the id blank if you use "fully decorated" property names such as mybutton.is-checked instead of just is-checked.
Any text you use other than letters, numbers, hyphens, commas, and periods should be encoded via the encodeURIComponent() function in JavaScript.
Winget URL: byond://winget?callback=[callback function]&id=[control ID/list]&property=[property/list]
In this winget, the IDs and properties you want can be separated by commas if you want to retrieve more than one. The winget operation works via a callback function you must define in JavaScript. The callback is given just one argument, a JavaScript object with all of the properties you requested. For example, this URL:
byond://winget?callback=wgcb&id=button1&property=is-checked,size,background-color
...might send this to the callback function wgcb:
{ "is-checked": true, "size": { "x": 60, "y": 20 }, "background-color": { "value": "none", "isDefault": true, "red": 236, "green": 233, "blue": 216, "alpha": 255, "css": "#ece9d8" } }
The property names will be in the same format you would expect from winget(), so when you're looking at multiple elements' properties, you'll get the full names in id.property format. The values are always sent back in a convenient form for JavaScript to work with; in the case of size, position, and color these will always be objects.
An optional control parameter for the winget call can be used if you want to send data to a callback in a different browser control.
A button that can be pressed to run a command, or possibly toggled.
A container that can hold one or two panes. If it holds two panes, a splitter may appear between them. This control can therefore be used to subdivide a window or pane into smaller units.
A grid that contains multiple cells that can show various kinds of output data.
Sending output to a grid looks like this:
You can output an atom to the grid, which can be clicked, dragged, etc. However, you should make sure that atom is not temporary and will persist until you no longer need it, or else the server may recycle it and the object in the cell will either disappear or be impossible to interact with anymore.
There are some limitations to output in grid controls:
The classic BYOND statpanel, which contains both stat and verb tabs. This is technically a 3-column grid with a variable number of rows.
Output to a statpanel is done via the stat() and statpanel() procs, during mob/Stat().
The same limitations that apply to grid output apply here.
A text box into which the user can type. By default this is used for sending commands, but it can be used for other purposes as well.
Note that when in "standard" mode of accepting user commands, built-in verbs like .click, or local commands like .winset, are not accepted when typed in. This kind of command can still be entered manually through the Client menu of the Options & Messages window.
A text label that appears on the skin.
A keyboard/gamepad/mouse macro, usually designed to run a command. The control is a means of interacting with the macro as an object, allowing some of its properties to be changed at runtime.
A container for other controls. The Main control takes two forms: a window or a pane.
A window exists independently and can be moved around on the screen. A pane has to be used within another container control such as a Child or Tab control.
The font parameters have no impact on a window's statusbar or titlebar; those are drawn by the operating system.
A map that will display icons from the game.
A menu item, that when activate will run a command.
Displays text output.
A tab control, where each tab holds a different pane.
Macros are used to convert keyboard and gamepad events into actions. There are two ways this works: A macro can run a command, or in some cases (such as gamepad controls) it can be used to remap one control to another.
A collection of macros is called a macro set, and the window currently in use defines which macro set will be used via its macro parameter.
Macros can be changed at runtime. If a macro does not have an id, you can refer to it by its key combination (name). If you have a macro set named macro1 and have a Ctrl+E macro for instance, you could use winset() with "macro1.Ctrl+E". See the Macro control for information on which parameters you can change with winset().
The name of the macro is actually the full key combination as it would appear in the macro editor, like CTRL+E, Space+REP, or Alt+Shift+F1. This is not case-specific and it doesn't matter where you put modifiers like CTRL+, SHIFT+, etc.
Oftentimes it's desirable to keep track of key presses yourself rather than have a hundred different macros defined. BYOND makes this possible via the Any and Any+UP macros, which respond to any key or gamepad button. UP is the only allowed modifier for this macro, since other modifier keys are handled by this same macro.
Typically, you will want to use set instant=1 on the verbs that will be tied to the Any macro, so that keyboard input doesn't queue up and lag behind.
In the command that goes with this macro, [[*]] will be replaced with the name of the key or gamepad button that was pressed/released. (See "Embedded Winget" in client commands for more details on the [[...]] format.)
The map-to parameter is used by mappings, which are like macros but are used to convert gamepad inputs easily and quickly to keyboard inputs. E.g., GamepadLeft can map to West which is the left arrow key. A set of default mappings will be added automatically at runtime if you don't include any gamepad mapping in your project.
BYOND will support up to four gamepads, and breaks up their input into the following categories:
See the list of available macros below for information on how to harness these inputs.
To let a user configure their gamepad, you need to call the client-side .gamepad-mapping command. Or, if they have access to the Options & Messages window and Dream Seeker's default menus, they can reach it from there. However it's a good idea to make this easy for them to find. Several common gamepads are already known by BYOND.
There is also the GamepadRaw macro, which is similar to Any in some ways and will avoid doing any processing (e.g. checking for dead zones on the analog sticks) so you can handle all input yourself. GamepadRaw does not rely on BYOND's controller configuration, so it will not, for instance, know that button 0 should be GamepadFace1. See below for more information on how to use this macro.
You can add macros (not local player-defined ones) for any of the mouse input commands, thereby bypassing the normal mouse verbs. This can be helpful for designing custom setups where you don't want to have to parse the normal parameter string that provides most of the info, and instead want to provide data directly to the verb. You will want set instant=1 on any such verb.
Mouse macro commands use the [[...]] syntax to embed values, just like embedded wingets. These are the values you can include in a mouse macro:
Embedded keyword | Meaning |
---|---|
action | Name of the mouse action (e.g. MouseDown, MouseMove, etc.). |
src | Object the mouse is touching, or dragging/dropping. |
loc | Turf or statpanel that src is over; in a drag-drop you should split this into src.loc and over.loc. |
button | Mouse button used for this action, if any: left, middle, or right. |
drag | Mouse button currently used for dragging. |
buttons | Mouse buttons currently down or involved in this action, separated by commas. |
keys | Modifier keys currently held (shift, ctrl, alt), separated by commas. |
over | Object the mouse is over in a drag/drop operation. |
id | Control ID; in a drag-drop you should split this into src.id and over.id. |
icon | The icon offset (starting from 1,1 at the lower left) where the mouse action occurred.* |
tile | The tile where the mouse action occurred, if relevant.* |
vis | Pixel coordinates relative to the icon's position on screen (same as icon but without taking transform into account).* |
screen_loc | The regular screen_loc cordinate string.* |
screen | screen_loc coordinates but entirely in pixels starting at 0,0 from lower left.* |
screen_tile | screen_loc coordinates but only the tile number starting at 1,1.* |
screen_offset | screen_loc coordinates but only the pixel offset from the tile, starting at 0,0.* |
delta | Wheel changes in a mouse wheel command.* |
left, right, middle | 1 if this button is down or involved in this action, 0 otherwise |
shift, ctrl, alt | 1 if this modifier key is held, 0 otherwise |
link | 1 if the mouse is over a maptext link, 0 otherwise |
cell | Grid cell involved in a mouse action. In a drag/drop action, src.cell is the dragging cell and over.cell is the drop cell. |
drag-cell | Alias for src.cell. |
drop-cell | Alias for over.cell. |
* Coordinate values are comma-separated, but you can follow them with .x or .y to get the individual X and Y numbers. |
An example mouse macro command might look like this:
my-mousedown-verb [[src]] [[button]] "keys=[[keys as params]];drag=[[drag as params]]"
And the verb to go with it looks like this:
In the example, the src value is a reference such as you would get with the ref() proc. It can be used as a verb argument directly and won't be enclosed by quotes by default. The button value is a string and the default formatting will put quotes around it. The keys and drag values were given the as params format specifier so they would behave as part of a parameter list.
In drag/drop actions, you can precede any value with src or over if there may be different information for the dragged object and the mouseover object/location. This also applies to things like keys, which by default will be the currently held keys but you can use src.keys to refer to the values from when the drag began.
This is a list of all keys and gamepad events that can be used in macros.
Macro modifiers are part of the macro name, and control the conditions in which the macro will fire. | |
Modifier | Meaning |
---|---|
SHIFT+ | This macro only counts if either Shift key is pressed. |
CTRL+ | This macro only counts if either Ctrl key is pressed. |
ALT+ | This macro only counts if either Alt key is pressed. |
+REP | If a key/button is held down, this macro repeats. |
+UP | This macro fires when the key/button is released. |
Keyboard keys are the garden-variety macros. (This list is abridged to exclude keys probably no one has.) | |
Key | Description |
A - Z | Letter key |
0 - 9 | Number key |
Numpad0 - Numpad9 | Numpad numbers |
North | Up arrow |
South | Down arrow |
East | Right arrow |
West | Left arrow |
Northwest | Home key |
Southwest | End key |
Northeast | Page Up key |
Southeast | Page Down key |
Center | Center key (numpad) |
Return | Enter / Return key |
Escape | Esc key |
Tab | Tab key |
Space | Space bar |
Back | Backspace key |
Insert | Ins key |
Delete | Del key |
Pause | Pause key |
Snapshot | Snapshot / Print Screen key |
LWin | Left Windows key |
RWin | Right Windows key |
Apps | Apps key |
Multiply | Multiply key |
Add | Add key |
Subtract | Subtract key |
Divide | Divide / Slash key |
Separator | Separator / Backslash key |
Shift | Shift key (when not used as a modifier) |
Ctrl | Ctrl key (when not used as a modifier) |
Alt | Alt key (when not used as a modifier) |
VolumeMute | Mute key |
VolumeUp | Volume up key |
VolumeDown | Volume down key |
MediaPlayPause | Play/pause media key |
MediaStop | Stop media key |
MediaNext | Next track key |
MediaPrev | Previous track key |
Special macros | |
Any | A special macro that can run a command on press/release of any key or gamepad button. UP is the only modifier allowed. In the command, [[*]] is replaced with the key/button name.* |
GamepadRaw* | Captures raw input from a gamepad, without regard to the adjustments done by the Gamepad Setup dialog. In the command, [[id]] is replaced by the name of the button or axis changed ("Button0" through "Button15" and "Axis0" through "Axis11"), [[value]] is replaced with the value of the button or axis, and [[*]] is equivalent to [[id]] [[value]]. |
* If no gamepad mappings are included in a game's interface, the default mappings are used instead, which will map the Dpad buttons to the arrow keys. This will cause the Any macro to register both a gamepad directional button and the mapped key on the same press. If you plan on using macros to capture gamepad input, you may wish instead to map any one of the directional buttons to "None", which will override the default gamepad mappings completely. | |
Gamepad buttons† can use another gamepad button as a modifier (but not CTRL, SHIFT, ALT), and can be mapped to one or two keyboard keys or mouse buttons. | |
Button | Description |
GamepadFace1 | A (Xbox), X (PS), bottom of diamond |
GamepadFace2 | B (Xbox), Circle (PS), right of diamond |
GamepadFace3 | X (Xbox), Square (PS), left of diamond |
GamepadFace4 | Y (Xbox), Triangle (PS), top of diamond |
GamepadL1 | Left top shoulder |
GamepadR1 | Right top shoulder |
GamepadL2 | Left bottom shoulder |
GamepadR2 | Right bottom shoulder |
GamepadSelect | Select / Back |
GamepadStart | Start / Forward |
GamepadL3 | Left analog click |
GamepadR3 | Right analog click |
Directional buttons: only one can pressed at a time, and the diagonal buttons are virtual. | |
GamepadUp | Up button |
GamepadDown | Down button |
GamepadLeft | Left button |
GamepadRight | Right button |
GamepadUpLeft | Up+left virtual button |
GamepadUpRight | Up+right virtual button |
GamepadDownLeft | Down+left virtual button |
GamepadDownRight | Down+right virtual button |
Gamepad analog sticks† can have commands and/or map to GamepadDir, GamepadDir4, or Mouse. They can use a gamepad button as a modifier. In a command, [[x]] and [[y]] are replaced by coordinates, and [[*]] is replaced by both with a comma for separation. | |
GamepadLeftAnalog | Left analog stick |
GamepadRightAnalog | Left analog stick |
Gamepad Dpads†‡ can have commands or are used as mapping targets for analog sticks. A gamepad button can be used as a modifier. In a command, [[*]] is replaced by a direction number, which can be 0. | |
GamepadDir | Dpad, converted to one of the eight standard directions. |
GamepadDir4 | Dpad, converted to a cardinal direction. |
† All of the gamepad macros defined above apply to the first gamepad. BYOND can now support up to four gamepads, and you can replace Gamepad in the names above with Gamepad2, Gamepad3, or Gamepad4 to access them. Each gamepad also has its own raw macro (i.e., Gamepad2Raw). ‡ If you use a Dpad macro like GamepadDir as a map-to target, you don't have to specify gamepad 2-4 in map-to; the mapping will automatically know that when Gamepad2LeftAnalog is mapped to GamepadDir, it means Gamepad2Dir. | |
Mouse macros can have commands but not be used as mapping targets. | |
MouseDown | Mouse button pressed (replaces MouseDown verb) |
MouseUp | Mouse button released (replaces MouseUp verb) |
MouseClick | A click action has occurred (replaces Click verb) |
MouseDblClick | A double-click action has occurred (replaces DblClick verb) |
MouseOver | Mouse has moved over a new icon or entered/exited a control (replaces MouseEntered and MouseExited verbs) |
MouseMove | Mouse has moved to a new pixel of the same icon (replaces MouseMove verb) |
MouseDrag | Mouse has begin dragging or is over a new drop target (replaces MouseDrag verb) |
MouseDragMove | Mouse is dragging and is over a new pixel of the same drop target (replaces MouseDrag verb in situations where MouseMove would apply) |
MouseDrop | Mouse drag has been released over a target (replaces MouseDrop verb) |
MouseWheel | A wheel movement has occurred (replaces MouseWheel verb) |
Mouse targets can only be used as mapping targets for another macro. | |
Mouse | The mouse cursor, mappable by a gamepad analog stick. |
MouseLeftButton | Left button, mappable by a gamepad button. |
MouseRightButton | Right button, mappable by a gamepad button. |
MouseMiddleButton | Middle button, mappable by a gamepad button. |
Controls can be interacted with via winset() and winget() to change or read various parameters.
Parameters come in a few different formats:
The list of all controls which shows which parameters are universal, and each individual control type lists additional parameters that apply to that type specifically.
Note: In any parameter's "Applies to" section, "all" refers to positionable controls only, not Macro or Menu controls. Macro and Menu will be listed separately if supported.
Default alignment of text/image, both horizontal and vertical.
A BYOND direction flag like WEST may be assigned to this parameter, or 0 for center alignment.
Info control: Allow HTML tags to be used in stat() info. The same limitations apply as to the Grid control.
Label control: Currently, the label control will not actually use the HTML; it will simply strip it out. Full support may appear in a later version.
Opacity of the window, from 0 (invisible) to 255 (opaque).
Anchors the control within the window or pane. If the anchor is not none, it is expressed as pecentages of the container's width and height. For example, an anchor of 100,100 means that the X and Y position are tied to the lower right of the container, and 50,0 is tied to the top center.
Setting only anchor1 will control the position of the control but won't affect its size.
Setting anchor2 as well will allow you to stretch the control as the container's size changes. You can think of this anchor1 controlling the top left corner, and anchor2 the bottom right corner.
The angle of the bar control's arc when its dir is clockwise or counterclockwise. Angles are measured clockwise from due north, so 0 is north, 90 is east, and so on. angle1 is the beginning of the arc, and angle2 is the end.
When true, the browser control will inject conditional scripting into HTML documents to make them behave nicer in very old browsers. However, it is unlikely there are any systems left that need this.
The control's background color. The exact way this applies depends on the control.
The color of the bar or slider.
Border type around the control or window. May not work the same in all controls.
Changes the type of button.
If true, this menu item is toggled like a checkbox or radio button when clicked.
Allow the window to be closed, and also shows a system menu for the window.
Allow the window to be minimized.
Allow the window to be resized or maximized.
If is-fullscreen is true, can-resize is ignored, so this value represents the state of the window when is-fullscreen is turned off again.
Allow this pane to retain its horizontal and/or vertical size and show scrollbars if necessary, instead of shrinking to fit the container.
Command executed when this control is activated.
For the Input control, whatever the user types in follows this command. If your command starts with an exclamation point !, everything after the ! is shown as a default prompt that may be cleared by the user.
The span of the current grid cell; it can be merged with cells to the right and down. If is-list is true, this setting is ignored. This setting is only available at runtime.
The number of columns and rows in the grid. Using -1 for either columns or rows will leave that value unchanged.
If is-list is true, this value can be set to a single number.
The active cell. Any output sent to the grid, that is not sent to a specific cell, will go into this cell.
If is-list is true, this value can be set to a single number.
The name of the pane in the active/default tab. If set to a pane that is not currently in this tab control, the pane by that name will be added as another tab.
The direction/orientation of the bar. As the value increases the bar will move further in this direction.
Shorthand values like cw and ccw can be used, or also numerical BYOND directions.
True if dragged objects may be dropped here. Default is true for Map, Info, and Grid controls, false for others. When in use, this will be the value of the over_control argument in MouseDrop() if you drop an atom here.
Grids can also add drag-cell and drop-cell to mouse proc parameters. The mouse procs' src_location and over_location arguments are in the form "[column],[row]" (or "[item"] if is-list is true) when dragging to/from a grid cell.
In Info controls, src_location and over_location in mouse procs will be the name of the statpanel tab.
Allows images to be pulled from the Web when using the <img> tag; otherwise only locally stored images can be shown.
Set to a positive number to make the window flash that many times, -1 to flash forever, and 0 to stop flashing.
This parameter is true if this control currently has focus.
This is also a special read-only global parameter. Calling winget() with no id and focus as the parameter will return the id of the currently focused control, if any.
Leave blank to use the default font. This can be used for CSS-style fallback fonts, e.g. "Arial,Helvetica".
You can include fonts in your resource file, making them available to the client, like so:
Point size of the font, or leave at 0 for the default size.
The Output control behaves differently for legacy reasons, unless legacy-size is false.
Sets the font style. Any combination of the above values may be used, or none of them. Multiple values may be separated by spaces or commas.
Used for "radio" buttons and menu items, where only one of them in the same group may be checked at a time. This value is a text string, or may be left empty.
Buttons in different windows/panes, or menu items in another menu/submenu, are always treated as a different group.
The color used to highlight moused-over statpanel items or verbs. In grids, this color is used when hovering over objects or links.
Custom icon used for the window. If no icon is specified, the Dream Seeker icon is used by windows by default.
If this control is a pane, its icon will appear on the tab if the pane is inside a tab control. Lack of an icon will mean no icon appears in the tab.
Note: The Windows .ico format is not used. Only image formats BYOND can already use are supported.
Size, in pixels, of icons on the map. A size of 0 stretches to fit available space.
This parameter has been deprecated. Use zoom instead.
The name of this control. Read-only.
If this is a Main control, the name should always be unique. For others, it is usually still a good idea to use a unique name, but they can be referenced by window.id at runtime.
You can use a colon in front of the type to refer to the default control of a certain type, if one exists, e.g. :map is the default map.
A background image to show in this control.
In the Output control this image is always tiled.
Note: Icons displayed in the output control will not show the background image underneath their transparent parts, but will instead show the background color.
For Label and Main, use image-mode to control how the image is displayed.
Determines how the background image is displayed.
Moves the menu item to the Nth position among its siblings. 0 or less is no change. Write-only.
Read-only.
Reads the position where the window's interior contents begin (i.e., not counting titlebar, statusbar, borders, etc.), relative to its outer-pos.
Read-only.
If the control is a window, this refers to its current interior size: i.e., not counting titlebar, statusbar, borders, etc. If it's maximized, this will be the true size of the window interior, as opposed to size which is the interior size once this window is no longer maximized.
If this control is a pane and can-scroll is true, this is the size of the display area not including the scrollbars.
True if the button or menu item is checked. Menu items can set this even if can-check is false.
Specifies that this is a default control. This should be true for your main window, and for your primary map, info, output, input, and browser controls.
The default control of a given type can be referenced in winset() and other skin-related procs by the name ":type", e.g. ":map".
Changing this value at runtime should be avoided, especially for windows. Results may be unpredictable.
Disables the control, menu item, or macro.
Gives this button a flat appearance instead of pseudo-3D highlights.
True if the window should be in fullscreen mode. This suppresses can-resize, titlebar, is-maximized, and is-minimized. They will continue to return the values that would apply if fullscreen mode were turned off.
True if the grid is used for a flexible list of items; the number of columns and rows may change to fit them.
True if the window is maximized.
If is-fullscreen is true, this value represents the state of the window when is-fullscreen is turned off again.
True if the window is minimized.
If is-fullscreen is true, this value represents the state of the window when is-fullscreen is turned off again.
True if this is a pane that will be used in other container controls, instead of an independent window. Read-only.
Hide text with asterisks. Copy to clipboard is not available in this mode, but the text parameter can still read the control's contents.
Note: For obvious reasons, you should never use the same password in a game that you would use anywhere else.
Make this an adjustable slider capable of being changed by the user, instead of a progress bar.
Make this control transparent.
Transparency support is extremely limited. Only some controls can actually use it, and only when on top of certain other controls.
Bars and labels handle transparency reasonably well, when not on top of other controls (or only on top of other conrols of these types).
The splitter between the two panes in this control is vertical.
True if this control can be seen. The main window should usually be made visible.
If stretching a background image, preserve its aspect ratio.
The id of the left/top pane in this control. The parameter names left and top can be used interchangeably.
When true, font sizes are scaled slightly larger for readability, which is legacy (and default) BYOND behavior. Set to false for exact font sizing.
If map auto-scales its icons (zoom is 0), make sure the entire map fits, and fill excess space with the background color.
If letterbox is not enabled, auto-zoom will fill all available space, and any excess will be cut off.
The color of grid lines.
The color used for links. In some controls visited links may have a different color.
Allows one pane to "lock" the splitter so if this Child control is resized, the splitter will stay put on that side.
The id of the macro set this window will use, if any, when it's active.
The macro name (e.g., "SOUTH") of a key combo, Dpad, mouse button, etc. that this macro maps to.
Maximum number of lines before the control drops old text to make room for more. 0 is no limit.
An overflow of 5% is allowed, to reduce flicker.
The id of the menu this window will use, if any, when it's active.
Input control: Create a multi-line input control. Read-only for this control.
Info and Tab controls: Show tabs in multiple rows if there are too many to fit in a single row.
Macro control: The key/gamepad combination such as R+REP, CTRL+Northwest, GamepadLeft.
Menu control: This is the menu item label. A tab character can be used between the name and a keyboard shortcut, like "Help\tF1". (Keyboard shortcuts must be implemented as macros in order to work. This is just a label.) A blank name shows just a separator.
True if this input control is for typing only; hitting Enter will not run a command.
Command executed when the control loses focus.
Command executed when the window is closed.
Command executed when the value of the bar/slider is changed. If you drag the slider around, the command will not run until you let go.
If you include [[*]] in the command, it will be replaced by the control's new value. (See "Embedded Winget" in client commands for more details on the [[...]] format.)
Command executed when the control gains focus.
Commandexecuted when this control is hidden by the game. Must be the default control for the game to show/hide it.
Currently not editable in Dream Maker.
Command executed when this control is shown by the game. Must be the default control for the game to show/hide it.
Currently not editable in Dream Maker.
Command executed when this control is resized. If you are dragging a window edge or splitter, the command won't run until you finish.
No command will be sent in response to size or splitter changes made by winset().
If you include [[*]] in the command, it will be replaced by the control's new size. Likewise, [[width]] will be replaced with the width and [[height]] with the height. (See "Embedded Winget" in client commands for more details on the [[...]] format.)
Command executed when the text that would go in the statusbar is changed. This applies even if this control is a pane and not a window, or is a window without a statusbar. It applies to all panes and windows that directly or indirectly contain whatever control generated the statusbar text (e.g., a map).
If you include [[*]] in the command, it will be replaced by the new text. (See "Embedded Winget" in client commands for more details on the [[...]] format.)
[[from]] can be used to reference the control (if any) that generated the next text. You can also use expressions like [[from.type]], [[from.parent.pos.x]], etc.
Command executed when the current tab is changed.
If you include [[*]] in the command, it will be replaced by the new tab's id. (See "Embedded Winget" in client commands for more details on the [[...]] format.)
Read-only.
Reads the control's current exterior position including titlebar, statusbar, borders, etc. If the window is not minimized or maximized, this is identical to pos.
Read-only.
If the control is a window, this refers to its current exterior size including titlebar, statusbar, borders, etc. If the window is maximized, this is the maximized size.
If this control is a pane and can-scroll is true, this is the size of the display area including the scrollbars.
The id of this control's parent. Write-only, used when creating a new control at runtime or deleting a control that was created this way.
Position of this control's upper left corner, relative to its container. (Not applicable to panes.)
The color used for the prefix/header column of statpanel displays. No color means the default text-color will be used.
In BYOND 3.0, this color was red.
The id of the right/bottom pane in this control. The parameter names top and bottom can be used interchangeably.
True if this control should allow right-clicks to behave like any other click instead of opening up popup menus or similar special behavior.
A semicolon-separated list of parameters that get saved with this control. This is often used for things a user might set, like zoom level for a map.
Currently not editable in Dream Maker.
Read-only.
For windows, this is the upper left corner of the nearest monitor's area.
Read-only.
For windows, this is the size of the nearest monitor's area.
The size of this control.
Setting 0 for width or height uses up any available space right/downward.
If the control is a window, this refers to its interior size when not maximized or minimized. That is, it does not count borders, titlebar, menu, or statusbar, and if the window is minimized/maximized, this refers to the window's normal size when it is restored. See the inner-size and outer-size params for comparison.
If this control is a pane and can-scroll is true, size refers to the total scrollable size of the pane, NOT the smaller size displayed. In this case, outer-size and inner-size refer to the display area with and without scrollbars, respectively.
Show forward/back navigation buttons.
Determines which grid lines to display.
When atoms are output to the grid, show the atom's name next to its icon.
If the atom has no icon and show-names is false, the grid cell will be blank.
Show a splitter if both the left and right (or top and bottom) panes are in use. The splitter can be dragged to resize the panes.
Shows an address bar for this browser control.
When output(object,grid) is sent, show smaller icons in this control instead of larger ones.
Position of the splitter when two panes are in use, whether show-splitter is true or not. This value is a percentage. Specifically, it is the percentage of the available width/height that is given to the left/top pane.
The color used for the suffix column of statpanel displays. No color means the default text-color will be used.
In BYOND 3.0, this color was blue.
Shows a status bar at the bottom of the window. This will show the name of an atom when you hover over it with the mouse.
Stretch the background image. Deprecated; use image-mode instead.
Custom stylesheet used for the control. Changes made at runtime will usually not impact any existing text.
For Map controls, this affects any maptext drawn, and changes to the style should appear on the next refresh.
Affects the background color for tabs. The regular background-color is used for the content area.
Affects the font for tabs. The regular versions of these without the tab- prefix are used for the content area.
Affects the text color for tabs. The regular text-color is used for the content area.
A comma-separated list of id values for the panes included as tabs in this control.
When setting this value, you can put + in front of the list to add tabs to the existing control, without affecting current tabs. You can likewise use - in front of the list to remove tabs.
Note: When using this with winset(), remember you will need to escape + as %2B via url_encode() or list2params().
Text shown in this control. For Input controls this setting is only available at runtime.
The control's foreground text color.
Show text mode even if icons are available. Text mode will be used if no icons are present, regardless of this setting.
Wrap text that is too long for the width of the label.
The title of this window or pane. For a window, the title will appear in the titlebar if present. For a pane, this will be displayed on the tab if this pane is in a Tab control.
If this is the default window, world.name takes precedence over the window title.
Show a titlebar for this window. This is also required for the close, minimize, and maximize buttons to appear.
If is-fullscreen is true, titlebar is ignored, so this value represents the state of the window when is-fullscreen is turned off again.
A color that will be turned into transparency wherever it appears in this window. Overall, this method of transparency comes with many limitations, so it is considered deprecated.
The type of this control. Read-only.
Use the browser's document title to override the title of the window or pane it appears in.
The "fullness" of this bar/slider, as a percentage.
The size, in pixels, of the map after zoom has been applied.
For instance, if the client view has 10×10 tiles (this includes any extended tiles caused by HUD objects) and world.icon_size is 32x32, the map has a native size of 320×320 pixels. If the map has a zoom level of 2, then view-size will be 640x640.
With a zoom value of 0, which is the default for most projects, the actual zoom level is automatically determined by the size of the map control, the map's native pixel size as explained above, and the value of the letterbox parameter.
The color used for visited links.
Width, in pixels, of the bar or slider. A value of 0 uses all available width.
Zoom factor for icons on the map. 1 means to show the icons at their original size, 2 is 200%, 0.5 is 50%, and so on. A value of 0 stretches to fit available space.
Controls the way the map is upscaled.
This section contains miscellaneous information that may apply to multiple vars or procs.
Byondapi is a set of exported functions from BYOND's core library that can be used by external libraries that you call via the call_ext() proc. The purpose is to make interfacing with native code easier, and to allow external access to BYOND's functionality. Before this existed, all external calls had to use text strings to pass data back and forth, which was inefficient for many uses and very limited.
To build your external library with Byondapi, you have to include the byondapi.h header file that's included in BYOND's distribution. When compiling in Windows, you'll also need to link with byondapi.lib; in Linux, your makefile should link with byondcore.so from BYOND's own bin directory.
For simplicity, BYOND defines some basic types and macros in byondapi.h. The one most relevant to you is u4c, which is an unsigned 4-byte integer. There's also s4c which is a signed integer, as well as simple 1-byte and 2-byte ints that use 1c and 2c (respectively) insteaed of the 4c suffix.
The main structure used to pass data back and forth is CByondValue. This mimics an internal structure in BYOND that holds values of all sorts: numbers, null, references to strings, references to objects and lists, and so on.
The exact functions used for interfacing with this structure are documented in byondapi.h.
The main tricky aspect of working with BYOND data is strings. If you need to get the contents of a string, you'll need to allocate memory for the character data and call Byond_ToString() to get a copy of the string. For converting character data to an internal string stored in CByondValue, you'll need to call ByondValue_SetStr().
There are many function calls available in Byondapi for interacting with the server. These include the ability to read and write vars, call procs, create lists, read and write from lists, and so on.
Most of these procs return boolean values: true if they succeed, false if not. In the event of a failure, you can call Byond_LastError() to get the error message.
In any functions that read data from lists, or read string data, they require the caller to allocate the needed memory for a copy of the string or list items. In these cases, the functions also take a u4c pointer for the length. If the return value is false and the length is set to zero, an error occurred. If the return value is false and the length is non-zero, the new length value is the required length of the array; the memory should be allocated and the function called again.
BYOND servers handle proc executin and the management of data in a single thread. If your library tries to call any BYOND server functions in a different thread of its own, the call will block until the server thread can handle it.
If you want to use the handy C++ wrappers and classes, you can include byondapi_cpp_wrappers.cpp and byondapi_cpp_wrappers.h in your library.
The ByondValue class is a wrapper around CByondValue that handles a number of operations for you. You can redefine the argv argument of any call_ext() functions as an array of ByondValue instead of CByondValue, but the return value should stay a CByondValue.
#include <string> #include <byondapi.h> #include <byondapi_cpp_wrappers.h> #include <string> extern "C" BYOND_EXPORT CByondValue merge(int n, ByondValue v[]) { ByondValue result; std::string merged, str; for(int i=0; i<n; i++) { v[i].ToString(str); if(str) merged += str; } result = merged.c_str(); // ByondValue's assignment operator takes care of everything return result; }
There is also a ByondValueList wrapper class for the list structure.
The external function calls like ByondValue_CallProc() have C++ wrappers that use the C calls internally, but if an error happens they'll throw a ByondExtException for you to catch. This replaces the more cumbersome approach of checking if the return value is false and then calling Byond_LastError().
DM-CSS is a subset of CSS, and only supports some kinds of selectors and attributes.
The following table lists all supported attributes, and whether they are supported in text output, maptext, and in other controls (labels/etc.) Other controls will often allow only one style for an entire unit of text. A checkbox in "Other" only indicates that some support exists in other controls, but it may vary by the type of control.
Attribute | Output | Maptext | Other | Notes |
---|---|---|---|---|
color | ✔️ | ✔️ | ✔️ | Alpha colors may not be supported in some controls. |
background | ✔️ | ✔️ | ✔️ | In most cases, only applies to the entire text body. |
background-color | ✔️ | ✔️ | ||
background-image | ✔️ | |||
font | ✔️ | ✔️ | ✔️ | |
font-family | ✔️ | ✔️ | ✔️ | |
font-style | ✔️ | ✔️ | ✔️ | |
font-weight | ✔️ | ✔️ | ✔️ | |
font-size | ✔️ | ✔️ | ✔️ | |
text-decoration | ✔️ | ✔️ | ✔️ | Limited to underline, overline, line-through, blink, and none. Support for each of these may vary depending on where they are used. |
text-align | ✔️ | ✔️ | ✔️ | justify is supported in output and maptext. |
vertical-align | ✔️ | ✔️ | Limited to top, middle, and bottom. | |
text-indent | ✔️ | ✔️ | ||
margin-left | ✔️ | ✔️ | ✔️ | |
margin-right | ✔️ | ✔️ | ✔️ | |
margin-top | ✔️ | |||
margin-bottom | ✔️ | |||
margin | ✔️ | ✔️ | ✔️ | |
width | ✔️ | ✔️ | Applies only to some elements such as images. | |
height | ✔️ | ✔️ | Applies only to some elements such as images. | |
line-height | ✔️ | ✔️ | Support in output control is limited; line heights less than 1 are not respected. Only unitless numbers, percentages, or em units are allowed. | |
white-space | ✔️ | ✔️ | normal, nowrap, pre, pre-wrap, pre-line | |
text-shadow | ✔️ | |||
-dm-text-outline | ✔️ | Custom attribute: Adds an outline to text. Values are in the form: width color style. The style is either blank, or any combination of the sharp and square keywords (see Outline filter). |
These pseudo-classes are allowed in some contexts, but they can only change the text color.
Psuedo-class | Output | Maptext | Other | Notes |
---|---|---|---|---|
:link | ✔️ | ✔️ | ✔️ | |
:visited | ✔️ | |||
:active | Currently not used, but future support is planned. | |||
:hover | ✔️ |
Text colors may be specified by name or RGB value. The RGB color format uses hexadecimal numbers, with 2 hex digits each for red, green, and blue. These range from 0 (00 in hex) to 255 (FF in hex). In certain situations BYOND will also honor a fourth pair of digits for alpha.
#rrggbb #rrggbbaa
It is also possible to use 4 bit values by using only one hex digit per
color. The full 8 bit color is produced by repeating each digit. For example,
#F00
(red) is the same as #FF0000
.
The named colors supported by BYOND, and their corresponding RGB values, are listed in the following table:
black | #000000 | |
silver | #C0C0C0 | |
gray or grey | #808080 | |
white | #FFFFFF | |
maroon | #800000 | |
red | #FF0000 | |
purple | #800080 | |
fuchsia or magenta | #FF00FF | |
green | #00C000 | |
lime | #00FF00 | |
olive or gold | #808000 | |
yellow | #FFFF00 | |
navy | #000080 | |
blue | #0000FF | |
teal | #008080 | |
aqua or cyan | #00FFFF |
There are different ways of interpreting color besides RGB. Several parts of BYOND are capable of using other color spaces.
The default color space is RGB, where each color is split into red, green, and blue components, as well as an optional alpha. All of these components range from 0 to 255.
The color yellow for instance is rgb(255,255,0) which is red and green mixed together at their maximum brightness, but no blue component.
HSV stands for hue, saturation, and value.
All pure hues such as red (hue=0) have a saturation of 100 and a value of 100. As saturation decreases, the colors turns whiter. Lower values mean darker colors and darker shades of gray.
In HSV, saturation is less meaningful as value gets closer to 0. Black of course always has a value of 0. With 10 as the value, saturation=100 gives you a very dark color whereas saturation=0 is a 10% shade of gray.
HSL is a little more intuitive than HSV. Here, the value is replaced by luminance, which again ranges from 0 to 100. Luminance is the average of the minimum and maximum values of the red, green, and blue components.
Black has a luminance of 0; white has a luminance of 100. Pure hues all have a saturation of 100 and luminance of 50. As saturation decreases, the color will approach a grayscale shade of L%.
Saturation is less meaningful the closer luminance is to 0 or 100. At a luminance of 100, the saturation is totally irrelevant. At 90, high saturation will get you a very light shade of the hue but that isn't very far off from a 90% shade of gray.
HCY stands for hue, chroma, and the Y is for grayscale luminance. (Again chroma and Y range from 0 to 100.) This color space is based around the apparent brightness of each color according to a rough approximation of human vision.
Chroma is similar to saturation in that it determines how far from grayscale the color is. As chroma decreases toward 0, the color approaches a grayscale shade of Y%. What's different about HCY color from HSV or HSL is that at chroma=0 and chroma=100 the colors should appear equally bright. Pure red, therefore, has a hue of 0, a chroma of 100, and a Y luminance of only 29.9—roughly what red would look like in black & white with all the color leached out.
This is a special file that's included in all projects when you compile. It contains various constants, definitions of some built-in datums, and so on.
You can see the contents of this file by creating a new file in Dream Maker called stddef.dm. It will automatically be filled with the standard definitions.
The contents of stddef.dm may change with new BYOND versions. However an eye is always kept on backwards-compatibility.