Post by Blain20 on Jun 11, 2008 21:33:07 GMT
I understand that much of the code posted on this board might as well be written in Greek (or some other foreign language for you Greek-readers out there). So I will attempt to explain some of what's going on so that you can at least read it and follow the logic.
A blank .c file [i.e. test.c] has no data within it. Because it has the file extension .c, it can be loaded into memory as an LPC object. The word object in this case refers to an LPC object, not necessarily a game object (room, weapon, armour, etc.) Notice that I say that test.c is completely empty. So when it loads, pretty much nothing happens. So now we need to put some code into it so that it becomes more useful.
The first thing we want to do is to ask the driver to "spell check" our code more thoroughly than it will by default. So the first line we want is:
#pragma strict_types
Note: I mentioned types in another thread "examples?". Also, note that "//w/testwiz/test.c" is just a comment (stuff that doesn't get executed but is there to tell the reader of the code something). This is telling you which file we're looking at because later we will look at other files.
The "strict types" pragma asks the driver to force us to put types where needed, and to "cast" types for external calls to other objects where we would otherwise not know what data type the other object is giving us. Without this pragma, you can relax your code style a bit, but if you make a mistake, it may not show up until some hapless player runs into it and it bugs out. So strict_types helps us squash any bugs a bit earlier. Some MUDs have this automatically defined as default for you, though other MUDs do not.
Still, this object does practically nothing upon loading. Before we get some oomph into it, let's set up some variables by "declaring" them. We declare these at the top so that they are available throughout the object. An object is read from line 1 to the last line. So if a variable or function is "used" or "called" and has not yet been "declared", you will generally see an error (there are some cases where this is not true, but I won't focus on that now).
private static object player;
Here we declared the global variable named "player" of the type "object" with the variably types "private" and "static". "player" is a name I came up with, and can be most anything, though there are some rules on the naming convention. I think one of the rules is it must begin with a letter. "object" is an LPC data type. Remember, objects are LPC objects here, not necessarily rooms, weapons, armours, or players, though these are included in this long list of LPC objects. "private" and "static" are special modifiers that will not be covered in this lesson. Global variables may be used anywhere within this LPC object.
Adding this variable did not allow our object to come alive just yet, but since we're going to use it in the follow section, we needed to declare it beforehand so that it doesn't error when we use it.
And now for our first function!
void create()
{
string name;
int level;
player = find_player("pillar");
name = player->query_name();
level = player->query_level();
write("Pillar's name is "+name+" and he is level "+level+"\n");
}
Now we've added quite a bit of code here. Let's begin. First, you'll notice "void create()". This is the function declaration, and "void" is the LPC data type that this function is expected to "return". "create" is the arbitrary name of the function just like "player" was the arbitrary name of the variable I declared above. Void is sort of an LPC data type, but it means that the function will return nothing at all. Thus, you'll note there is no "return" statement in the function, because omitting it is the same as having it as far as the void function type is concerned. We'll cover functions that return something of use later on.
A function consists of a name, return type, modifiers (I didn't use any, and we'll cover those later [they will be similar to private and static for variables]). The parenthesis (,), any arguments within the parens (we don't have any for this function, so we'll cover those later), braces {,}, and then the code held within the parens. The first thing in our function is local variable declaration. Local variables may only be used within this function (create). Notice I used the data types string and int[eger]. I also named the two variables "name" and "level" so that anyone reading this code later can understand what information I will store within.
Next, we have our real code that allows us to do something. First, I store an object into the "player" global variable. In this case, I searched for Pillar's player character object using a function call to find_player with his name as an argument. Find_player searches a secret database for a list of player objects until it finds one that has the name "pillar". It then returns the object in memory that pertains to Pillar's PC object. Storing it into the global player variable allows me to access it later by reference. The next two lines do just that. I ask Pillar's PC object what its name is, and it gives me "Pillar" and stores this string into the local variable "name". I then ask it his level, and it stores a number (whatever Pillar's level is currently... 24? I forget...). Then I make a function call to the write() function with an argument of a string that I want it to tell to the current player that caused the create function to be called. You can see that the string is not a perfect string because we're "computing" the string on the fly. The resulting string will end up being
"Pillar's name is Pillar and he is level 24\n"
The write() function will print this on your screen without the quotes, and the \n will be translated into an invisible NEWLINE character that makes the invisible cursor move to the next line and first column (like a typewriter or keyboard RETURN/ENTER key).
And then the closing brace } ends that function.
Now our code should look like this:
#pragma strict_types
private static object player;
void create()
{
string name;
int level;
player = find_player("pillar");
name = player->query_name();
level = player->query_level();
write("Pillar's name is "+name+" and he is level "+level+"\n");
}
A function is processed whenever it is called by the driver or another object, or from within the object itself. For right now, I can't show you the latter two, but I can explain a quick function call coming from the driver. I was being sneaky by naming my function "create()" because this function name is special only in the sense that the driver will call it upon loading the object. So, every time I load our LPC object, the driver will call create(), and then I will see a message telling me what Pillar's name and level is at that time.
To show you about calling other objects, you can look at query_name(), query_level(), and write(). The first two are local functions (called lfuns) that exist within Pillar's player object. These lfuns had to be written inside the player object in order for them to exist there, just like I wrote my create() function, or else it wouldn't otherwise exist at this point. The write() function is an external function (called an efun) and exists in the driver. To understand what the driver is, imagine an executable file on Windows or Linux that you can load and run. Now imagine we're stuck inside this object in a micro-world that it holds inside. The driver creates a virtual operating system, so to speak, from within which we play a MUD, as well as write code and alter the environment and the objects within. write() is usually defined inside the driver and thus is not easily changed like LPC code is. The driver is usually written in the language C (at least for LPMUDs), and so the write() function is actually a C code function. My LPC code function create() can interact with the efuns (C code functions) because that's how the driver was designed. You as an interactive user (the person reading this text right now) can receive messages on your computer screen in your terminal (zMUD, GMUD, telnet, etc.) usually because of functions like write() or tell_object(). They handle all the gobbledygook necessary to transmit a string of characters to your terminal from deep inside the driver executable. All we need to worry about here is sending a string through the efuns given to us by the driver, like write() and tell_object().
Real quickly, I'll touch on this_player(). The efun this_player() returns an object pointer (pointers point to a block of memory that holds information about your player character BECAUSE it is linked to a user (you/your Internet connection to the game). So writing/telling to a monster is rather pointless for most games, since monsters don't have an interactive user sitting at a computer and controlling them. Anyway, usually any time a process is happening, there is an interactive user responsible for it. An example of this is when I load this test object, I am the one who caused it. Thus, my player character object is temporarily stored in memory as the causer and can be retrieved by calling the efun this_player(). You could set this to a variable, such as player = this_player() and then do all sorts of cool queries on it, such as player->query_name() and player->query_level() on ME instead of Pillar. Now, once this current thread is finished, this_player() is cleared out to zero (0) and then the next process is begun and this_player() is set to whoever caused it to happen. So when you issue a command into the game, you're this_player() so long as that command is being processed. The write("a string to be written to the user's terminal") efun is shorthand for tell_object(this_player(), "a string to be written to the user's terminal").
I will also take a moment to tell you that the above file will error upon loading. Why? Well, remember that we told the driver to be strict on our typing (as in data types, not hitting keys on a keyboard), and so:
name = player->query_name();
level = player->query_level();
Should be written as:
name = (string)player->query_name();
level = (int)player->query_level();
However, we don't have to "cast" the type of
player = find_player("pillar");
Because find_player() is an efun, and the driver already knows what type this function returns. The only function calls we need to cast when using strict_types are ones to other LPC objects, such as Pillar's player character. Also, if we call a function without our own object, we don't have to cast because the driver is currently mulling over our particular object, and thus currently has function return types in mind.
I will get into inheritance later, but just for a quick relevance to the subject at hand, you can call a function that your object has inherited as if it were local as well. What I mean by calling "as if it were local" is as follows:
To call another LPC object, you must use what's called a call_other. call_other() is an efun that facilitates calling other objects. This efun returns whatever the other object's function returned. A shorthand to this is the -> efun. Notice that -> looks nothing like call_other(), write(), create(), or tell_object() mentioned so far. That's because it's shorthand, as I said. Some people got tired of writing call_other(object, function, arguments...) and decided to let you do object->function(arguments...).
Now to quickly show you a mock function query_name and query_level in Pillar's player object:
//obj/player.c
#pragma strict_types
private string name;
private int level;
string query_name()
{
return capitalize(name);
}
int query_level()
{
return level;
}
Note that this is not usually what these functions look like on any given MUD. But I wanted to show you how the "return" process looks. It's quite simple in that you have "return" followed by a variable (and the contents of the variable are sent back out to the caller) or static information, meaning hard-coded stuff like
int query_big_number()
{
return 4562273859;
}
Also notice that the name variable (which holds the string "pillar") is first capitalized to "Pillar" before being sent out. capitalize("yourname") ==> "Yourname"
To call an efun or a function that's held within your object (including those you inherit from another object), you simply call them like write(), tell_object(), etc. Since we only have one function in our object, we will write another one to show internal calls. Before we do that, let us re-organize our object since we now know that create gets called upon loading. So we'll do our processing in another function, and let create call it upon loading after the driver calls create. We can easily do this by changing the name create to check_pillar, and then adding a new create function and making a local function call to check_pillar. We'll also prototype our new function and I will explain that after.
//w/testwiz/test.c
#pragma strict_types
private static object player;
void check_pillar();
void create()
{
write("Starting create().\n");
write("Calling check_pillar().\n");
check_pillar();
write("Ending create().\n");
}
void check_pillar()
{
string name;
int level;
player = find_player("pillar");
name = player->query_name();
level = player->query_level();
write("Pillar's name is "+name+" and he is level "+level+"\n");
}
Okay. Here we moved the meat of our code to our new function check_pillar(), we prototyped the function at the beginning of the file, and then called it from within create(). Notice also that we have some write() calls with messages explaining what we're doing. I will see these when I load the object.
Now I'm going to load the object and here is what I see:
Starting create().
Calling check_pillar().
Pillar's name is Pillar and he is level 24.
Ending create().
When a function executes, if it calls another function, the primary function pauses, the called function executes, and then once it's done, our main function continues from where it left off.
to be continued...
A blank .c file [i.e. test.c] has no data within it. Because it has the file extension .c, it can be loaded into memory as an LPC object. The word object in this case refers to an LPC object, not necessarily a game object (room, weapon, armour, etc.) Notice that I say that test.c is completely empty. So when it loads, pretty much nothing happens. So now we need to put some code into it so that it becomes more useful.
The first thing we want to do is to ask the driver to "spell check" our code more thoroughly than it will by default. So the first line we want is:
#pragma strict_types
Note: I mentioned types in another thread "examples?". Also, note that "//w/testwiz/test.c" is just a comment (stuff that doesn't get executed but is there to tell the reader of the code something). This is telling you which file we're looking at because later we will look at other files.
The "strict types" pragma asks the driver to force us to put types where needed, and to "cast" types for external calls to other objects where we would otherwise not know what data type the other object is giving us. Without this pragma, you can relax your code style a bit, but if you make a mistake, it may not show up until some hapless player runs into it and it bugs out. So strict_types helps us squash any bugs a bit earlier. Some MUDs have this automatically defined as default for you, though other MUDs do not.
Still, this object does practically nothing upon loading. Before we get some oomph into it, let's set up some variables by "declaring" them. We declare these at the top so that they are available throughout the object. An object is read from line 1 to the last line. So if a variable or function is "used" or "called" and has not yet been "declared", you will generally see an error (there are some cases where this is not true, but I won't focus on that now).
private static object player;
Here we declared the global variable named "player" of the type "object" with the variably types "private" and "static". "player" is a name I came up with, and can be most anything, though there are some rules on the naming convention. I think one of the rules is it must begin with a letter. "object" is an LPC data type. Remember, objects are LPC objects here, not necessarily rooms, weapons, armours, or players, though these are included in this long list of LPC objects. "private" and "static" are special modifiers that will not be covered in this lesson. Global variables may be used anywhere within this LPC object.
Adding this variable did not allow our object to come alive just yet, but since we're going to use it in the follow section, we needed to declare it beforehand so that it doesn't error when we use it.
And now for our first function!
void create()
{
string name;
int level;
player = find_player("pillar");
name = player->query_name();
level = player->query_level();
write("Pillar's name is "+name+" and he is level "+level+"\n");
}
Now we've added quite a bit of code here. Let's begin. First, you'll notice "void create()". This is the function declaration, and "void" is the LPC data type that this function is expected to "return". "create" is the arbitrary name of the function just like "player" was the arbitrary name of the variable I declared above. Void is sort of an LPC data type, but it means that the function will return nothing at all. Thus, you'll note there is no "return" statement in the function, because omitting it is the same as having it as far as the void function type is concerned. We'll cover functions that return something of use later on.
A function consists of a name, return type, modifiers (I didn't use any, and we'll cover those later [they will be similar to private and static for variables]). The parenthesis (,), any arguments within the parens (we don't have any for this function, so we'll cover those later), braces {,}, and then the code held within the parens. The first thing in our function is local variable declaration. Local variables may only be used within this function (create). Notice I used the data types string and int[eger]. I also named the two variables "name" and "level" so that anyone reading this code later can understand what information I will store within.
Next, we have our real code that allows us to do something. First, I store an object into the "player" global variable. In this case, I searched for Pillar's player character object using a function call to find_player with his name as an argument. Find_player searches a secret database for a list of player objects until it finds one that has the name "pillar". It then returns the object in memory that pertains to Pillar's PC object. Storing it into the global player variable allows me to access it later by reference. The next two lines do just that. I ask Pillar's PC object what its name is, and it gives me "Pillar" and stores this string into the local variable "name". I then ask it his level, and it stores a number (whatever Pillar's level is currently... 24? I forget...). Then I make a function call to the write() function with an argument of a string that I want it to tell to the current player that caused the create function to be called. You can see that the string is not a perfect string because we're "computing" the string on the fly. The resulting string will end up being
"Pillar's name is Pillar and he is level 24\n"
The write() function will print this on your screen without the quotes, and the \n will be translated into an invisible NEWLINE character that makes the invisible cursor move to the next line and first column (like a typewriter or keyboard RETURN/ENTER key).
And then the closing brace } ends that function.
Now our code should look like this:
#pragma strict_types
private static object player;
void create()
{
string name;
int level;
player = find_player("pillar");
name = player->query_name();
level = player->query_level();
write("Pillar's name is "+name+" and he is level "+level+"\n");
}
A function is processed whenever it is called by the driver or another object, or from within the object itself. For right now, I can't show you the latter two, but I can explain a quick function call coming from the driver. I was being sneaky by naming my function "create()" because this function name is special only in the sense that the driver will call it upon loading the object. So, every time I load our LPC object, the driver will call create(), and then I will see a message telling me what Pillar's name and level is at that time.
To show you about calling other objects, you can look at query_name(), query_level(), and write(). The first two are local functions (called lfuns) that exist within Pillar's player object. These lfuns had to be written inside the player object in order for them to exist there, just like I wrote my create() function, or else it wouldn't otherwise exist at this point. The write() function is an external function (called an efun) and exists in the driver. To understand what the driver is, imagine an executable file on Windows or Linux that you can load and run. Now imagine we're stuck inside this object in a micro-world that it holds inside. The driver creates a virtual operating system, so to speak, from within which we play a MUD, as well as write code and alter the environment and the objects within. write() is usually defined inside the driver and thus is not easily changed like LPC code is. The driver is usually written in the language C (at least for LPMUDs), and so the write() function is actually a C code function. My LPC code function create() can interact with the efuns (C code functions) because that's how the driver was designed. You as an interactive user (the person reading this text right now) can receive messages on your computer screen in your terminal (zMUD, GMUD, telnet, etc.) usually because of functions like write() or tell_object(). They handle all the gobbledygook necessary to transmit a string of characters to your terminal from deep inside the driver executable. All we need to worry about here is sending a string through the efuns given to us by the driver, like write() and tell_object().
Real quickly, I'll touch on this_player(). The efun this_player() returns an object pointer (pointers point to a block of memory that holds information about your player character BECAUSE it is linked to a user (you/your Internet connection to the game). So writing/telling to a monster is rather pointless for most games, since monsters don't have an interactive user sitting at a computer and controlling them. Anyway, usually any time a process is happening, there is an interactive user responsible for it. An example of this is when I load this test object, I am the one who caused it. Thus, my player character object is temporarily stored in memory as the causer and can be retrieved by calling the efun this_player(). You could set this to a variable, such as player = this_player() and then do all sorts of cool queries on it, such as player->query_name() and player->query_level() on ME instead of Pillar. Now, once this current thread is finished, this_player() is cleared out to zero (0) and then the next process is begun and this_player() is set to whoever caused it to happen. So when you issue a command into the game, you're this_player() so long as that command is being processed. The write("a string to be written to the user's terminal") efun is shorthand for tell_object(this_player(), "a string to be written to the user's terminal").
I will also take a moment to tell you that the above file will error upon loading. Why? Well, remember that we told the driver to be strict on our typing (as in data types, not hitting keys on a keyboard), and so:
name = player->query_name();
level = player->query_level();
Should be written as:
name = (string)player->query_name();
level = (int)player->query_level();
However, we don't have to "cast" the type of
player = find_player("pillar");
Because find_player() is an efun, and the driver already knows what type this function returns. The only function calls we need to cast when using strict_types are ones to other LPC objects, such as Pillar's player character. Also, if we call a function without our own object, we don't have to cast because the driver is currently mulling over our particular object, and thus currently has function return types in mind.
I will get into inheritance later, but just for a quick relevance to the subject at hand, you can call a function that your object has inherited as if it were local as well. What I mean by calling "as if it were local" is as follows:
To call another LPC object, you must use what's called a call_other. call_other() is an efun that facilitates calling other objects. This efun returns whatever the other object's function returned. A shorthand to this is the -> efun. Notice that -> looks nothing like call_other(), write(), create(), or tell_object() mentioned so far. That's because it's shorthand, as I said. Some people got tired of writing call_other(object, function, arguments...) and decided to let you do object->function(arguments...).
Now to quickly show you a mock function query_name and query_level in Pillar's player object:
//obj/player.c
#pragma strict_types
private string name;
private int level;
string query_name()
{
return capitalize(name);
}
int query_level()
{
return level;
}
Note that this is not usually what these functions look like on any given MUD. But I wanted to show you how the "return" process looks. It's quite simple in that you have "return" followed by a variable (and the contents of the variable are sent back out to the caller) or static information, meaning hard-coded stuff like
int query_big_number()
{
return 4562273859;
}
Also notice that the name variable (which holds the string "pillar") is first capitalized to "Pillar" before being sent out. capitalize("yourname") ==> "Yourname"
To call an efun or a function that's held within your object (including those you inherit from another object), you simply call them like write(), tell_object(), etc. Since we only have one function in our object, we will write another one to show internal calls. Before we do that, let us re-organize our object since we now know that create gets called upon loading. So we'll do our processing in another function, and let create call it upon loading after the driver calls create. We can easily do this by changing the name create to check_pillar, and then adding a new create function and making a local function call to check_pillar. We'll also prototype our new function and I will explain that after.
//w/testwiz/test.c
#pragma strict_types
private static object player;
void check_pillar();
void create()
{
write("Starting create().\n");
write("Calling check_pillar().\n");
check_pillar();
write("Ending create().\n");
}
void check_pillar()
{
string name;
int level;
player = find_player("pillar");
name = player->query_name();
level = player->query_level();
write("Pillar's name is "+name+" and he is level "+level+"\n");
}
Okay. Here we moved the meat of our code to our new function check_pillar(), we prototyped the function at the beginning of the file, and then called it from within create(). Notice also that we have some write() calls with messages explaining what we're doing. I will see these when I load the object.
Now I'm going to load the object and here is what I see:
Starting create().
Calling check_pillar().
Pillar's name is Pillar and he is level 24.
Ending create().
When a function executes, if it calls another function, the primary function pauses, the called function executes, and then once it's done, our main function continues from where it left off.
to be continued...