New coder help file

New coder help file

Postby Suhis » 08.07.2011 15:51:26

Based on my experience in guiding beginning coders, I have noticed
that there are some common mistakes among them. In this file I will
attempt to provide the more correct solutions and explain them. I
will assume that you at least know what variables and functions are
and have a full understanding of them. If you don't, I recommend
doing some studying elsewhere and then coming back to this file.

If some of my methods strike you as wrong, or you have better
solutions, I urge you to let me know. I am not perfect, after all.
(Well, in a sense I am. *)

So, on to the business we go. I am going to begin by something that
isn't very technical in itself, but still remains very important.
That is:


1) Writing readable code


Yes, I am sorry, but this has to be written. Too many new coders start
with pagefuls of weirdly indented code with "cool and hip" variable
names such as "perse" and "foo." When you write your code, you must
realize that someone, maybe even you, will probably have to deal with
your code later on. It's all about maintainability. Here's an example
of badly written code:
Code: Select all
  void reset(status arg){
  object pylfk,pylfk2;
  int pylfk3,pylfk4;
  ::reset(arg);
  if(arg)return;
  pylfk=environment();
  if(!present(
  "lion",pylfk)) pylfk=clone_object("/file/name");
  }
 

Now, while you might find the weird and unused variables amusing,
and are perhaps too lazy to remove them after you thought you would
use them, can you really tell what the heck is happening in the code
without getting a headache? I mean... it looks like just a fricking
blotch of characters. Now let's view a proper example:
Code: Select all
  void reset(status arg)
  {
    object env, mons;
    ::reset(arg);
    if(arg) return;
   
    env = environment();
    if(!present("lion"), env))
    {
      mons = clone_object("/file/name");
    }
  }
 

It doesn't take a genius to see which one is easier to read and maintain.
You see that adding a little indentation and more descriptive variable names,
it gets much much clearer. (Some veterans use weird variable names --
don't follow their example: their code is a pain to read.) While I am not
trying to convert you into my views of code indentation, I urge you to be
uniform in your code, so that all your code is formatted in the same way.
(Oh, and pleeease use my way of indentation! :)

As time goes by and you edit your code, your indentations will probably begin
to decay slightly if you don't remain always sharp. It is good to etch
intendation into your spine, so that you don't even think of it when you're
hitting those spaces.

Oh, and never _ever_ use tabulator hits for indentation. Different editors and
clients have a different way of showing tabs and therefore your code _will_ be
messed up on most of other people's sight.

You should keep your line-length below 80 characters too. While most of the
people have clients capable of showing longer than this, there might be
situations when you have to fix your code in a hurry from your library, using
some neanderthal telnet client accompanied with the MUD's internal 'ed'
editor. Believe me, just do this and you will have many headaches less.

Now on to the real thing: the actual coding.



2) Using the reset function


I have noticed that many starting coders have no clue of what a proper
reset-function should look like. Hell, some don't even know what reset
actually does. In here, I will attempt to explain it.

First of all, when an object is loaded for the first time (be that a
room that is visited for the first time, a monster that is cloned
for the first time, or anything else) its reset function will be
called immediately after compilation. Thus, reset function is generally
used to give initial values to an object's variables, or in case of rooms,
clone the monsters that should be in the room. After the first time of
calling reset (after the first reset), the reset function will be called
again every now and then.

Now you are probably asking what's that "status arg" thing doing in there.
When reset is called, an argument is passed to it which tells whether it's
the very first reset of the object or one of the resets that come
afterwards. So, this argument has the value of 0 (zero) on the first
reset, and anything else on other resets.

Now there are things that you want to do only once - like set descriptions
and items. So, to do these only on the first reset, we can do this:
Code: Select all
  void reset(status arg)
  {
    ::reset(arg);
   
    // this line causes the following code to be executed only on
    // the first reset, i.ex. item creation
    if(arg) return;
   
    // these lines are only executed when arg == 0, i.ex. only on
    // the first reset
    set_short("A demonstration");
    set_long("A beautiful demonstration.\n");
  }
 

In this code, you see the line with "return" on it. Anything beyond that
line in the reset function will be executed only on the first reset.
You also see ::reset(arg); - we will explain that one later, just
remember that most of the time this line should be the first line of
code in your reset function (right after the possible variable
declarations).

So what should be included in reset function? Anything that should be
set only once - like smells, tastes, values, etc - should be placed
after the line with if(arg) return. Of course the most nitpicky of
you will be saying that an object's value can change after it's
created, but that code will be elsewhere. Consider the reset values
as "starting values," or somesuch.

And what about before the return line? There you can put things
that happen frequently with a long interval. Such things are
monster repop and maybe doors closing automatically.. But those
things are already handled for you in the room-inherit, so you
don't have to worry about them (unless you wanna get fancy and
know what you're doing).



3) Using the init function


The init function is pure magic, let me tell you. It is probably
the most called function of your whole object. If you need your
object to have fancy emotes or your rooms to have enter messages,
init is the function to look at.

So, what makes init so special? Now to explain that, I will have
to do a brief crash course on inventories and environments in MUDs.
Each object has a list of other objects that are inside it and
correspondingly, each object has a knowledge of which object they
are inside of. When you move in the mud, there are many functions
called that ultimately result in your removal from the old
environment's inventory and addition to the new list. When this
addition is made -- or simply, when you move into an object --
you will cause the calling of the init function in every single
object that you can see, that is: the new environment object and
all the other objects in the environment object's inventory list.
And if that's not fancy enough, all these objects will also call
your init. It's like a trigger that fires when you see other objects.

From the init function, you can call this_player() to determine
who was the one that caused your init to be called.

Most usually, init function is used to determine custom commands
associated with the object in question. That is done by using the
"add_action" function. So, basically when an object that has
custom add_actions moves into your vicinity, you will call its
init function and these actions are added to you. These actions
are automatically removed from you when you lose vicinity of the
object, so you won't have to worry about that.

Here's an example of an init function:
Code: Select all
  void init()
  {
    ::init();
   
    add_action("boast_function", "boast");
  }
 

This init function says that each time it is seen, a new command
called "boast" is added to the one who called init. Notice that
there is this mysterious "::init()" that is similar to the "::reset(arg)"
seen in last chapter. Again, I urge you not to *never* forget
this one, unless you aren't inheriting anything. For example
if you forget to call ::init() in your own init function that
is written in a room object, your exits will not work.

Next is up a little more about using add_action and writing
new commands with it.



4) Writing your custom commands in objects


This is the part where even older coders make mistakes. Yes,
most of the time it seems like everything is all right: your
own code works. BUT, and this is one huge but (no pun intended):
if you are sloppy, it is very easy to break other people's code
meanwhile your own code remains functional.

Let's imagine you want to make it possible to smell the flower
that you plucked from aunt Daisy's flowerbed. So you pick it up
and type in "smell flower" and it works all right. All right?
Well, let's speculate a little. Let's just say there's an important
quest item, a handkerchief for example, that was coded by someone
else and can also be smelled. You have both of these in your
inventory -- the flower and the handkerchief. You probably already
notice that now there are two versions of the smell command defined
for the holder of these objects. If things are coded wrong, typing
"smell handkerchief" will result in the flower code just blurting
out "Smell what?" Or the other way around.

I will explain a little on how actions work. An add_action defines
a command and the function that will be called when the command is
used. The function returns a status (a value that is either 0 or 1)
and gets a single string as a parameter. The string is obviously
everything that was typed after the command -- for example in case
of "smell handkerchief" this string parameter would be "handkerchief".
Simple enough. The more interesting of these two arguments is the
return value.

The MUD has a very intelligent system for handling actions with
the same name. They are placed in sort of a queue, where the most
recently added action is first. When you type a command, the first
function in the queue is executed. It this function returns 1,
the MUD will think that the command was correctly executed and
happily stop execution at that. However, if it returns 0, it will
be the MUD's cue that it should go on and execute the next function
in the queue. If all the functions in the queue return 0, the MUD
will spew out an error message. Usually this message is the
oh-so-familiar "What?" but wait, you can actually change it. The power
to change it is something you should not ignore.

In our earlier example of the flower and the handkerchief, the way
things should go is that when typing "smell handkerchief" -- let's
assume the flower is the first one on the queue -- your flower code
should return 0 to signal the MUD that we are not going to do
anything with this command. But before returning 0, we can set the
error message to "Smell what?" in case there is no other object that
can handle the command! So if there was no handkerchief, the MUD
would see that all the objects in the queue returned 0 and print out
the error message. Instead of "What?" it will print out the new error
message that we just set: "Smell what?" But anyway, in our case the
handkerchief actually is there, and after the flower returned 0, the
handkerchief's function for "smell" is called. And as can be guessed,
handkerchief will return 1 and the MUD will be content.

Here is a possible init function that both of the objects could have:
Code: Select all
  void init()
  {
    ::init();
   
    add_action("cmd_smell", "smell");
  }
 

Here's the command code for the flower:
Code: Select all
  // flower.c
  status cmd_smell(string arg)
  {
    if(arg != "flower")
    {
      // notify_fail sets the error message
      notify_fail("Smell what?\n");
      // we will return 0 to tell the MUD to keep looking
      return 0;
    }
   
    write("The flower smells very flowery.\n");
    say(this_player()->query_name()+" smells a flower.\n", this_player());
   
    // return 1 tells the MUD that we handled the command and it should not
    // look any further
    return 1;
  }
 

The code for the handkerchief would probably be almost identical
to this one, so I am not going to write it here.



5) Overriding inherited functions

This subject is somewhat advanced and I am not going to explain
inheritance in here. If you don't have a clue what inheritance is,
you should probably do some reading; there is a plethora of
good articles regarding this.

Calling the original function from the new version happens by
preceeding the function name with two colons. So, the "::reset(arg);"
and "::init();" are just calls to the versions of the function that
are defined in the inherited file. Therefore you should always remember
to add them as otherwise some of the core functionalities of your
inherited files will not be called.

Here is a short list of hints you should always remember:

- You should always make sure you have the same return type and the
same argument list as the original function.

- If your only purpose is to add functionality, not to remove the old
one, you should always make use of the original function.

An example:
Code: Select all
  status id(string str)
  {
    return ::id(str) || str == "something" || str == "blahblah";
  }




So, here was my little collection of tips and hints. What I want
you to know is that I live on feedback, so please go on and throw
a tell or mudmail. Also keep in mind that my intention is not to
leave this file as is, but to keep expanding and clarifying it --
all this based on your feedback. Oh, and if you have any difficulties
with LPC, ask me, I will probably answer and if I think it's something
that everybody should know, I will add it to this file.

Thank you for your time,

// Halen



(* "Nobody is perfect. I am a nobody. Therefore: I am perfect.")

EOF
Suhis
 
Posts: 180
Joined: 31.01.2004 13:31:16

Return to Manuals

Who is online

Users browsing this forum: No registered users and 1 guest

cron