ID:36273
 
Keywords: beginner, tutorial, zbt
Here's another BYONDscape Classic: the third and (so far) last ZBT! BYOND is actually an ideal system for anyone who wants to write a text MUD, and in this article Zilal gives you a glimpse of what's possible. -- Gughunter


Welcome to Zilal's Beginner Tutorial for the BYOND system. I wrote this because even newbies who read the guide, and look at Your First World, are often confused and maybe a little overwhelmed and still don't know where to start. And when you're confused about a new thing, you frequently don't know even enough to think up the right questions to ask to get yourself help.

I'm going to walk you through the creation of a simple world, and I'm not going to assume you know anything about programming or BYOND already. And I'm going to show you one way to do what beginning programmers often want to know how to do: creating rooms and navigating them.

Below, the titles of each section are meant to be approachable; what's in parentheses after the title is a more clinical indication of what you'll learn in that section.

I. Getting Set Up (Opening the program)

Go to your program menu and open the Dream Maker program. Under the File menu, choose "New Environment...", click on the right side of the input box, and type "Testmud". Click OK. When the next box comes up, you will see it wants to create a new Code File with the same name. Click OK.

Every world has to have an environment. For one world you may have many code files, or many sound files, and so on, but they're all part of the same environment. In our case, the Testmud environment. A code file (or .dm file) is where you do the actual programming. In the panel on the left, you can see little icons representing the environment and the Testmud code file.

A folder for this environment was created in your BYOND\bin\ folder, since we didn't specify otherwise. You can go look if you want. By default, BYOND is installed in C:\Program Files\BYOND (if you don't know where BYOND was installed on your computer, it's probably there). In the bin folder, you'll see a Testmud folder. In that, you'll see Testmud.dme (the environment) and Testmud.dm (the code file).

II. First Things First (Comments)

At the top of Testmud.dm, our code file, put this line:

//This is a test mud created by <your name> on <today's date>.

...where you replace what's in the >'s with the appropriate information.

That's a comment. It begins with the double-slash, which makes it turn grey. You can say anything you want in a comment and it won't affect your code. Comments are very important; a good programmer adds comments to her code explaining how it works. A comment should be made as if you're explaining the code to another programmer, even if you never intend anyone else to see it.

That may sound silly, but forgetting how your own code works is a lot easier than it sounds. It's far better to have good commenting and not need it than to need it and not have it.

III. Code that Does Stuff (Verbs)

Hit return a couple times to move down the page, and put in this code (indent lines by pressing the tab key):

mob
verb
say(msg as text)
usr << "You say, '[msg]'"
oview() << "[usr] says, '[msg]'"

We made our first verb. Verbs are the commands the player has access to in a game. We started off with "mob", which means that for the verb to work, a mob must exist in the world. Which could be you. (If we had started off with "turf", then the verb would require a turf to exist. And a mob too, in fact, since without at least one mob in the world there'd be no one to use the verb.) Mob is short for mobile and is usually used to represent players and monsters.

Under mob we put "verb". We used a tab before verb, which means it belongs to mob; mob is the parent. If we'd put "verb" all the way on the left, it wouldn't belong to anything.

Next is "say" and some stuff. By tabbing and putting it under verb, we're indicating that say is a verb. What goes on with that line is slightly more complex. Before we get to that we have to comment our code like good little programmers.

mob
verb
say(msg as text) //what the usr says is passed into "msg" as text
usr << "You say, '[msg]'" //the usr gets one message,
oview() << "[usr] says, '[msg]'"//everyone else in view gets another

Any of the comments in this tutorial may wrap around the screen in your text editor, but there's no carriage returns in each individual one. Now I'll explain what the comments above mean.

(When you're still learning the language, comments also help to better remember what you just learned... like summarizing in your own words the chapter you're reading for History class. You'll be grateful later when I call you up to test you.)

IIIa. Arguments Are Healthy (Verb arguments)

        say(msg as text)            //what the usr says is passed into "msg" as text

As programmers put it, verbs take arguments. But that's not very helpful if you don't know what an argument is or how it can be taken. We can all see the word doesn't make much sense. What exactly are we arguing about? (I prefer to argue about the Yankees and the Red Sox, but if you were interested in that you'd read my tutorial on how to acknowledge that the Red Sox are superior.)

Think of an argument as input. With a verb, this input comes from the player. You decide what name to call the incoming information. In this case, it's "msg", short for "message." We could call it anything we want, but it's good to pick a name that's both descriptive and easy to type.

So, we don't know what input is going to come in here, but whatever it is, we know what we're going to call it. And we know what type of input it's going to be. Text. Not a number, not a mob, but text. Which is words, mostly. Text might contain digits in it: "I am 23 years old"--but that doesn't make it a number. Msg as text.

A player who types "say" in the game will then be able to type in any text he wants. The text gets passed in to the say verb. Picture a player using your verb to say something like "Hello!", and imagine those words dropping right into "msg".

IIIb. Output! (The operator)

            usr << "You say, '[msg]'"   //the usr gets one message,

We put this line under the verb, indented, since it's the action the verb does. Those two lesser-than signs are called an operator. "The operator," in fact. Don't ask me how to pronounce that. Operators are symbols, usually placed between things, that cause something specific to happen. In this case, what's happening is that the right side is being output to the left side. Which is the usr, in our case. Picture the stuff on the right being funneled >> into the usr. Uh, in the opposite direction as we did above. Here it is in the same direction.

.rsu eht otni delennuf gnieb thgir eht no ffuts eht erutciP

Makes much more sense, doesn't it?

            oview() << "[usr] says, '[msg]'"//everyone else in view gets another

Here we output a second message, but not to the usr. This one is going to everyone in view, outside of the usr.

IIIc. What the Heck Is That? (Imbedded expressions)

What exactly "You say, '[msg]'" and "[usr] says, '[msg]'" mean might have you stymied at this point. They have quotes around them, which means they're text strings. What the player typed in, our msg, is a text string... I called it "text" before since, hey, that's what our verb said. Msg as text. Not msg as text string. The verb does not lie. But they do mean the same thing.

When something in a text string has [] brackets around it, that's what they call an imbedded (or embedded) expression. I don't know where programmers come up with this stuff. I believe they sit around drunk throwing darts at a page of the Oxford English Dictionary. However, the imbedded part of "imbedded expression" makes some sense. An imbedded expression is like a nugget buried in our text string.

When your code gets executed... which means run, rather than guillotined, not that that wouldn't be fun... that text string doesn't get funneled into the world exactly as you see it above. Rather, anything that's an imbedded expression gets replaced. You'll see instead whatever the word in brackets refers to. The [usr] is replaced by the usr's name. Usr, also a pronunciation challenge, is whichever player just used the verb. When a text string containing [usr] gets displayed in your world, the [usr] part gets replaced by the verb-user's name.

If I know you, you've already figured out what [msg] gets replaced by. I don't know you; however, this should not stop you from sending me money. Think fast! If a player named Ichabod uses the say verb to say "I'm going to whack everybody with a 30-pound petrified hunk of mutton!", what will the world see?

Yes, that's right. Stars. No, no, I jest. The world will see...

Ichabod says, 'I'm going to whack everybody with a 30-pound petrified hunk of mutton!'

Voila. Chatroom. (You know it's a chatroom because the person speaking already shows a baseless hostility toward everybody else.)

By the way... never let an expression im bed with you. They steal the covers.

IIId. Action! (Compiling and running)

Go under the Build menu and choose "Compile." In the bottom panel you should see:

loading Testmud.dme
saving Testmud.dmb

Testmud.dmb - 0 errors, 0 warnings

If you don't, check your code to make sure it looks like what's above.

When your code is compiled, it's translated into a format the computer (and/or whichever program is running your program) can read. If there are no errors during this process, your .dm code file will also be saved. Now go under the Build menu again and choose "Run." The Dream Seeker (a client, or interface to other programs) will open.

If you're prompted by the Dream Seeker to choose a key, go ahead and do that.

Once your world opens you should see the verb "say" in a panel named "Commands." Click on that or type "say" on the command line (the pink type-in window), then your message. If you're using the command line be sure to put a space in between. And press enter. Now get back here!

IV. This World is Pretty Friggin' Boring (More verbs)

Picky, picky! We'll make it a little more interesting. How about an emote verb? Figured out a way to do it already? Well I'll show you one, just in case. Indented to the same level as say, between it and verb, put:

        emote(msg as text)      //what the usr types is passed in
usr << "Everyone sees:" //the usr sees the first message;
view() << "[usr] [msg]" //the whole room sees the second one

Now if I type "emote bats her eyelashes.", what will I see? What will anyone else in view see?

Everyone sees:
Zilal bats her eyelashes.

and

Zilal bats her eyelashes.
Well, actually, they'd never see that, because I'm not that kind of gal.

IVa. More Creepy Abbreviations (Procs)

What I haven't yet told you is that we've been using procs. Don't be scared. Proc is short for procedure. It's a bit of encapsulated code that sits in our program until we need it. When we need it, we call it, and it performs its function and then returns execution of the program to wherever the program was when it stopped to call the proc.

Anything that has parentheses after it () is a proc. Verbs are simply a kind of proc, the kind that's called when a usr types one in. But oview() and view() are also procs; built-in procs provided by the DM language to make our tasks easier. We call them right in the code when we need them.

As you've learned, oview()'s function is to provide a list of people in view other than the usr, and view()'s is to provide that list but with the usr included. When we output text to one of them, we're outputting it to the list that oview() or view() returns when it's called.

The DM language provides many, many useful procs for us so we don't have to code everything from scratch. (You can find a list of them in the DM Reference.) But every game will need its own custom-built procs as well.

IVb. Procadile Dundee (Proc declaration)

Beneath your mob code, put this:

proc
Msg23(second,third) //declares a new proc that takes 2 arguments
usr << second //outputs the first to the usr
oview() << third //outputs the second to oview()

This is new. No text strings, no imbedded expressions, no love. What we've done is created a new kind of proc. We had to declare it; we put it under "proc" so the program knows that's what it is. And we gave it a nice descriptive name. We could have named it in lowercase, but I like to use uppercase to distinguish custom procs from built-in ones like oview().

    Msg23(second,third)         //declares a new proc that takes 2 arguments

The name "Msg23" refers to what function our new proc is going to perform: it's going to output both second- and third-person forms of a message to the appropriate viewers. (So you'd read it as "message two-three," not "message twenty-three," unless you're really getting into that whole "code" concept.) The proc takes two arguments, and we've given those descriptive names too. Second refers of course to the second-person message we'll be passing into the proc, and third to the third-person message.

(And, of course, second-person refers to statements with "you" as the subject, while third-person is for statements directed at neither you nor me, but "them.")

        usr << second           //outputs the first to the usr
oview() << third //outputs the second to oview()

We don't know what text strings the arguments "second" and "third" are going to hold when we use this proc, but we know what to do with them: output them to the usr and oview().

You may have noticed that, unlike in our verb declarations, we did not name the arguments "first as text" and "third as text." There's a reason for that. The "as text" qualification is necessary in verbs because the Dream Seeker client needs to know what kind of information to accept from the person typing. The client uses "expansion" as a typing aid, trying to fill in the rest of whatever verb or argument it thinks the usr is going for, and to do that it needs to know what that usr is supposed to be typing in.

But with our custom procs, we'll be the ones calling them, not a usr typing something in. With no expansion involved, there's no need to tell the client anything.

We'll put our new proc to use in a moment. Right now, go under the Build menu and compile again, which besides translating the new code will save our files. Always remember to save regularly, and make backups (with real projects, anyway). If you ever forget, there's a 99.9% chance that'll be the day lightning sizzles out of a clear sky to strike your computer.

I had to rewrite half this tutorial because I forgot to save, and my computer sensed this and obediently crashed. Do as I say, not as I do.

IVc. Fancy Fancy (Calling procs)

We're going to alter our say verb to use this new proc.

        say(msg as text)            //what the usr says is passed into "msg" as text
Msg23("You say, '[msg]'","[usr] says, '[msg]'") //gives our task to Msg23

All right! With our new Msg23() proc, we can use one line to do the job of two. Imagine what a time-saver that will be when you have a huge MUD with 3,000 verbs.

You were planning on at least 3,000 verbs, right

Above, we pass in our two arguments: the second-person text string, and the third-person text string. Msg23() does the job of outputting them to the correct parties, and the result will look just the same in the game as it did when that code was in the verb.

V. Arguing with the Mob (Named arguments)

Now I think it's time for a verb that'll really let our players interact. In a silted, typically meaningless American sort of way. Here are a new verb and proc for us:

        wave(var/mob/M as mob in oview())   //waves to a mob in oview()
Msg223("You wave to [M].",target = M,"[usr] waves to you.","[usr] waves to [M].")
    Msg223(second1,target,second2,third)
usr << second1 //outputs first text string to usr
target << second2 //outputs second text string to verb target
for (var/mob/M in oview()) //outputs third to mobs in view() who are neither
if (M != target) M << third

Keeping with our alphabetical order, we'll put wave under say, and Msg223() above Msg23().

        wave(M as mob in oview())   //waves to a mob in oview()

Wave takes an argument in a more complex way than our say or emote verbs do. Instead of text, it takes a mob as an argument. The mob has to be in oview(). It gets dropped into the variable M. So when a usr uses the wave verb, the client will only let it input the name of another mob after "wave."

            Msg223("You wave to [M].",target = M,"[usr] waves to you.","[usr] waves to [M].")

Next we call our new proc... with four arguments. The new proc is called Msg223 because it will display two second-person messages (with "you" as the subject) and one third-person message. The first message goes to the usr, the second to the target of the verb, and the third to anyone in view who isn't the usr or the target.

The thing is that our Msg223() proc won't know who the target of the verb is, so we have to pass in that target as an argument too. Normally, arguments are received by a proc in whichever order they were passed in. And indeed, we've set it up to send Msg223() the arguments second1, target, second2, and third in the same order Msg223() is set up to receive them. But with four arguments, things can get confusing. If you ever called Msg223() with the arguments in the wrong order, people would get the wrong messages, they'd be waving to their enemies and total strangers, it'd be chaos!

To avoid that chaos, we're telling Msg223() that M is definitely meant to get dropped into the argument named "target." Now we can put "target = M" wherever we want among the text strings when we call Msg223(), but we might as well just keep it where it is.

You could also name the text strings. A completely safe Msg223() call would look like this:

            Msg223(second1 = "You wave to [M].",target = M,second2 = "[usr] waves to you.",third = "[usr] waves to [M].")

...but I think that's more safety than we need. I trust you!

Va. For Love or Money (The for and if statements)

Now let's take a closer look at what our newest proc does.

    Msg223(second1,target,second2,third)

There's the proc declaration, complete with all four arguments.

        usr << second1          //outputs first text string to usr
target << second2 //outputs second text string to verb target

That's easy enough to figure out: our first text string is output to usr, and our second to the target of the verb. So if I wave to you in our little MUD, I will see:

You wave to Reader.

And you'll see:

Zilal waves to you.

Aren't I friendly?

        for (var/mob/M in oview())  //outputs third to mobs in view() who are neither

Here's some new stuff. We use a DM statement here, the for statement. With it, we have the program loop through all mobs in oview(), and for each one, do whatever is indented below the for.

Now, when we're looping through each mob in oview(), we need somewhere to temporarily hold a reference to that mob, so that we can do evil things to it. That's where var/mob/M comes in. With "var" we declare a new variable; with "mob" we tell the program that the variable is meant to hold a reference to a mob. And "M" is just what we name it.

            if (M != target) M << third

Here's where we do our thing to each mob the for loop gets to in turn. We make an evaluation about it. This is done with the if statement. The symbol != means "does not equal." What we're telling the program to do is check and see if our temporary M isn't the target. And if it isn't, we output the third person message to it. (We don't have to check and see if M isn't the usr, since we're looping through oview(), which has already excluded the usr.)

This means that anyone in view who isn't the usr and isn't the verb target will see:

Zilal waves to Reader.

Now, compile and run! You will be able to wave to anyone who comes into your game. Unfortunately you have no friends, so you won't really be able to test out your verb. But you now have a fancy chatroom. Ain't life grand? You should Save All or Compile again now, by the way.

VI. But I Don't Want a Chatroom!! (Atom prototypes)

Rooms provide the structure of a MUD. There are a couple ways in DM to make text MUD rooms; I'm going to show you the most flexible.

Most of the things you'll have in your world will belong to the "atom" category. Atom stands for areas, turfs, objs and mobs. They're all things that can be represented on the map in a graphical game. However, three of them -- areas, objs and mobs -- will also be useful to our text MUD. But we're not going to call them aoms, because we have enough unpronounceable words already.

Nice and alphabetical now, above your mob code, put:

area
var //declare new area variables
north
south
east
west

Lounge //create a new area prototype
desc = "It is very comfy here. The walls are edged in bean bag chairs, and there is a benevolent-looking portrait of Zilal overhead."

The first thing we did was to define some custom variables. We indented them all under var, which is indented under area, which indicates they are all new variables for the area type.

The next thing we did was create a new area prototype, /area/Lounge. A prototype is like a blueprint. A blueprint of a house isn't the house itself... and I don't recommend using it to keep the cold out... but you do use it to build a house. Our code here doesn't create a lounge in the game. It's just the blueprint. We can make as many lounges from that one blueprint as we want.

Next we assigned a value to a variable of the Lounge. It happens to be a variable built in to DM: desc, short for description. Since it was built-in, we didn't have to declare it. The value we assigned to desc is a text string; the description of our lounge.

So we have a lounge blueprint. How do we make lounges from it?

VIa. Brave New World (More built-in procs)

Aside from mobs that are created when people log into a world, all creation of new instances of atoms must take place within a proc. When would be a good time to create a new lounge? Well, right when the game starts up sounds good to me.

So we'll need a proc that gets activated when the game is started. Thankfully, DM provides built-in procs that get trigged by common events, and a world's startup is one of those events. So we will take the built-in world proc New(), and override it by writing our own code for it. Above our area code... all right, so it's not alphabetical, but does put important stuff first... put:

world
New() //overrides the world's New() proc
for (var/Atype in typesof(/area)) //loops through area prototypes
var/area/A = new Atype //creates a new instance of each prototype
A.tag = A.name //makes its tag the same as its name

..() //calls the parent

What's that do?

world
New() //overrides the world's New() proc

Here we access the properties of the "world" object, which is a way to get at stuff that governs the whole game. Most specifically, we access its New() proc.

        for (var/Atype in typesof(/area))   //loops through area prototypes

Another for loop. This time we're looping through all the kinds of area prototypes in our game. To do this, we use a handy built-in proc called typseof(). The variable we use to temporarily hold a reference to each prototype is called Atype.

            var/area/A = new Atype      //creates a new instance of each prototype

Next we define a new variable, meant to hold a reference to an area. We make it a new instance of whatever prototype Atype is.

            A.tag = A.name          //makes its tag the same as its name

Tag and name are more built-in variables, like desc. When we named our Lounge prototype "Lounge," that automatically made its name, well, "Lounge." The uses of the name variable probably come to mind pretty easily. Tag may be a mystery. The tag variable is used for a unique identifier, and it can only be set inside a proc. We're going to be using it later for its uniqueness. Sort of like the slavering BYOND hordes over the world use me.

        ..()                    //calls the parent

You've been a very fidgety student and I have considered calling your parent, but that's not what the comment is about. The ..() command says to go call the actual built-in New() proc. It's called the parent proc because it's the version ours was derived from. (Didn't you ever ask, "Mom, how was I derived?")

The New() WE coded is a child, and it doesn't know everything the parent does. So we tell it to go ask its parent what to do with that ..() line. That way, if the built-in New() does anything important, we won't nix that function when we override it the proc.

VIb. Five Notes (Clarification, advice, info, warning)

1. I've put comments on almost every line in our Testmud program, but that's neither expected nor desirable in normal practice. Just put in enough commenting that another coder reading your stuff would be able to figure out basically what it does. Or exactly what it does, perhaps, with very complex code.

2. Note how we give our variables names that relate to their purpose. I strongly recommend this. Make a variable's name as descriptive as you can without making it a pain to type repeatedly.

3. When we declare a variable from within a proc, it exists only in that proc. Once the proc returns the variable goes poof. If you want another proc to use that variable you must pass it in. You'll learn more about that as you progress in your study of BYOND.

4. If you recall, we set areas' tags in a proc. If you were to go back and set that below, say, Lounge's desc, you'd get an error.

Testmud.dm:20:error:tag:may not be set at compile-time

That's because a tag has to be unique, and uniqueness isn't something the program can check when you compile it; it can only be checked when the world is up and running. If something is unknown when you compile... such as whether a tag is really unique... you have to put it inside a proc.

5. You might have noticed a difference between New(), a built-in proc, and view(), also a built-in proc. One is capitalized and one isn't. That's to indicate New() is a proc belonging to something -- the world -- while view() is just a regular old proc. It's all on its own. (Msg23() is all on its own too, but it's not built-in. In your own programs, it's up to you whether to capitalize custom procs.)

VIc. Making an Entrance (the Login() proc)

So now we have a world with a lounge in it. But how do WE get into the Lounge? Hmm well, when would be a good time to put us in the Lounge? How about right when we log in? I'm glad you agree! There is a built-in proc that gets called when mobs log in. We're going to override that.

mob
Login() //overrides mob's Login() proc
Move(locate("Lounge")) //finds the Lounge, and puts mobs there
..() //calls the parent

Here we call another built-in proc: Move(). In this case we're not overriding it, just using it. In a graphical game, Move() is automatically called when players move around the map. But we have no map, so unless we do something, players in our MUD essentially go nowhere. Just like you in school, young man.

We use yet another built-in proc... see how handy those are? ...as an argument to Move(). The locate() proc will find whatever in the world has the tag we pass in as its argument. Since tags are unique, there is only one Lounge, and that's what locate() will find. And that's where players logging in will be moved to.

By now, all our code together should look like:

//This is a test mud created by <your name> on <today's date>.

world
New() //overrides the world's New() proc
for (var/Atype in typesof(/area)) //loops through area prototypes
var/area/A = new Atype //creates a new instance of each prototype
A.tag = A.name //makes its tag the same as its name

..() //calls the parent

area
var //declare new area variables
north
south
east
west

Lounge //create a new area prototype
desc = "It is very comfy here. The walls are edged in bean bag chairs, and there is a benevolent-looking portrait of Zilal overhead."

mob
Login() //overrides mob's Login() proc
Move(locate("Lounge")) //finds the Lounge, and puts mobs there
..() //calls the parent

verb
emote(msg as text) //what the usr types is passed in
usr << "Everyone sees:" //the usr sees the first message;
view() << "[usr] [msg]" //the whole room sees the second one

say(msg as text) //what the usr says is passed into "msg" as text
Msg23("You say, '[msg]'","[usr] says, '[msg]'") //gives our task to Msg23

wave(var/mob/M as mob in oview())//waves to a mob in oview()
Msg223("You wave to [M].",target = M,"[usr] waves to you.","[usr] waves to [M].")

proc
Msg223(second1,target,second2,third)
usr << second1 //outputs first text string to usr
target << second2 //outputs second text string to verb target
for (var/mob/M in oview()) //outputs third to mobs in view() who are neither
if (M != target) M << third

Msg23(second,third) //declares a new proc that takes 2 arguments
usr << second //outputs the first to the usr
oview() << third //outputs the second to oview()

At this point, Save All or Compile again.

VId. Buntime Rugs (Runtime bugs)

Try running our MUD now. There isn't much to see, is there? I thought we were in the Lounge!

What we have is a runtime bug, of a sort. Our code all checks out, looks great... but it doesn't do what we want it to. This can be very frustrating, and you'll no doubt bang your head on the keyboard countless times over this during your programming career. This is why I have POIUYTREWQ imprinted across my forehead. In case you've ever wondered.

So what's wrong with the code we just wrote? We want to be able to see what room we're in, right? Some events in BYOND happen all on their own; Login() getting called when a player logs in, for instance. But a lot of expected events don't. If we want players to see what room they're in, we'll have to show them ourself.

VIe. REALLY Making an entrance (The Move() proc)

When's a good time to output a room description to players? When they enter the room, of course. There are actually a few procs called when players go from place to place, but we're going to use one we already know about... Move(). Below the mob Login() code, put:

    Move(area/A)            //overrides mob's Move() proc
..() //calls the parent
src << "<b>[A.name]</b>"
src << A.desc //displays room description

When we call Move() in the Login() proc, we pass an area (retrieved with the locate() proc) into it, so we receive it here with A, meant to hold an area reference. Next we call the parent, since the parent Move() is what does the actual moving of the mob. Next we display the name and description of the room the mob was moved to. Try it!

VII. In the Rooms the Women Come and Go (Review)

It wouldn't be a very good MUD with just one room. So let's make a couple more and find out how to move between them. Make these your new area prototypes (two new, one altered):

Courtyard
desc = "Hushed noises from the lounge drift out into this pleasant-smelling courtyard."
east = "Lounge"

Kitchen
desc = "A refrigerator is here stocked with snacks and drinks for hardworking BYOND coders."
south = "Lounge"

Lounge //create a new area prototype
desc = "It is very comfy here. The walls are edged in bean bag chairs, and there is a benevolent-looking portrait of Zilal overhead."
north = "Kitchen"
west = "Courtyard"

We made two more rooms, a courtyard and a kitchen, and we set exits up for each of them. We made sure the exits lined up, so that the Courtyard and Lounge were properly east and west of each other. (Drawing out a map can help with this.) Now we're going to make sure the exits get displayed when people enter a room. Our new Move() code is this:

    Move(area/A)                //overrides mob's Move() proc
..() //calls the parent
src << "<b>[A.name]</b>"
src << A.desc //displays room description
src << "People here: \..."
for (var/mob/M in A) //loops through mobs in room, displaying each
if (M == src) src << "You \..."
else src << "[M] \..."

src << "\nExits: \..." //displays any exits
if (A.north) src << "north \..."
if (A.south) src << "south \..."
if (A.east) src << "east \..."
if (A.west) src << "west \..."
src << "" //forces carriage return

We do a few things in our new code. Most of it builds on what you already know, but let's take a closer look.

        src << "People here:   \..."

The only thing new here is the \... text macro. It means "put the next output on this line instead of a new one.

        for (var/mob/M in A)        //loops through mobs in room, displaying each
if (M == src) src << "You \..."
else src << "[M] \..."

Here we do a nice if and else statement. The double equals == is used when you're just checking to see if something equals something, not actually making it equal that something. We check to see if the M the loop is on is the same mob as the source of the verb (the player who moved). If so we output the word "You"; if not, the mob's name.

        src << "\nExits:   \..."    //displays any exits

There's another text macro in there, \n. That forces the program to move down a new line to display the next output.

        if (A.north) src << "north   \..."
if (A.south) src << "south \..."
if (A.east) src << "east \..."
if (A.west) src << "west \..."

More if statements. In these we simply check to see if the room's north, south, east and west exit exist; that is, if they have any value assigned to them.

        src << ""           //forces carriage return

Whether or not there were any exits, the program is still waiting to put the next output on the same line, and we don't want that to go on forever, so we output a blank line with no \... at the end.

Now compile, and run.

VIIa. You Move Me (Client directional procs)

Not a bad-looking room, eh? But how in the world do we get to our nice new courtyard and kitchen?

Luckily for us, the DM language provides built-in procs that are triggered when people use the arrow keys (or the directional buttons in the Dream Seeker, or the keypad). We access them this way:

client                          //overrides the client's North() proc
North()
var/area/A = usr.loc //creates a variable to hold usr.loc
if (A.north) usr.Move(locate(A.north)) //moves player north if possible
else usr << "You can't go there." //otherwise not

The "client" means that we're accessing stuff the Dream Seeker controls or keeps track of; in this case, use of the arrows/keypad/directional buttons. Loc is another built-in variable, one that gets set whether we set it or not, and whether a player's location is somewhere or nowhere. For instance, Move() sets loc. We check to see if the usr's loc has a north exit, and if so, locate it and move them there. If not, we tell them they can't go there.

You may be noting that we used "usr" here but "src" in Move() to refer to the player who moved. There are many situations where both usr and src will work for your code. I like to stick to usr when and only when the proc being called was a direct result of a player's keystrokes. With that direct line from player to proc, you can be sure "usr" really is who you think it is. But who the usr is can sometimes become unexpected when you're calling procs from other procs.

Take the code above and duplicate it for South(), East() and West() too, making sure to alter both instances of A.north each time. NOW compile and run. Use the arrow keys to move from room to room. Looking good!

VIIb. Any Object-ions? (Objs)

We need some stuff in this MUD. One of the atom types, obj, is perfect for stuff. Let's create a prototype of an obj for our game.

obj
certificate
desc = "The certificate reads, 'This is to certify that you have studied Zilal's Beginner Tutorial for text MUD fans.'"

Now for some useful verbs to let you interact with it. I won't comment these; see if you can figure out how they work from what you've already learned.

mob
verb
drop(var/obj/O in usr.contents)
Msg23("You drop [O].","[usr] drops [O].")
O.loc = usr.loc

get(var/obj/O as obj in oview())
Msg23("You get [O].","[usr] gets [O].")
O.loc = usr.contents

inventory()
usr << "You are carrying:"
for (var/obj/O in usr.contents) usr << O

look(var/obj/O in view())
usr << "You look at [O]."
usr << O.desc

We have a certificate prototype, but if we're going to play with it, we need to create one in the game. Let's alter mob/Login().

mob
Login() //overrides mob's Login() proc
Move(locate("Lounge")) //finds the Lounge, and puts mobs there
..() //calls the parent
new /obj/certificate (usr)

We didn't use "var" to declare a new variable to hold our new certificate obj. That's because we don't need to access any of its properties, so we don't need to keep track of a reference to it. We're just creating a new one in usr... short for usr.contents. (Contents is yet another wonderful built-in variable.)

VIIc. Are We Having Fun Yet? (I hope so!)

Now all we have left to do is alter Move() to display the objects in the room too. Between the People and Exits code, put:

        src << "\nStuff here:   \..."
for (var/obj/O in A) src << "[O] \..."

Compile and test. And we're done.

VIII. Exercises

Here are a few things to try on your own. Can you...

1. Populate your world with more rooms and interactive objs?

2. Set up your world to also make use of the directions northeast, southeast, northwest and southwest?

3. Send a message to the room when a player arrives? When he leaves?

4. Make verbs that allow players to set their descriptions, and look at each others'?

5. Create "immobile" objs that are created when their rooms are, that can be looked at but not gotten?

IX. Final Words

Start small. Get comfortable with the language before you embark on that 10,000 room RPG you've always dreamed about making. Download and fiddle with the "Your First World" demo from the Tutorials section of the website.

The DM Reference I've mentioned a few times in this tutorial can be found in C:/Program Files/BYOND/help/ref, or on the BYOND website. When you have a question about how to do something, I advise scanning through the procs there to see if anything does what you want. There are a lot of them but chances are even if you don't find what you want, something else you'll find useful will catch your eye.

If you're still lost after you've thought your problem through, experimented, and scanned the Reference, check out the BYOND Forum. Browse or do searches for your issue. If you're still not getting anywhere, post to the Newbie Central board and ask your question. Be specific or it's very difficult for others to help you, and don't ask others to do your work for you.

Wrong: "How do I make a combat system?"
Right: "What's a sample equation to see if a mob with 50 strength and 40 accuracy will hit a monster with 45 defense?"

Wrong: "Why'd I get this error?"
Right: "Here's my mob variables and my code for logging in. How come I got this error for the third line from the bottom?"

Wrong: "Can somebody give me code for character generation?"
Right: "Which procs would be useful for letting a player choose his race or class?"

Wrong: "How do I use BYOND?"
Right: "I read the tutorials, scanned the Reference, bought the book, now all I want to know is: where can I donate money to Dantom International?"

X. There is no X. Don't ask me Y.

Z

Simply amazing :)
Why is this so wide?